1. Flutter interact with native
Adding a Flutter Fragment to an Android app
- Add a FlutterFragment to an Activity with a new FlutterEngine
The first thing to do to use a FlutterFragment is to add it to a host Activity.
To add a FlutterFragment to a host Activity, instantiate and attach an instance of FlutterFragment in onCreate() within the Activity, or at another time that works for your app:
class MyActivity : FragmentActivity() {
companion object {
// Define a tag String to represent the FlutterFragment within this
// Activity's FragmentManager. This value can be whatever you'd like.
private const val TAG_FLUTTER_FRAGMENT = "flutter_fragment"
}
// Declare a local variable to reference the FlutterFragment so that you
// can forward calls to it later.
private var flutterFragment: FlutterFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Inflate a layout that has a container for your FlutterFragment. For
// this example, assume that a FrameLayout exists with an ID of
// R.id.fragment_container.
setContentView(R.layout.my_activity_layout)
val fragmentManager = supportFragmentManager
flutterFragment =
fragmentManager.findFragmentByTag(TAG_FLUTTER_FRAGMENT) as FlutterFragment?
if (flutterFragment == null) {
val newFlutterFragment: FlutterFragment = FlutterFragment
.withCachedEngine(FLUTTER_ENGINE_ID).build()
flutterFragment = newFlutterFragment
fragmentManager.beginTransaction()
.add(
R.id.container,
newFlutterFragment,
TAG_FLUTTER_FRAGMENT
).commit()
}
}
}
- Using a pre-warmed FlutterEngine
By providing a pre-warmed FlutterEngine, as previously shown, your app renders the first Flutter frame as quickly as possible.
// Somewhere in your app, before your FlutterFragment is needed, like in the
// Application class ...
// Instantiate a FlutterEngine.
FlutterEngine flutterEngine = new FlutterEngine(getApplicationContext());
// Configure an initial route.
flutterEngine.getNavigationChannel().setInitialRoute("flutter_module");
//Start executing Dart code in the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
);
// Cache the pre-warmed FlutterEngine to be used later by FlutterFragment.
FlutterEngineCache
.getInstance()
.put("default_flutter_engine", flutterEngine);
- Run Flutter with a specified initial route
void main() {
// Start off with whatever the initial route is supposed to be.
run('flutter_module');
}
Future<String> run(String name) async {
// The platform-specific component will call [setInitialRoute] on the Flutter
// view (or view controller for iOS) to set [ui.window.defaultRouteName].
// We then dispatch based on the route names to show different Flutter
// widgets.
// Since we don't really care about Flutter-side navigation in this app, we're
// not using a regular routes map.
runApp(_widgetForRoute(name));
return '';
}
Widget _widgetForRoute(String route) {
switch (route) {
case 'flutter_module':
return ModuleApp();
default:
return Center(
child: Text('Unknown route: $route', textDirection: TextDirection.ltr),
);
}
}
Introduction of Platform Channel
-
There are three types of platform channel, each Channel contains three member variables:
name: The unique identifier of a Channel, which can contain more than one, but whose name is unique.
messager: BinaryMessenger, the tool to send and receive of messages.
Codecs: Codecs of messages.
The specific implementation
MethodChannel
MethodChannel sends messages via invokeMethod, the first parameter must exist and be unique, matching the Android native. The second parameter is the transmitted data, similar to the Intent's ExtraData. The third parameter that can be hidden is the codec.
Flutter:
MethodChannel methodChannel =
const MethodChannel('com.bhp.productsuite/auth');
Future<Null> _jumpToPreviewPage() async {
try {
final String result =
await methodChannel.invokeMethod('jumpToPreviewPage', arguments);
print(result);
} on PlatformException catch (e) {
}
}
Future<Null> _getToken() async {
try {
final String _token = await methodChannel.invokeMethod('getToken');
} on PlatformException catch (e) {}
}
Android:
const val FLUTTER_AUTH_CHANNEL = "com.bhp.productsuite/auth"
const val METHOD_GET_TOKEN = "getToken"
const val METHOD_JUMP_TO_PREVIEW_PAGE = "jumpToPreviewPage"
override fun onCreate(savedInstanceState: Bundle?) {
val binaryMessenger = FlutterEngineCache.getInstance()
.get(FLUTTER_ENGINE_ID)?.dartExecutor?.binaryMessenger
MethodChannel(binaryMessenger, FLUTTER_AUTH_CHANNEL)
.setMethodCallHandler { call, result ->
// Note: this method is invoked on the main thread.
when (call.method) {
METHOD_GET_TOKEN -> {
Observable.fromCallable {
App.getInstance().component().authMgr().syncRefreshToken()
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result.success(it) },
{ result.error("-1", "refresh token fail", null) })
}
METHOD_JUMP_TO_PREVIEW_PAGE -> {
val arguments: String = call.arguments as String
val coalShiftHandover = Gson().fromJson<CoalShiftHandover>(
arguments,
CoalShiftHandover::class.java
)
println(coalShiftHandover)
val params = Bundle()
params.putParcelable(
Constants.KEY_OF_COAL_SHIFT_HANDOVER,
Parcels.wrap(coalShiftHandover)
)
FragmentContainerActivity.ofFragment(
this,
PreviewHandoverFragment::class.java,
params,
""
)
.launchForResult(
this,
WriteCoalShiftHandoverFragment.REQUEST_CODE_PREVIEW_SUBMIT
)
result.success("success")
}
else -> {
result.notImplemented()
}
}
}
}
Note:
- Using DS codes to show the case.
- MethodChannel mainly sends an interactive request to the Android native via Flutter.
BasicMessageChannel
BasicMessageChannel mainly transmits string and semi-structured data; There are various types of encoding and decoding, and it is recommended that Android and Flutter are consistent when used.
The codes of Flutter:
static const messageChannel = const BasicMessageChannel('samples.flutter.io/message', StandardMessageCodec());
static const messageChannel2 = const BasicMessageChannel('samples.flutter.io/message2', StandardMessageCodec());
Future<String> sendMessage() async {
String reply = await messageChannel.send('the data was sent to Native');
print('reply: $reply');
return reply;
}
void receiveMessage() {
messageChannel2.setMessageHandler((message) async {
print('message: $message');
return 'the data was returned to Native';
});
}
@override
void initState() {
// TODO: implement initState
super.initState();
receiveMessage();
sendMessage();
}
The codes of Android:
val binaryMessenger = FlutterEngineCache.getInstance()
.get(FLUTTER_ENGINE_ID)?.dartExecutor?.binaryMessenger
BasicMessageChannel<Object> messageChannel = new BasicMessageChannel<Object>(binaryMessenger, "samples.flutter.io/message", StandardMessageCodec.INSTANCE);
// Receiving message listening
messageChannel.setMessageHandler(new BasicMessageChannel.MessageHandler<Object>() {
@Override
public void onMessage(Object o, BasicMessageChannel.Reply<Object> reply) {
System.out.println("onMessage: " + o);
reply.reply("the data was returned to Flutter");
}
});
// Trigger event execution
BasicMessageChannel<Object> messageChannel2 = new BasicMessageChannel<Object>(binaryMessenger, "samples.flutter.io/message2", StandardMessageCodec.INSTANCE);
// Sending message
messageChannel2.send("the data was sent to Flutter", new BasicMessageChannel.Reply<Object>() {
@Override
public void reply(Object o) {
System.out.println("onReply: " + o);
}
});
EventChannel
A named channel for communicating with platform plugins using event streams.
Stream setup requests are encoded into binary before being sent, and binary events and errors received are decoded into Dart values. The MethodCodec used must be compatible with the one used by the platform plugin. This can be achieved by creating an EventChannel counterpart of this channel on the platform side. The Dart type of events sent and received is dynamic, but only values supported by the specified MethodCodec can be used.
Android:
val binaryMessenger = FlutterEngineCache.getInstance()
.get(FLUTTER_ENGINE_ID)?.dartExecutor?.binaryMessenger
new EventChannel(binaryMessenger, CHANNEL).setStreamHandler(new EventChannel.StreamHandler() {
@Override
public void onListen(Object arguments, final EventChannel.EventSink events) {
events.success("The data which send to Flutter");
}
@Override
public void onCancel(Object arguments) {
}
});
Flutter:
static const EventChannel _eventChannel =
const EventChannel('samples.flutter.io/test');
String _result = '';
StreamSubscription _streamSubscription;
@override
void initState() {
super.initState();
_getEventResult();
}
@override
void dispose() {
super.dispose();
if (_streamSubscription != null) {
_streamSubscription.cancel();
}
}
_getEventResult() async {
try {
_streamSubscription =
eventChannel.receiveBroadcastStream().listen((data) {
setState(() {
_result = data;
});
});
} on PlatformException catch (e) {
setState(() {
_result = "event get data err: '${e.message}'.";
});
}
}