Creating a Bridge in Flutter between Dart and Native Code

Written by dixiterk | Published 2019/01/15
Tech Story Tags: programming | android | flutter | bridge-in-flutter | mobile-app-development

TLDRvia the TL;DR App

How to develop a Bridge in Flutter between Dart and NativeĀ Code

Flutter allows us to call platform-specific APIs available in Java or Kotlin code on Android and in Objective C or Swift code onĀ iOS.

Flutter’s platform-specific API works with messageĀ passing.

From Flutter app we have to send messages to a host on iOS or Android parts of the app over a platform channel. The host listens on the platform channel and receives the message. It then uses any platform-specific APIs using the native programming language and sends back a response to the Flutter portion ofĀ app.

Architecture overview

Messages are passed between the Flutter Code(UI) and host (platform) using platform channels. Messages and responses are passed asynchronously and the user interface remains responsive.

On Dart side using MethodChannel(API) we send a message which is corresponding to a method call. On the Android side MethodChannel Android (API) and on the iOS side FlutterMessageChannel (API) is used for receiving method calls and sending back result. These classes allow us to develop a platform plugin with a very simpleĀ code.

If required, method calls can also be sent in the reverse direction, with the Android/IOS platform acting as client and method implemented inĀ Dart.

Data types supported by PlatformĀ Channel

The standard platform channel uses standard message codec that supports efficient binary serialization of simple JSON-like values of types boolean, number, String, byte buffer, list, and map. The serialization and deserialization of these values to and from messages happen automatically when we send and receiveĀ values.

Creating flutter app that calls iOS and AndroidĀ code

Now we will create a flutter app with a method call that will be implemented in Android (Java) and iOS (Objective C) respectively.

1. Create a new appĀ project

In a terminalĀ run:

flutter create flutter_to_native

By default, Flutter supports writing Android code in Java and iOS code in Objective C. If you want to use Kotlin and Swift, create a project with the following command.

flutter create -i swift -a kotlin flutter_to_native

2. Create a platformĀ Channel

The client and host sides of the channel are connected through the channel name passed in the channel constructor. All channel names used in a single app must be unique. In our example, we are creating the channel name flutter.native/helper

class _MyHomePageState extends State<MyHomePage> {
  static const platform = const MethodChannel('flutter.native/helper');

3. Invoke method on platformĀ Channel

Invoke a method on the method channel, specifying the concrete method to call via the String identifier. In the code below, it is helloFromNativeCode

String response = "";
  try {
    final String result = await  platform.invokeMethod('helloFromNativeCode');
    response = result;
  } on PlatformException catch (e) {
    response = "Failed to Invoke: '${e.message}'.";
  }

Use the returned response to update the user interface state inside setState.

setState(() {
  _responseFromNativeCode = response;
});

4. Create method implementation in Android usingĀ java

In Android Studio open Flutter app and select the android folder inside it. Open the file MainActivity.java

Now we have to create a MethodChannel with the same name that we have created in FlutterĀ App.

public class MainActivity extends FlutterActivity {
  private static final String CHANNEL = "flutter.native/helper";

We have to create a MethodCallHandler in onCreateĀ method

new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
        new MethodChannel.MethodCallHandler() {
          @Override
          public void onMethodCall(MethodCall call, MethodChannel.Result result) {
            if (call.method.equals("helloFromNativeCode")) {
              String greetings = helloFromNativeCode();
              result.success(greetings);
            }
          }});

5. Create method implementation in iOS using Objective C

Open the file AppDelegate.m of your Flutter app in Xcode. Now we have to create a FlutterMethodChannel with the same name that we have created in FlutterĀ App.

FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
        FlutterMethodChannel* nativeChannel = [FlutterMethodChannel
        methodChannelWithName:@"flutter.native/helper"
        binaryMessenger:controller];

Create method callĀ handler

[nativeChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
      if ([@"helloFromNativeCode"  isEqualToString:call.method]) {
      NSString *strNative = [weakSelf helloFromNativeCode];
      result(strNative);
    } else {
    result(FlutterMethodNotImplemented);
  }
}];

Complete Code

Android NativeĀ code

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 = "flutter.native/helper";
  @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("helloFromNativeCode")) {
                  String greetings = helloFromNativeCode();
                  result.success(greetings);
                }
              }});
  }

private String helloFromNativeCode() {
    return "Hello from Native Android Code";
  }
}

iOS nativeĀ code

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

@implementation AppDelegate

        - (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

        FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
        FlutterMethodChannel* nativeChannel = [FlutterMethodChannel
        methodChannelWithName:@"flutter.native/helper"
        binaryMessenger:controller];
        __weak  typeof(self) weakSelf = self;
        [nativeChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
        if ([@"helloFromNativeCode"  isEqualToString:call.method]) {
        NSString *strNative = [weakSelf helloFromNativeCode];
        result(strNative);
        } else {
        result(FlutterMethodNotImplemented);
        }
        }];

        [GeneratedPluginRegistrant  registerWithRegistry:self];
        return [super  application:application didFinishLaunchingWithOptions:launchOptions];
        }
        - (NSString *)helloFromNativeCode {
        return  @"Hello From Native IOS Code";
        }

@end

Flutter code

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: const Text('Native Code from Dart'),
      ),
      body: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  static const platform = const MethodChannel('flutter.native/helper');
  String _responseFromNativeCode = 'Waiting for Response...';

  Future<void> responseFromNativeCode() async {
    String response = "";
    try {
      final String result = await platform.invokeMethod('helloFromNativeCode');
      response = result;
    } on PlatformException catch (e) {
      response = "Failed to Invoke: '${e.message}'.";
    }

    setState(() {
      _responseFromNativeCode = response;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            RaisedButton(
              child: Text('Call Native Method'),
              onPressed: responseFromNativeCode,
            ),
            Text(_responseFromNativeCode),
          ],
        ),
      ),
    );
  }

}

Result

Run the code on Android and iOS devices. Click on the Button ā€œCall NativeĀ Methodā€

Final result

Thanks to Atul Sharma for the article. If you enjoyed this article, feel free to hit that clap button šŸ‘ to help others findĀ it.

This article is a part of the series of articles related to mobile technology. If you are looking for a mobile app development team, please contact us at [email protected].

Originally published at 47billion.com on January 15,Ā 2019.


Published by HackerNoon on 2019/01/15