Foto de Joseph Barrientos en Unsplash
Introduction
Flutter includes a built-in way to communicate with the underlying platform: the so called method channels.
But… wait a second… isn’t Flutter a cross-platform framework? Why should we need to go down into the “native” world then?
Well, sometimes we want to take advantage of a given HW feature, like the camera or the geolocation, and we can’t find a plugin or 3rd party library that suits our needs. Then we will have to do all the heavy-lifting ourselves and access the feature natively by using method channels.
General overview
Method channels are just another type of objects provided by the Flutter API. Each channel is identified by its name, so every “bridge” of communication between Flutter and the native platform must have a unique identifier.
That’s why we usually set up names by combining the package identifier and some suffix, for example:
static const String PACKAGE =
"com.bgomez.flutter_weather";
static const String SUFFIX = "/openweather";
Data exchange
Method channels can be seen as a direct stream of data with the native operating system, allowing us to invoke methods and send or retrieve information.
All data exchanged through method channels is sent as messages: a message is just a bundle with some key-value pairs serialized and deserialized automatically by the platform.
All messages exchanged are sent asynchronously.
System architecture

When using method channels, the Flutter framework and its underlying platform follow a client/server architecture.
The most frequent scenario is when:
- on the Flutter side, we request resources (so Flutter acts as client)
- on the native side, we perform the operations required and serve the result (so native side acts as server)
However, we can also configure the channel to exchange the roles played by each side.
Project set-up
Apart from the dart files in our application, when using method channels, we will have to add Android and/or ios native code as well, each one on its corresponding folder.
Workflow
- On the native side, implement the required code
- On the native side, config the native “entry point” class
- On the Flutter side, create a method channel
- On the Flutter side, use the previous object to invoke the native operation
The steps required to get a method channel up & running is the same for both Android and iOS, but implementation details change on the native side.
Following sections take the Android native implementation as example, implementing a method channel to retrieve data about the weather forecast.
1. Native side: implementation
To begin with, we will define a class named “OpenWeatherService“, responsible of retrieving the weather forecast from a remote server. The class implementation uses Kotlin coroutines:
object OpenWeatherService {
val URL = "api.openweathermap.org"
suspend fun getForecast(
appId: String,
lat: Double,
lon: Double) : String {
...
//XXX: hit some API and get weather data...
}
2. Native side: config the entry point
After that, we will “hook” the previous service class, so its methods can be accessed from the native Android app.
In order to do that, we must:
- register the name of the operations we want to access
- link each one of these names to the corresponding operation implementation inside the class
In Flutter, the entry point for the underlying Android project is the “MainActivity.configureFlutterEngine()” method. So both registration and linking must be performed inside that method:
private val CHANNEL = "com.bgomez.flutter_weather"
private val SUFFIX = "/openweather"
private val GET_CURRENT = "getCurrentWeather"
private val GET_FORECAST = "getForecast"
override fun configureFlutterEngine(
@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
binaryMessenger,
"$CHANNEL$SUFFIX")
.setMethodCallHandler { call, result ->
// CURRENT WEATHER
if (call.method == GET_CURRENT) {
val res =
OpenWeatherService
.getCurrentWeather(appId, lat, lon)
result.success(res)
// 24H FORECAST
} else if (call.method == GET_FORECAST) {
val res=
OpenWeatherService
.getForecast(appId, lat, lon)
result.success(res)
}
}
}
Method channel invocation must be performed on the Android UI thread, so we must wrap the previous snippet with some code for thread handling:
override fun configureFlutterEngine(
@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// Force invocation on UI thread
android.os.Handler(
android.os.Looper.getMainLooper()).post {
//XXX: prev method channel code goes here
})
}
3. Flutter side: create a method channel
As mentioned before, the Flutter framework includes the “MethodChannel” data type. Instances of this class represent a bridge of communication between Flutter and native:
A channel is created directly by invoking the class constructor and passing its name as parameter. We can wrap the operation in a factory method:
static const String PACKAGE ="...";
static const String SUFFIX = "/weather";
MethodChannel create() {
return MethodChannel("$PACKAGE$SUFFIX");
}
After that, we use the previous function to create our instance:
final channel = create();
4. Flutter side: invocation of native method using the MethodChannel
Last but not least, we must invoke operations on the underlying platform using the “MethodChannel.invokeMethod()“, which takes as parameters:
- the native method name we want to execute
- the message we want to pass to the native side. This is just a JSON object containing key-value pairs with the values required to perform the operation
final json =
await channel
.invokeMethod(
"getForecast",
{
"lat": settings.city.geo.lat,
"lon": settings.city.geo.lon,
"appId": settings.appId
});
And that would be all! Our method channel is now ready to communicate with the underlying platform.
Sample code
As usual, check this repository to access the source code. Write you next time!
https://github.com/begomez/FlutterForecast
References:
https://flutter.dev/docs/development/platform-integration/platform-channels