Helpful notes about running native code in flutter - MethodChannel template

Flutter Mar 06, 2021

These are notes on running native code on flutter and a quick templates to get started, sources at the end.

Running native code with flutter is accomplished by sending messages using a defined platform channel. Using the MethodChannel the flutter app sends and receives messages from the host platform (iOS / Android).

Method calls are encoded into binary before being sent, and binary results received are decoded into Dart values. The data sended through the channel is converted like in the following table:

Messages and responses are passed asynchronously but the channels method needs to be invoked in the main thread. Because of this the main thread can jank if the method takes too long to compute, this might be mitigated by sending messages in different threads in the backend more on that in a moment.

Other characteristic of this is that the Method Channels can't be use in isolates because the main isolate is the only one running in the main thread.

How to create a methodchannel

Define the methodchannel in the state of the app. The name needs to match.

You can use this as a template for whatever you want to do.

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
...
class _MyHomePageState extends State<MyHomePage> {
  static const platform = const MethodChannel('com.dev.app/channel_name');

  
}

Platform specific code, creating the 'backend', in the MainActivity.kt or .java create the method channel.

import androidx.annotation.NonNull
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.dev.app/channel_name"
  private fun doSomethingFunction(): Int {
	return 10	
  }

    return batteryLevel
  }
  override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
      call, result ->
      // Note: this method is invoked on the main thread.
      if (call.method == "doSomething") {
        val funValue = doSomethingFunction()

        if (funValue != -1) {
          result.success(funValue)
        } else {
          result.error("UNAVAILABLE", "Error message.", null)
        }
      } else {
        result.notImplemented()
      }
    }
  }
}

Call method channel in the flutter app, function in the state:

String _valueReceivied= 'default value';
  Future<void> _getBatteryLevel() async {
    String tempValue;
    try {
      final int result = await platform.invokeMethod('doSomething');
      batteryLevel = 'New data $result';
    } on PlatformException catch (e) {
      batteryLevel = "Failed to get new data: '${e.message}'.";
    }

    setState(() {
      _valueReceivied = tempValue;
    });
  }

Then execute the function as you see fit.

You can also use invokeListMethod and invokeMapMethod that are an implementation of invokeMethod however they expect to receive a List or Map. More info.

This way of communicating and executing code with the backend of the platform is not typesafe as it requires the definition of the same datatype in the backend as in the flutter app.
Another way of doing this is using code generation with Pigeon. More info in the pub.dev page. I will try to make a post about this if i find it interesting.

You can make Packages to separate the platform-specific code for the UI and also publish the package to the Flutter ecosystem. Developing, Publishing.

Threading

The main thread and where the method channels are executed is also the UI thread so if some computation takes too long to finish the UI might suffer from this.
So one thing to keep in mind is...

When your app performs intensive work in response to user interaction, this single thread model can yield poor performance unless you implement your application properly. Specifically, if everything is happening in the UI thread, performing long operations such as network access or database queries will block the whole UI. When the thread is blocked, no events can be dispatched, including drawing events. From the user's perspective, the application appears to hang. Even worse, if the UI thread is blocked for more than a few seconds (about 5 seconds currently) the user is presented with the infamous "application not responding" (ANR) dialog. The user might then decide to quit your application and uninstall it if they are unhappy.

You can jump from a background thread to the UI thread by doing this

on Kotlin:

Handler(Looper.getMainLooper()).post {
  // Call the desired channel message here.
}

on Java:

new Handler(Looper.getMainLooper()).post(new Runnable() {
  @Override
  public void run() {
    // Call the desired channel message here.
  }
});

Not sure what the docs meant by this, but this might be a way of running the native code in a different thread, although I've tested something similar and I've experienced erros, so more testing needs to be done here, if you have any info let me know.

This is awesome to execute native code or libraries that don't have support in dart, in my example this is awesome for ML libraries and get models to run on the device.
One thing that I've experience is that when running ML models the UI threads struggles so I will make test on running these models on different threads, or making plugins as they run in different threads.
I did a quick introduction on how to use Pytorch mobile on flutter, I'm planning on doing a more comprehensive one. I going to talk about the use of threads for making things faster and without lag if i find that they work properly.

Sources:

Writing custom platform-specific code
Learn how to write custom platform-specific code in your app.
MethodChannel class - services library - Dart API
API docs for the MethodChannel class from the services library, for the Dart programming language.

Tags

Ramiro

Hey hi! My name is Ramiro, this is my blog about artificial intelligence, coding and things that are in my head. Follow me on twitter @ramgendeploy so we can chat!