Android

MethodChannel for Communication between Dart and Native Code in Flutter

Author : Lee
Published Time : 2025-11-06

Preface

As far as I know, the proportion of Android development engineers who truly master Flutter is not very high. If you don't think so, it may be because your circle is relatively high-end. Do you know how long it takes for Android native developers to integrate all their knowledge systems? Of course, Flutter is excluded here. For those with top qualifications, it takes at least 3 years, while for most people, their IQ is above average, about 5 years. If it's an ordinary code farmer, it may take 8-10 years or even longer. Of course, I am only estimating based on the previous non AI era, and there may be a slight decrease in the future AI era. 10 years, which means an ordinary programmer needs to work from graduation to the crisis of almost 35 years old to truly free up energy to carefully study Flutter, a cross platform technology. Of course, you said my initial goal was Flutter, that's no problem, it's not within the scope of discussion.

Many Android developers actually do not have the opportunity to practice Flutter projects at all, because it is difficult for small companies to hire such big shots who have been around for more than ten years. Of course, no one will take you to play Android Flutter hybrid development from a global perspective, unless you are lucky enough to have someone take it.

Introduce the main topic

If you have no experience with Android Flutter hybrid development, you need to read my article juejin.cn/post/725412 first. To truly implement hybrid development, it not only requires a solid foundation in native development, but also the courage to diligently study new technologies. Mixed development cannot avoid a topic, how to communicate or call each other's interoperability? This introduces our protagonist today - Method Channel. It is directly translated as a method channel, which can be understood as a bridge for calling methods on the other end. Let's take a look at how to write it.

Communication process

Native Dart tuning

val intent = FlutterActivity
    .withNewEngine()
    .initialRoute(ROUTE_HOME)
    .build(this)
intent.setClass(this, MyFlutterActivity::class.java)
startActivity(intent)

Start the native shell Activity of Flutter interface first, and register the manifest file.

package com.doracrypto.crypto.ui.activity

import android.content.Intent
import com.doracrypto.crypto.AppConfig
import dora.util.SPUtils
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MyFlutterActivity : FlutterActivity() {

    companion object {
        private const val CHANNEL = "com.doracrypto.crypto/native"
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
            .setMethodCallHandler { call, result ->
                when (call.method) {
                    "isVipUser" -> {
                        result.success(SPUtils.readBoolean(context, AppConfig.IS_VIP))
                    }
                    "startBuyVipActivity" -> {
                        startActivity(Intent(this@MiniAppActivity, BuyVipActivity::class.java))
                        result.success(null)
                    }
                    else -> result.notImplemented()
                }
            }
    }
}

Although this thing is not difficult, it is indeed a necessary path. On the native side, we need to provide various methods for Dart to call.

import 'package:flutter/services.dart';

class MethodChannelPlugin {

  static const MethodChannel methodChannel = MethodChannel('com.doracrypto.crypto/native');

  // /// Call native browser components
  // static Future<void> gotoWebView(String url) async {
  //   try {
  //     await methodChannel.invokeMethod('gotoWebView',{'url': url});
  //   } on PlatformException {
  //     print('Failed go to gotoWebView');
  //   }
  // }

  /// Check if you are a VIP user
  static Future<bool> isVipUser() async {
    try {
      final result = await methodChannel.invokeMethod('isVipUser');
      return result == true; // Guaranteed return bool
    } on PlatformException catch (e) {
      print('Failed isVipUser: $e');
      return false; // Fallback return when an error occurs false
    } catch (e) {
      print('Unexpected error in isVipUser: $e');
      return false;
    }
  }

  ///Jump to the VIP purchase interface
  static Future<void> startBuyVipActivity() async {
    try {
      await methodChannel.invokeMethod('startBuyVipActivity');
    } on PlatformException catch (e) {
      print('Failed startBuyVipActivity: $e');
    } catch (e) {
      print('Unexpected error in startBuyVipActivity: $e');
    }
  }
}

Call it like this.

bool isVip = await MethodChannelPlugin.isVipUser();
if (!isVip) {
  MethodChannelPlugin.startBuyVipActivity();
  return;
}

Dart calls native

Mainly Dart calls native, and there are not many scenarios where Dart is called native. Not many, but that doesn't mean there isn't any. Let me briefly mention it here.

import 'package:flutter/services.dart';

class NativeBridge {

  static const MethodChannel _channel = MethodChannel("com.doracrypto.crypto/dart");

  static void init() {
    //Receive native calls
    _channel.setMethodCallHandler((call) async {
      if (call.method == "fromNative") {
        String msg = call.arguments as String;
        print("Received native call:$msg");
        return "Dart received it: $msg";
      }
      return null;
    });
  }
}

Initialize in main.dart.

void main() {
  //Waiting for initialization to complete
  WidgetsFlutterBinding.ensureInitialized();
  NativeBridge.init();
  runApp(MyApp());
}

Native side calls Dart.

class MainActivity: FlutterActivity() {

    private lateinit var channel: MethodChannel

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        channel = MethodChannel(
            flutterEngine.dartExecutor.binaryMessenger,
            "com.doracrypto.crypto/dart"
        )
        Handler(Looper.getMainLooper()).postDelayed({
            channel.invokeMethod("fromNative", "Hello from Android")
        }, 3000)
    }
}

The channel name only needs to be unique and does not necessarily have to be in this format. Finally, compile aar with three lines of command and get it done.

flutter clean
flutter pub get
flutter build aar

By relying on the compiled aar package into the native project, we have achieved great success, and finally proceed with the normal process of loading the native package.

In conclusion

Flutter is a truly good thing that is quite friendly to entrepreneurs. After reaching advanced development, it greatly saves development and maintenance time because only one code is maintained. When you implement the core algorithm in Dart, the native shell can actually be distributed to others for development, which further saves development time, provided that your project is stable and profitable first.