Two-way communication between Flutter and Android iOS

The complete code in this article is here: GitHub - zhaolongs/Flutter-Android-iOS: Two-way communication demo between Flutter and Android iOs

Note for demo running:

1. Json data parsing exception: org.json.JSONException: Unterminated object at character 40

Solution: remove the spaces in ({"method":"test","ontent":"flutter data","code":100});

platform channel

Messages are passed between the client (UI) and the host (platform) using platform channels , as shown in the following diagram:

Messages and responses are delivered asynchronously to ensure that the user interface remains responsive.

A brief introduction to the three APIs of Platfrom Channel

  • MethodChannel : Flutter calls each other with native methods for method usage.
  • EventChannel : Send messages natively, received by Flutter, used for data flow communication
  • BasicMessageChannel : Flutter and native send messages to each other for data transfer
     

EventChannel is used to send Android native event streams to Flutter , such as sending notifications to Flutter after state changes such as native monitoring of gravity sensing, one-to-many notifications, similar to native broadcasting. EventChannel can only send messages to flutter natively, and cannot call and send messages with flutter. The other two can call and send messages with flutter.

The three channels are independent of each other and have their own purposes, but they are very similar in design. Each Channel has three important member variables:

name: String type, representing the name of the Channel and its unique identifier.
messager: The BinaryMessenger type, representing the message messenger, is a tool for sending and receiving messages.
codec: MessageCodec type or MethodCodec type, representing the codec of the message.
Example: MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec)

Note: There may be multiple Channels in a Flutter application. Each Channel must specify a unique name when it is created. Channels use names to distinguish each other. When a message is sent from the Flutter side to the Platform side, the Handler (message handler) corresponding to the Channel will be found according to the channel name passed by it.

Let's first understand the concepts of BinaryMessenger, Codec, and Handler.

BinaryMessenger

BinaryMessenger is a tool for communication between PlatformChannel and Flutter. The message format used in its communication is binary format data. BinaryMessenger is an interface in Android, and its implementation class is FlutterNativeView.

Codec

Codec is a message codec, which is mainly used to convert data in binary format into data that Handler can recognize. Flutter defines two Codecs: MessageCodec and MethodCodec. MessageCodec is used for codec between binary format data and basic data. The codec used by BasicMessageChannel is MessageCodec. The codec used by MethodChannel and EventChannel is MethodCodec.

Handler

Flutter defines three types of Handlers, which correspond one-to-one with the PlatformChannel types, namely MessageHandler, MethodHandler, and StreamHandler. When using PlatformChannel, a Handler will be registered for it, PlatformChannel will decode the binary data into data that can be recognized by Handler through Codec, and hand it over to Handler for processing. When the Handler finishes processing the message, it will return the result through the callback function, encode the result into binary format data through the codec, and send it back to the Flutter side through BinaryMessenger.

This article will implement: (via MethodChannel)

  • Implement Flutter to call Android and iOS native methods and call back to Flutter
  • Implement Flutter to call Android and iOS native and open an Activity page native to Android and a ViewController page native to iOS
  • Implement Android, iOS native active sending of messages to Flutter
  • Implement Android and iOS native TestActivity pages to actively send messages to Flutter

The effect in Android (this picture is still the rendering of BasicMessageChannel, but it is realized by MethodChannel)

insert image description here

The effect in ios (this picture is still the rendering of BasicMessageChannel, but it is realized by MethodChannel)

insert image description here

foreword
   

1. For example, if we want to realize that A calls B, B will trigger, and B then calls A, and A will trigger such a function.
2. Then we need to set the monitoring method called by B in A, and set the monitoring method in B to be called by B. The listener method called by A 

implements Flutter and calls the Andoid iOS native method and calls back
   
to implement the calling method in Flutter
 

//创建MethodChannel
  // flutter_and_native_101 为通信标识
  // StandardMessageCodec() 为参数传递的 编码方式
  static const methodChannel = const MethodChannel('flutter_and_native_101');

 

  //封装 Flutter 向 原生中 发送消息 的方法 
  //method 为方法标识
  //arguments 为参数
  static Future<dynamic> invokNative(String method, {Map arguments}) async {
    if (arguments == null) {
      //无参数发送消息
      return await methodChannel.invokeMethod(method);
    } else {
      //有参数发送消息
      return await methodChannel.invokeMethod(method, arguments);
    }
  }


Trigger the call, which is triggered in the click events of the three Buttons respectively, and no parameters are passed to the native Android iOS below.

   invokNative("test")
     ..then((result) {
        //第一种 原生回调 Flutter 的方法
        //此方法只能使用一次
        int code = result["code"];
        String message = result["message"];
       setState(() {
          recive = "invokNative 中的回调 code $code message $message ";
        });
   });
//用来实现 Android iOS 主动触发 向 Flutter 中发送消息
invokNative("test2");
//用来实现 Flutter 打开 Android iOS 中的一个新的页面
invokNative("test3");


Implement the listener method in Android and call back


Registering message listeners in Android's MainActivity

    

private MethodChannel mMethodChannel;
    //记着要在 onCreat方法中调用
    private void methodChannelFunction() {
        mMethodChannel = new MethodChannel(getFlutterView(), "flutter_and_native_101");
        //设置监听
        mMethodChannel.setMethodCallHandler(
                new MethodChannel.MethodCallHandler() {
                    @Override
                    public void onMethodCall(MethodCall call, MethodChannel.Result result) {
                        String lMethod = call.method;
                        // TODO
                        if (lMethod.equals("test")) {
                            Toast.makeText(mContext, "flutter 调用到了 android test", Toast.LENGTH_SHORT).show();
                            Map<String, Object> resultMap = new HashMap<>();
                            resultMap.put("message", "result.success 返回给flutter的数据");
                            resultMap.put("code", 200);
                            //发消息至 Flutter 
                            //此方法只能使用一次
                            result.success(resultMap);
                            
                        } else if (lMethod.equals("test2")) {
                            Toast.makeText(mContext, "flutter 调用到了 android test2", Toast.LENGTH_SHORT).show();
                            Map<String, Object> resultMap = new HashMap<>();
                            resultMap.put("message", "android 主动调用 flutter test 方法");
                            resultMap.put("code", 200);
                            //主动向Flutter 中发送消息
                            mMethodChannel.invokeMethod("test", resultMap);
                            //延迟2秒再主动向 Flutter 中发送消息
                            mHandler.postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    Map<String, Object> resultMap2 = new HashMap<>();
                                    resultMap2.put("message", "android 主动调用 flutter test 方法");
                                    resultMap2.put("code", 200);
                                    mMethodChannel.invokeMethod("test2", resultMap2);
                                }
                            }, 2000);
                            
                            
                        } else if (lMethod.equals("test3")) {
                            //测试通过Flutter打开Android Activity
                            Toast.makeText(mContext, "flutter 调用到了 android test3", Toast.LENGTH_SHORT).show();
                            Intent lIntent = new Intent(MainActivity.this, TestMethodChannelActivity.class);
                            MainActivity.this.startActivity(lIntent);
                        } else {
                            result.notImplemented();
                        }
                    }
                }
        );
    }

Implement the listener method in iOS and call back


AppDelegate in iOS



#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
#import <Flutter/Flutter.h>
//TestViewController 是创建的一个 测试页面
#import "TestViewController.h"

@implementation AppDelegate{
    FlutterMethodChannel* methodChannel;
}

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [GeneratedPluginRegistrant registerWithRegistry:self];
        ... ... 
    //FlutterMethodChannel 与 Flutter 之间的双向通信
    [self  methodChannelFunction];

        ... ... 
    
    
    
    return [super application:application didFinishLaunchingWithOptions:launchOptions];
}


-(void) methodChannelFunction{
    FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
    //创建 FlutterMethodChannel
    // flutter_and_native_101 是通信标识
    methodChannel = [FlutterMethodChannel
                     methodChannelWithName:@"flutter_and_native_101"
                     binaryMessenger:controller];
    //设置监听
    [methodChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
        // TODO
        NSString *method=call.method;
        if ([method isEqualToString:@"test"]) {
            
            NSLog(@"flutter 调用到了 ios test");
            NSMutableDictionary *dic = [NSMutableDictionary dictionary];
            [dic setObject:@"result.success 返回给flutter的数据" forKey:@"message"];
            [dic setObject: [NSNumber numberWithInt:200] forKey:@"code"];
            //FlutterResult回调 发消息至 Flutter 中
            //此方法只能调用一次
            result(dic);
            
        }else  if ([method isEqualToString:@"test2"]) {
            NSLog(@"flutter 调用到了 ios test2");
            NSMutableDictionary *dic = [NSMutableDictionary dictionary];
            [dic setObject:@"result.success 返回给flutter的数据" forKey:@"message"];
            [dic setObject: [NSNumber numberWithInt:200] forKey:@"code"];
            //通过此方法 可以主动向Flutter中发送消息
            //可以多次调用
            [methodChannel invokeMethod:@"test" arguments:dic];
        }else  if ([method isEqualToString:@"test3"]) {
            NSLog(@"flutter 调用到了 ios test3 打开一个新的页面 ");
            TestViewController *testController = [[TestViewController alloc]initWithNibName:@"TestViewController" bundle:nil];
            [controller presentViewController:testController animated:YES completion:nil];
        }
        
    }];
    
    
}

@end

Android and iOS natively actively send messages to Flutter

Implement active mobilization calling method in Android

In MainActivity, an instance of MethodChannel, mMethodChannel, is created, and the mMethodChannel instance can be used directly in MainActivity to send messages to Flutter.
 

	
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("message", "android 主动调用 flutter test 方法");
resultMap.put("code", 200);
//主动向Flutter 中发送消息
mMethodChannel.invokeMethod("test", resultMap);

In other Activity pages, we can't use this instance. One of my methods here to implement the newly created Activity page in Android to send messages to Flutter is the broadcast mechanism

Register the broadcast in MainActivity, and send messages through the instance mMessageChannel of BasicMessageChannel in the broadcast receiver.

Send the broadcast to the broadcast receiver in MainActivity from other pages in Android, so that the newly created Activity page in Android can send messages to Flutter
 

public class MainActivity extends FlutterActivity {
	
	
	... ...
	
	Handler mHandler = new Handler(Looper.myLooper());
	private MainReceiver mMainReceiver;
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
		//注销广播
		unregisterReceiver(mMainReceiver);
	}
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		... ...
		//注册广播
		mMainReceiver = new MainReceiver();
		IntentFilter lIntentFilter = new IntentFilter("android.to.flutter");
		registerReceiver(mMainReceiver, lIntentFilter);
		
		
	}
	
	
	public class MainReceiver extends BroadcastReceiver {
		public MainReceiver() {
		}
		
		@Override
		public void onReceive(Context context, Intent intent) {
			Toast.makeText(context, "接收到自定义的广播", Toast.LENGTH_SHORT).show();
			mHandler.post(new Runnable() {
				@Override
				public void run() {
					Map<String, Object> resultMap2 = new HashMap<>();
					resultMap2.put("message", "android 主动调用 flutter test 方法");
					resultMap2.put("code", 200);
					
					if (mMethodChannel != null) {
						// 向Flutter 发送消息
						mMethodChannel.invokeMethod("test2", resultMap2);
					}
					
				}
			});
		}
	}
}

Implement the listener call method in Flutter

 
  //创建MethodChannel
  // flutter_and_native_101 为通信标识
  // StandardMessageCodec() 为参数传递的 编码方式
  static const methodChannel = const MethodChannel('flutter_and_native_101');

  //设置消息监听
  Future<dynamic> nativeMessageListener() async {
    methodChannel.setMethodCallHandler((resultCall) {

      //处理原生 Android iOS 发送过来的消息
      MethodCall call = resultCall;
      String method = call.method;
      Map arguments = call.arguments;

      int code = arguments["code"];
      String message = arguments["message"];
      setState(() {
        recive += " code $code message $message and method $method ";
        print(recive);
      });
    });
  }

Implement active mobilization calling method in iOS

#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
#import <Flutter/Flutter.h>
#import "TestViewController.h"

@implementation AppDelegate{
    FlutterBasicMessageChannel* messageChannel;
}

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [GeneratedPluginRegistrant registerWithRegistry:self];
    
    
    //注册通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationFuncion:) name:@"ios.to.flutter" object:nil];
    
    ... ...
    
    return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

   ... ... 

- (void)notificationFuncion: (NSNotification *) notification {
    // iOS 中其他页面向Flutter 中发送消息通过这里
    // 本页中 可以直接使用   [messageChannel sendMessage:dic];
    //处理消息
    NSLog(@"notificationFuncion ");
    NSMutableDictionary *dic = [NSMutableDictionary dictionary];
   
    if (messageChannel!=nil) {
        [dic setObject:@" [messageChannel sendMessage:dic]; 向Flutter 发送消息 " forKey:@"message"];
        [dic setObject: [NSNumber numberWithInt:401] forKey:@"code"];
        [messageChannel sendMessage:dic];
    }
    
}

- (void)dealloc {
    //单条移除观察者
    //[[NSNotificationCenter defaultCenter] removeObserver:self name:@"REFRESH_TABLEVIEW" object:nil];
    //移除所有观察者
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end


extend

Things to pay attention to after upgrading the flutter version:

The new Android plugin API has been available for the Android platform since version 1.12 was released. APIs based on PluginRegistry.Registrar will not be deprecated immediately, but we encourage you to migrate to APIs based on FlutterPlugin.

The biggest adjustment involved this time should be related to the improvement of Android plugins APIs. This adjustment requires users to re-adjust the code of Android modules and plugins in the Flutter project to adapt.

Please see the difference between the old and new versions here: https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects

I got this error after I developed the native communication between flutter and native:

[ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: MissingPluginException(No implementation found for method getAll on channel plugins.flutter.io/shared_preferences)

Workaround: https://github.com/flutter/flutter/issues/10912

I solved the problem by adding GeneratedPluginRegistrant.registerWith(flutterEngine); to the public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) method;
 

-import io.flutter.app.FlutterActivity;
-import io.flutter.plugin.common.MethodCall;
+import androidx.annotation.NonNull;
+import io.flutter.embedding.android.FlutterActivity;
+import io.flutter.embedding.engine.FlutterEngine;
 import io.flutter.plugin.common.MethodChannel;
-import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
-import io.flutter.plugin.common.MethodChannel.Result;
+import io.flutter.plugins.GeneratedPluginRegistrant;
 
 public class MainActivity extends FlutterActivity {
     private static final String CHANNEL = "samples.flutter.dev/battery";
-    //以前的注册方法
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-
-        super.onCreate(savedInstanceState);
-        GeneratedPluginRegistrant.registerWith(this);
-            
-        new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
-                new MethodCallHandler() {
-                    @Override
-                    public void onMethodCall(MethodCall call, Result result) {
-                        // Your existing code
-                    }
-                });
-    }
+    //现在的注册方法
+    @Override
+    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
+        //GeneratedPluginRegistrant.registerWith(flutterEngine);//父类方法已经实现注册
+        new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
+                .setMethodCallHandler(
+                    (call, result) -> {
+                        // Your existing code
+                }
+        );
+    }
 }

The configureFlutterEngine method in FlutterActivity is as follows

@Override
  public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    if (delegate.isFlutterEngineFromHost()) {
      // If the FlutterEngine was explicitly built and injected into this FlutterActivity, the
      // builder should explicitly decide whether to automatically register plugins via the
      // FlutterEngine's construction parameter or via the AndroidManifest metadata.
      return;
    }

    GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
  }

The following shows a simple usage of a high version of flutter

Flutter calls Android

Here is a simple Android function: pop up an AlertDialog, and then call this function in Flutter.

Android side implementation

First implement the function in MainActivity, as shown below.

package com.example.flutterdemo;

import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        super.configureFlutterEngine(flutterEngine);
        GeneratedPluginRegistrant.registerWith(flutterEngine);
        MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor(), "com.example.platform_channel/dialog");//2
        methodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
                if ("dialog".equals(call.method)) {
                    if (call.hasArgument("content")) {
                        result.success("成功");
                    } else {
                        result.error("error", "失败", "content is null");
                    }
                } else {
                    result.notImplemented();
                }
            }
        });
    }
}

Flutter side implementation

Add the following code to main.dart.

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  static const platformChannel =
      const MethodChannel('com.example.platform_channel/dialog');//1

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      home: Scaffold(
        appBar: AppBar(
          title: Text("Flutter调用Android"),
        ),
        body: Padding(
          padding: EdgeInsets.all(40.0),
          child: RaisedButton(
            child: Text("调用Dialog"),
            onPressed: () {
              showDialog("Flutter调用AlertDialog");
            },
          ),
        ),
      ),
    );
  }

  void showDialog(String content) async {
    var arguments = Map();
    arguments['content'] = content;
    try {
      String result = await platformChannel.invokeMethod('dialog', arguments);//2
      print('showDialog ' + result);
    } on PlatformException catch (e) {
      print('showDialog ' + e.code + e.message + e.details);
    } on MissingPluginException catch (e) {
      print('showDialog ' + e.message);
    }
  }
}

3. Android calls Flutter

Sometimes after Flutter calls Android, Android will return the result to Flutter. Although it can sometimes be implemented with result, the processing on the Android side may be asynchronous, and the result object cannot be held for a long time. At this time, Android is required to call Flutter. Because the UI of the page is drawn on the Flutter side, it is difficult for us to control the Android side in the page. To implement Android calling Flutter, we can use the life cycle of Android's Activty. If the application is switched to the background and then switched back to the foreground, the onResume method of Activty will It will be called, and we can implement the function of calling Flutter in the onResume method.

Android implementation

package com.example.flutterdemo;

import android.os.Bundle;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.HashMap;
import java.util.Map;

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {
    private static MethodChannel methodChannel;
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        super.configureFlutterEngine(flutterEngine);
        GeneratedPluginRegistrant.registerWith(flutterEngine);
        methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(),"com.example.platform_channel/text");

    }

    @Override
    protected void onResume() {
        super.onResume();
        Map map = new HashMap();
        map.put("content","Android进阶三部曲");
        methodChannel.invokeMethod("showText", map, new MethodChannel.Result() {//2
            @Override
            public void success(Object o) {
                Log.d(TAG,(String)o);
            }
            @Override
            public void error(String errorCode, String errorMsg, Object errorDetail) {
                Log.d(TAG,"errorCode:"+errorCode+" errorMsg:"+errorMsg+" errorDetail:"+(String)errorDetail);
            }
            @Override
            public void notImplemented() {
                Log.d(TAG,"notImplemented");
            }
        });

    }
}

Implementation on the Flutter side

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

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return MyAppState();
  }
}

class MyAppState extends State<MyApp> {
  static const platformChannel =
      const MethodChannel('com.example.platform_channel/text');

  String textContent = 'Flutter端初始文字';

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    platformChannel.setMethodCallHandler((methodCall) async {
      switch (methodCall.method) {
        case 'showText':
          String content = await methodCall.arguments['content'];
          if (content != null && content.isNotEmpty) {
            setState(() {
              textContent = content;
            });
            return 'success';
          } else {
            throw PlatformException(
                code: 'error', message: '失败', details: 'content is null');
          }
          break;
        default:
          throw MissingPluginException();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      home: Scaffold(
        appBar: AppBar(
          title: Text('Android调用Flutter'),
        ),
        body: Padding(
          padding: EdgeInsets.all(40.0),
          child: Text(textContent),
        ),
      ),
    );
  }
}

Guess you like

Origin blog.csdn.net/jdsjlzx/article/details/123483933