【问题标题】:How do I use a Flutter MethodChannel to invoke a method in dart code from the native swift code?如何使用 Flutter MethodChannel 从原生 swift 代码调用 dart 代码中的方法?
【发布时间】:2021-02-24 18:36:42
【问题描述】:

我已经查看了很多关于这个主题的类似问题,但没有一个解决方案对我有用。我正在 Flutter 中开发一个应用程序,但想从 AppDelegate.swift 调用我的 main.dart 文件中的特定方法在原生 iOS 项目中。

为了删除所有其他变量,我已将问题提取到一个新的 dart 项目中。我正在尝试使用methodChannel.invokeMethod()AppDelegate.swift 调用setChannelText(),但没有成功。

有人知道我哪里出错了吗?我知道我没有对methodChannel.invokeMethod() 中的“名称”参数采取行动,但那是因为我只希望调用调用该方法...

这是我的 main.dart 文件:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  MethodChannel channel =
      new MethodChannel("com.example.channeltest/changetext");
  String centerText;
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: Colors.purple,
        body: Center(
          child: Text(
            centerText,
            style: TextStyle(
              color: Colors.white,
              fontWeight: FontWeight.bold,
              fontSize: 30.0,
            ),
          ),
        ),
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    this.channel.setMethodCallHandler((call) async => await setChannelText());
    this.centerText = "Hello World!";
  }

  Future setChannelText() async {
    Future.delayed(Duration(milliseconds: 200));
    setState(() => this.centerText = "Another Text.");
  }
}

这是我的 AppDelegate.swift 文件:

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    var methodChannel: FlutterMethodChannel!
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions:                 

[UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    
    let rootViewController : FlutterViewController = window?.rootViewController as! FlutterViewController
    methodChannel = FlutterMethodChannel(name: "com.example.channeltest/changetext", binaryMessenger: rootViewController as! FlutterBinaryMessenger)
    
    //This call would obviously be somewhere else in a real world example, but I'm just
    //testing if I can invoke the method in my dart code at all..
    methodChannel.invokeMethod("some_method_name", arguments: nil)
    
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

最后,我试图在启动后立即更改文本,但事实并非如此。

Screenshot of app running on iOS simulator

提前感谢您的帮助!

【问题讨论】:

标签: ios swift flutter dart


【解决方案1】:

问题

问题是您的平台端(在本例中为 iOS)正在调用 Flutter 端的方法 Flutter 准备好之前。无法从平台端进行检查,因此您的 Flutter 应用必须告诉您的平台端。您在 Android 上也会遇到同样的问题。

解决方案

要克服这个问题,您必须告诉平台方应用程序已准备就绪(通过发送平台方法)并将其保存在布尔值中,或实例化一个类并调用一个方法。然后平台端就可以开始发送消息了。

您真的应该阅读日志,它应该会警告您以下内容:“没有任何东西在听这个,或者 Flutter 引擎未连接”。

import 'dart:async';

import 'package:flutter/src/services/platform_channel.dart';

class StringService {
  final methodChannel =
      const MethodChannel("com.example.app_name.method_channel.strings");

  final StreamController<String> _stringStreamController =
      StreamController<String>();

  Stream<String> get stringStream => _stringStreamController.stream;

  StringService() {
    // Set method call handler before telling platform side we are ready to receive.
    methodChannel.setMethodCallHandler((call) async {
      print('Just received ${call.method} from platform');
      if (call.method == "new_string") {
        _stringStreamController.add(call.arguments as String);
      } else {
        print("Method not implemented: ${call.method}");
      }
    });
    // Tell platform side we are ready!
    methodChannel.invokeMethod("isReady");
  }
}

您可以在reverse_platform_methods,尤其是AppDelegate.swift 看到一个工作项目。我没有为 Android 实现它,但您可以在 MainActivity.kt 中以类似的方式实现它。

问题

大多数应用不希望代码首先从平台端调用您的用例是什么?根据您的回答,我可能会提供更好的建议。我实现了这个来处理推送到设备的推送通知,所以“事件”肯定是从平台端触发的。

此外,如果您遇到错误和警告,您应该显示它们,例如No implementation found for method $method on channel $name'.

【讨论】:

    【解决方案2】:

    嗯,问题在于初始化过程。在 dart/flutter 部分准备好处理它之前,您尝试从 swift 代码中调用您的方法。

    您必须执行以下步骤才能获得结果:

    1. 重要。在你的AppDelegate for ios 中使用applicationDidBecomeActive 方法
    @UIApplicationMain
    @objc class AppDelegate: FlutterAppDelegate {
        
     var methodChannel: FlutterMethodChannel? = nil
    
      override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
    
        print("Setup methodChannel from Swift")
        let rootViewController : FlutterViewController = window?.rootViewController as! FlutterViewController
        methodChannel = FlutterMethodChannel(name: "com.example.channeltest/changetext", binaryMessenger: rootViewController as! FlutterBinaryMessenger)
    
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
      }
        
        //THIS METHOD
        override func applicationDidBecomeActive(_ application: UIApplication) {
            methodChannel?.invokeMethod("some_method_name", arguments: "ios string")
        }
    }
    

    对于安卓onStart()方法:

    class MainActivity : FlutterActivity() {
        var channel: MethodChannel? = null
    
        override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
            super.configureFlutterEngine(flutterEngine)
    
            channel = MethodChannel(
                flutterEngine.dartExecutor.binaryMessenger,
                "com.example.channeltest/changetext"
            )
    
        }
    
        override fun onStart() {
            super.onStart()
            channel?.invokeMethod("some_method_name", "android str")
        }
    }
    
    1. 使用MethodChannel 创建您自己的课程(如上一个答案)
    class TestChannel {
      static MethodChannel channel =
      const MethodChannel("com.example.channeltest/changetext");
    
      final StreamController<String> _controller =
      StreamController<String>();
    
      Stream<String> get stringStream => _controller.stream;
    
      TestChannel() {
        channel.setMethodCallHandler((call) async {
          if (call.method == "some_method_name") {
            _controller.add(call.arguments as String);
          } else {
            print("Method not implemented: ${call.method}");
          }
        });
      }
    }
    
    1. 重要。创建它的全局实例
    final _changeTextChannel = TestChannel(); //<--- like this
    
    void main() {
      runApp(MyApp());
    }
    
    1. 在 UI 中处理
    class TestPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
              child: StreamBuilder<String>(
            stream: _changeTextChannel.stringStream,
            builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
              if (snapshot.hasError) {
                return Text("Error");
              }
    
              if (!snapshot.hasData) {
                return Text("Loading");
              }
    
              return Text(snapshot.data ?? "NO_DATA");
            },
          )),
        );
      }
    }
    

    【讨论】:

    • 虽然我认为这是一个非常简洁的解决方案,简化了代码,但这个解决方案需要在 Flutter 应用运行时立即设置方法调用处理程序,这是一个主要缺点。如果您需要在准备好响应来自 Dart 端的消息之前发出网络请求或读取 Shared Preferences 怎么办?当然,我们在这里使用流并且可以缓冲来自平台的消息,但是当我们想要 2 个侦听器时会发生什么,如果我们使用广播流,我们将没有缓冲区,并且消息会丢失。
    • 感谢您的评论。您知道很难找到“灵丹妙药”,尤其是在编程中,尤其是在没有额外上下文的情况下。问题是关于如何从平台端向一个方向调用颤振端,而不从颤动端发送第一个请求。我试图回答这个问题。那么,关于流的问题呢?例如,我们可以将来自 some_method_name 的所有数据存储在列表中,并将 stringStream 更改为从列表中发出所有数据项的生成器。在这种情况下,您可以获得所有 StreamBuilder 中的所有数据。 @BenButt
    【解决方案3】:

    Flutter 端代码:

    import 'dart:async';
    
    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    
    class _MyHomePageState extends State<MyHomePage> {
      static const platform = MethodChannel('samples.flutter.dev/battery');
    
      // Get battery level.
      String _batteryLevel = 'Unknown battery level.';
    
      Future<void> _getBatteryLevel() async {
        String batteryLevel;
        try {
          final int result = await platform.invokeMethod('getBatteryLevel');
          batteryLevel = 'Battery level at $result % .';
        } on PlatformException catch (e) {
          batteryLevel = "Failed to get battery level: '${e.message}'.";
        }
    
        setState(() {
          _batteryLevel = batteryLevel;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Material(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  child: Text('Get Battery Level'),
                  onPressed: _getBatteryLevel,
                ),
                Text(_batteryLevel),
              ],
            ),
          ),
        );
      }
    }

    此处为 Swift 代码:

    @UIApplicationMain
    @objc class AppDelegate: FlutterAppDelegate {
      override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
        let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
        let batteryChannel = FlutterMethodChannel(name: "samples.flutter.dev/battery",
                                                  binaryMessenger: controller.binaryMessenger)
        batteryChannel.setMethodCallHandler({
          [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
          // Note: this method is invoked on the UI thread.
          guard call.method == "getBatteryLevel" else {
            result(FlutterMethodNotImplemented)
            return
          }
          self?.receiveBatteryLevel(result: result)
        })
    
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
      }
    }

    或者参考这个链接:

    Platform Channels

    【讨论】:

    • 您错过了问题的重点,OP 希望从 Swift 与 Dart 进行通信,而不是相反。另外,您不应该只是发布源代码块,请尝试实际回答。
    • 这只是从 Flutter 文档中复制粘贴代码,没有理解问题或文档,甚至没有解释它。
    猜你喜欢
    • 2021-08-05
    • 2020-03-03
    • 2021-11-21
    • 2021-12-28
    • 2019-05-25
    • 2020-04-25
    • 2020-07-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多