Flutter – Android, iOS , Native communication Simple Demo

By | December 24, 2018

Hello Devs,

You know flutter is now a hot topic. Learning flutter will always help in your future career.

In this article we will see how we can communicate between Flutter and Android.

Watch Video Tutorial

MethodChannel

Flutter communicate with Native using “MethodChannel“. So you have to create a method channel with a constant as identifier. Make sure you give the same constant name both on Native side(Android or iOS) and Flutter side as well…Here it is “MyNativeChannel“.

static const platform = const MethodChannel('MyNativeChannel');

Communication with native is always asynchronous, so the return type will always be of type “Future<>“. This is important to understand.

Below is a simple function that communicates with the Native side.

Future<void> getData() async {
    String message;
    try {
        message = await platform.invokeMethod('getData');
        print(message);
    } on PlatformException catch (e) {
        message = "Failed to get data from native : '${e.message}'.";
    }
    setState(() {
        msg = message;
    });
}

The method should be async with the await callback.
await platform.invokeMethod(‘getData’) will send the method name “getData” to the native side and on the native side we can check what is the method name and execute that method.

Android Native side

We are trying to call a method that is defined in the Android native side and we know that the method name is “getData()”.

Open the MainActivity inside the android/src/your_package folder and create a method named “getData()“. Our method simply returns a String and looks like this.

private String getData() {
    return "Message From Android";
}

iOS Native side

  NSString *from = call.arguments[@"from"];

Receive the event from Flutter Side

we have to define something on the Android side so that we can receive the call from Flutter side.

In your MainActivity

 private static final String CHANNEL = "MyNativeChannel";

 new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
                new MethodChannel.MethodCallHandler() {
                    @Override
                    public void onMethodCall(MethodCall call, MethodChannel.Result result) {
                        if (call.method.equals("getData")) {
                            result.success(getData());
                        } else {
                            result.notImplemented();
                        }
                    }
                });

When you call “getData()” function in Flutter that invokes the “MethodCallHandler” in the Android side and we will receive the function name with params from the Flutter side.

Complete MainActivity

The complete MainActivity would look like this.


import android.os.Bundle;

import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {

    private static final String CHANNEL = "MyNativeChannel";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);

        new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
                new MethodChannel.MethodCallHandler() {
                    @Override
                    public void onMethodCall(MethodCall call, MethodChannel.Result result) {
                        if (call.method.equals("getData")) {
                            result.success(getData());
                        } else {
                            result.notImplemented();
                        }
                    }
                });
    }

    private String getData() {
        return "Message From Android";
    }
}

iOS Native Side
Go to AppDelegate.m file and replace the contents with this

#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    FlutterViewController *controller = (FlutterViewController *) self.window.rootViewController;
    FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"MyChannel" binaryMessenger:controller];
    
    [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
        NSString *from = call.arguments[@"from"];
        if([@"myNativeFunction" isEqualToString:call.method]){
            NSString *messageToFlutter = [self myNativeFunction];
            messageToFlutter = [NSString stringWithFormat:@"%@, Back to %@", messageToFlutter, from];
            result(messageToFlutter);
        }
    }];
  [GeneratedPluginRegistrant registerWithRegistry:self];
  // Override point for customization after application launch.
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

-(NSString *) myNativeFunction{
    return @"Message from iOS";
}

@end

Note: Once you change the native side, you have restart the complete app to take effect.

Sending Params to Native

To Send params to the native side, you can modify the above flutter function like this.

Flutter Side

Future<void> getData() async {
    var params = <String, dynamic>{"from": "Flutter"};
    String message;
    try {
        message = await platform.invokeMethod('getData', params);
        print(message);
    } on PlatformException catch (e) {
        message = "Failed to get data from native : '${e.message}'.";
    }
    setState(() {
        msg = message;
    });
}

Android Side

Map<String, Object> params = (Map<String, Object>) call.arguments;
if (call.method.equals("getData")) {
    result.success(getData() + " Back to " + params.get("from"));
} else {
    result.notImplemented();
}

Complete Flutter Code

The complete flutter code will look like this…
Our UI has a Text and a button that will invoke the getData function in the Android side.
When we get the result back from the Android side, we will show it in the Text in the Flutter side.

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

class NativeDemoScreen extends StatefulWidget {
  @override
  _NativeDemoScreenState createState() => _NativeDemoScreenState();
}

class _NativeDemoScreenState extends State<NativeDemoScreen> {
  static const platform = const MethodChannel('MyNativeChannel');
  String msg = "No Message";

  Future<void> getData() async {
    String message;
    try {
      message = await platform.invokeMethod('getData');
      print(message);
    } on PlatformException catch (e) {
      message = "Failed to get data from native : '${e.message}'.";
    }
    setState(() {
      msg = message;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Native Demo"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              msg,
            ),
            RaisedButton(
              child: Text("Call Native Function"),
              onPressed: getData,
            )
          ],
        ),
      ),
    );
  }
}

It’s that simple. Let me know your feedback in the comments section below.

Thanks for reading.

Leave a Reply

Your email address will not be published. Required fields are marked *