Rebuilding a Native Mobile App in Flutter From the Inside Out: Part IV - Method Channels
- Part I: Why?
- Part II: Add-to-App
- Part III: Entry Points
- Part IV: Method Channels
In our previous installment, we saw how to specify which Flutter page we want to navigate to from the native side of our app. Next, we are going to give the user a way to dismiss the presented FlutterViewController
or FlutterActivity
/FlutterFragment
. In order to do this, we need to let the Flutter and native sides of our app communicate with one another. Facilitating this communication between Flutter and native is the role of method channels.
Before moving forward, be sure to follow along with the example project. If you are continuing from the previous article, no further action is required. If you haven't yet worked with the example project, be sure to clone it and check out the part-iv
branch. After cloning, cd into FlutterIntegration_Flutter
and run flutter pub get
. Then cd into FlutterIntegration_iOS
and run pod install
. You should now be ready to work with the example project.
With method channels, we can set up a channel between the Flutter and native sides of our project. We can then serialize data into a dictionary and send that data to the other side. Let's take a look at how this is done.
Configuring Flutter to invoke method channel calls
Configuring the Flutter project to invoke a method channel call is easy. We simply create a method channel and call invokeMethod
on said method channel. As mentioned, you can serialize and pass data across this channel, but we are simply going to invoke the method and listen for this invocation on the native sides.
Let's head over to main.dart
and wrap our Text
widget in HelloWorldPage
in a Column
and add a FlatButton
under the Text
widget:
class HelloWorldPage extends StatelessWidget {
final bool isInGerman;
HelloWorldPage({this.isInGerman = false});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(isInGerman ? 'Hallo Welt!' : 'Hello World!'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
isInGerman
? 'Dies ist eine vollständig Flutter Seite, die in Ihre native App integriert ist!'
: 'This is a fully Flutter page, integrated into your native app!',
textAlign: TextAlign.center,
),
),
FlatButton(
color: Colors.blue,
onPressed: () {},
child: Text(
isInGerman ? 'Geh Zurück' : 'Go Back',
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(color: Colors.white),
),
),
],
),
),
);
}
}
Now, we simply add MethodChannel('HelloWorldMethodChannel').invokeMethod('dismiss');
to our FlatButton
's onPressed
handler. In order to use MethodChannel
, we also have to put import
'package:flutter/services.dart'
;
at the top of our main.dart
file, just under import
'package:flutter/material.dart'
;
.
FlatButton(
color: Colors.blue,
onPressed: () {
MethodChannel('HelloWorldMethodChannel')
.invokeMethod('dismiss');
},
child: Text(
isInGerman ? 'Geh Zurück' : 'Go Back',
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(color: Colors.white),
),
),
Listening for method channel invocation on iOS
To listen for calls on the "HelloWorld" Method Channel, we only have to add a small chunk of code to our iOS project. This code creates a method channel with the "HelloWorldMethodChannel"
name and with our engine's binaryMessenger
. It then sets a handler that listens for method channel calls, checks the name of the method, and runs code depending on what method was called. In our case, we are wanting to dismiss the HelloWorldFlutterViewController
whenever the "dismiss"
method is called:
class HelloWorldFlutterViewController: FlutterViewController {
init(engine: FlutterEngine) {
super.init(engine: engine, nibName: nil, bundle: nil)
FlutterMethodChannel(name: "HelloWorldMethodChannel", binaryMessenger: engine.binaryMessenger).setMethodCallHandler { (call, result) in
if call.method == "dismiss" {
self.dismiss(animated: true, completion: nil)
}
}
}
...
}
Listening for method channel invocation on Android
Much like listening to method channel invocation on iOS, we only need a small chunk of code to listen within our Android project. This code is doing basically the same thing as well, i.e. creating a method channel with the "HelloWorldMethodChannel"
name and with our engine's binaryMessenger
and creating a handler that listens for and reacts to method channel calls. To do this, we have to override the configureFlutterEngine(FlutterEngine)
method on our HelloWorldFlutterActivity
:
package com.wwt.flutterintegrationexample
import android.content.Context
import android.content.Intent
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.plugin.common.MethodChannel
class HelloWorldFlutterActivity : FlutterActivity() {
...
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "HelloWorldMethodChannel").setMethodCallHandler { call, _ ->
if (call.method == "dismiss") {
this.finish()
}
}
}
...
}
Wrapping up
Whew, that was a lot! But hopefully, this sample project and tutorial will help you and your team more easily begin to migrate your native app to a Flutter app. This overview has covered the basics of integrating Flutter into an existing native app, but depending on your needs, there will likely be some unexpected "gotchas" that you'll run into. We plan on covering some of what we ran into and how we solved these problems in a future installment.
To see what we've done with Flutter integration, download ATC Connect by clicking one of the links below and signing in by requesting a magic link using your work or personal email address.
I hope you found the information in this article valuable. If you are in the process of migrating a native app to Flutter or have questions about the process, feel free to leave a comment below and start a conversation with us!