Native Bridge between Flutter and Android
Establishing two-way communication in Flutter
Introduction
We will look at how to create a native bridge between Flutter and Android Native (Kotlin) in this article. I will make this brief and straight to the point.
It should be noted that this bridge will transfer integer data from Flutter to Android and string data from Android to Flutter, but you can use this to transfer other data as well.
Building Native Bridge
One-way communication
Build a native bridge in Flutter
Add
services.dart
importimport 'package:flutter/services.dart';
Add
NativeBridge
classclass NativeBridge { static const MethodChannel _channel = MethodChannel('com.example/native'); static void getNativeData() async { await _channel.invokeMethod('getNativeData').then((value) => print(value)); } }
First, we are going to create a
NativeBridge
class in which we will write our connection code. The bridge in the native code will be identified by the same name that we are using to create a staticMethodChannel
. ThegetNativeData
method in our Kotlin file will be called by the static methodgetNativeData()
, which will then print the log statement.Call
getNativeData()
methodNativeBridge.getNativeData();
Call the static method
getNativeData()
anywhere sensible in your Dart code.
Connect it's counterpart in
MainActivity.kt
Add necessary imports
import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel
Add this code snippet to
MainActivity
classprivate val CHANNEL = "com.example/native" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> if (call.method == "getNativeData") { val data = getNativeData() result.success(data) } else { result.notImplemented() } } } private fun getNativeData(): String { // Implement your native code logic here return "Native Data from Android" }
The
CHANNEL
name in this case needs to match theMethodChannel
name in our dart file in order to connect the two channels. For the purpose of determining which method to execute, we now write a simple if-check statement and then execute the required method.
Final code files for one-way communication between Flutter and Android
main.dart
// ignore_for_file: avoid_print import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'My App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const HomePage(), ); } } class NativeBridge { static const MethodChannel _channel = MethodChannel('com.example/native'); static void getNativeData() async { await _channel.invokeMethod('getNativeData').then((value) => print(value)); } } class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { NativeBridge.getNativeData(); return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: const Text('My App'), ), body: const Center( child: Text('data'), ), ); } }
MainActivity.kt
package com.example.bridge import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel class MainActivity: FlutterActivity() { private val CHANNEL = "com.example/native" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> if (call.method == "getNativeData") { val data = getNativeData() result.success(data) } else { result.notImplemented() } } } private fun getNativeData(): String { // Implement your native code logic here return "Native Data from Android" } }
Here, we have managed to successfully create a one-way connection between Android and Flutter. In this case, a log statement stating "Native Data from Android," which is coming from Android to Flutter, is printed when the app runs.
Two-way communication
Two-way communication can be achieved by adding a few data type parameters to
_channel.invokeMethod
and making minor adjustments togetNativeData
method. The finished Dart and Kotlin files are below.main.dart
// ignore_for_file: avoid_print import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'My App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const HomePage(), ); } } class NativeBridge { static const MethodChannel _channel = MethodChannel('com.example/native'); static void getNativeData(int num) async { await _channel.invokeMethod('getNativeData', <String, int>{ "num": num, }).then((value) => print(value)); } } class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { int i = 0; return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: const Text('My App'), ), body: Center( child: ElevatedButton( onPressed: () { NativeBridge.getNativeData(++i); }, child: const Text('Click me'), ), ), ); } }
MainActivity.kt
package com.example.bridge import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel class MainActivity: FlutterActivity() { private val CHANNEL = "com.example/native" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> if (call.method == "getNativeData") { if (!call.hasArgument("num")) { result.error("MissingArgumentError", "You must provide num parameter", null) } val num: Int = call.argument<Int>("num")!! val data = getNativeData(num) result.success(data) } else { result.notImplemented() } } } private fun getNativeData(num: Int): String { // Implement your native code logic here return "From Native: $num" } }
Here, we have successfully created a two-way communication channel between Flutter and Android. When we click the on-screen button, we send integer data to Android and receive a different string each time (in the form of a log statement).
The complete project source code for the one-way and two-way communication codes is maintained commit-wise; you can view it by clicking this link.
Thank you
Did you reach the bottom? Thank you for reading!
Feel free to connect with me. ๐