Source: Android Team – Natural Yu

This paper mainly solves three problems:

  1. Integrate Flutter into the Android project to open the default page of Flutter
  2. You can jump to the specified page of Flutter
  3. You can embed the specified components of a Flutter into the native page and pass parameters

1. Integrate Flutter into Android

Here, we to Flutter the Module to create a Flutter engineering (Flutter), and then run up, can in. Android/Flutter/build/outouts/aar folder below to get the aar

The reason why the Flutter Module is developed here, not the Flutter Application, is to get this AAR. This folder is automatically generated by the Flutter Module. The Android folder contains the Flutter folder, but not the Flutter Application folder. In this way, we can borrow the Gradle script that Flutter already has to generate aArs. Otherwise, we have to write our own Gradle packaging script. It is easy to step into a pit and not get up.

Then open another window, create a new Android project (Flutter_container) and copy the AAR

One problem that needs to be noted here is that because of Flutter itself, the icudtl.dat file copied from AAR is missing. We need to manually copy the icudtl.dat file to assets/ Flutter_shared directory.

How to obtain the icudtl.dat file by unpacking the default APK generated by the Flutter project

We then need to set up the Activity to receive the Flutter in the host Android project. There are two core ideas for the Flutter project’s android/app directory:

  1. Application: initializes the Flutter
public class App extends Application {

    @Override
    public void onCreate() { super.onCreate(); FlutterMain.startInitialization(this); }}Copy the code
  1. Activity: Inherits FlutterActivity
/** * A white screen appears when you jump to the Flutter screen in native mode. Public class MainFlutterActivity extends FlutterActivity {@override protected void onCreate(Bundle) savedInstanceState) { super.onCreate(savedInstanceState); GeneratedPluginRegistrant.registerWith(this); }}Copy the code

After that, we can jump to the MainFlutterActivity and access the default page of the Flutter project in the Android project.

2. Go to the specified page

However, we know that when we jump from the Android project to the Flutter, we must selectively jump to the specified page. We cannot simply jump to the default page, so we need to use the static route of the Flutter.

Dart of the Flutter project is modified to specify two routes for a page: homePage and channelPage

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo', theme: ThemeData(primarySwatch: color.blue,), home: TestPage(), // <String, WidgetBuilder> {'homePage': (BuildContext context) => new HomePage(),
        'channelPage': (BuildContext context) => new ChannelPage(), }, ); }}Copy the code

Then, in the host Android project, add the Activity container for the specified page and get the View of the specified page with Flutter. CreateView

Note that HomeFlutterActivity only needs to inherit AppCompatActivity, not FlutterActivity.

public class HomeFlutterActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FlutterView homePage = Flutter.createView(
                this,
                getLifecycle(),
                "homePage"
        );
        setContentView(homePage); }}Copy the code

After that, we can jump to the HomeFlutterActivity and enter the specified page of the Flutter project in the Android project.

3. Embed View and pass parameters

It was possible to jump to the specified page, but apparently there was a big problem: you couldn’t pass parameters.

This is a major disadvantage of the static route to Flutter. Although dynamic routes can pass parameters and receive return values, dynamic routes cannot be used for native calls.

 Navigator.of(context)
                .push<String>(new MaterialPageRoute(builder: (context) {
              return new NextPage(params);
            })).then((String value) {
              setState(() {
                params = value;
              });
            });
Copy the code

There is a route library for Flutter: Fluro, which can implement static route parameter transfer, for example:

The ginseng

var bodyJson = '{"user":1281,"pass":3041}';
router.navigateTo(context, '/home/$bodyJson');
Copy the code

receive

Router router = new Router();

void main() {
  router.define('/home/:data', handler: new Handler(
      handlerFunc: (BuildContext context, Map<String, dynamic> params) {
        return new FluroHomePage(params['data'] [0]); })); runApp(MyApp()); }Copy the code

However, this method works within a Flutter, but cannot be called to a native Flutter. Fluro is not used in a native Flutter when passing a Flutter. CreateView, which is the default route.

We investigated a number of options, but finally, we ran out of options and resorted to the dumbest method of all: passing parameters through a MethodChannel.

Flutterview.post (new Runnable())) is a MethodChannel call that will not be passed to Flutter.

Native transfer to Flutter

Native calls

MethodChannel channel = new MethodChannel(flutterView, CHANNEL);
channel.invokeMethod("invokeFlutterMethod"."hello,flutter", new MethodChannel.Result() {
    @Override
    public void success(@Nullable Object o) {
        Log.i("flutter"."1. Primitive invokefluttermethod-success :"+o.toString());
    }
    @Override
    public void error(String s, @Nullable String s1, @Nullable Object o) {
        Log.i("flutter"."1. Primitive invokefluttermethod-error");
    }
    @Override
    public void notImplemented() {
        Log.i("flutter"."1. Invokefluttermethod-notimplemented"); }});Copy the code

Flutter to perform

platform.setMethodCallHandler((handler) {
      Future<String> future=Future((){
        switch (handler.method) {
          case "invokeFlutterMethod":
            String args = handler.arguments;
            print("2. Flutter perform invokeFlutterMethod:${args}");
            return "this is flutter result"; }});return future;
    });
Copy the code

Flutter passes references to the native

Flutter call

print("3. InvokeNativeMethod Flutter call");
int result =
    await platform.invokeMethod("invokeNativeMethod"."hello,native");
print("5. Original implementation results received:${result}");
Copy the code

Native to perform

channel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
    @Override
    public void onMethodCall(MethodCall call, MethodChannel.Result result) {
        switch (call.method) {
            case "invokeNativeMethod":
                String args = (String) call.arguments;
                Log.i("flutter"."4. Native execution invokeNativeMethod:"+args);
                result.success(200);
                break;
            default:
        }
    }
});
Copy the code

Finally, post the complete code of the reference page, which basically runs:

  1. InvokeFlutterMethod is called natively
  2. Flutter execution invokeFlutterMethod
  3. Flutter call invokeNativeMethod
  4. Native execution invokeNativeMethod

Android:

public class ChannelFlutterActivity extends AppCompatActivity {

    private static final String CHANNEL = "com.ezbuy.flutter";

    FlutterView flutterView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_channel);
        FrameLayout frFlutter = findViewById(R.id.fr_flutter);
        flutterView = getFlutterView("channelPage");
        frFlutter.addView(flutterView);
        flutterView.post(new Runnable() {
            @Override
            public void run() { initMethodChannel(flutterView); }}); } public FlutterView initMethodChannel(FlutterView flutterView) { MethodChannel channel = new MethodChannel(flutterView,  CHANNEL); //1. Call the Flutter method channel.invokemethod ("invokeFlutterMethod"."hello,flutter", new MethodChannel.Result() {
            @Override
            public void success(@Nullable Object o) {
                Log.i("flutter"."1. Primitive invokefluttermethod-success :"+o.toString());
            }

            @Override
            public void error(String s, @Nullable String s1, @Nullable Object o) {
                Log.i("flutter"."1. Primitive invokefluttermethod-error");
            }

            @Override
            public void notImplemented() {
                Log.i("flutter"."1. Invokefluttermethod-notimplemented"); }}); Log.i("flutter"."1. Invoke invokeFlutterMethod natively"); / / 4. Flutter call native methods to monitor the channel. SetMethodCallHandler (new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall call, MethodChannel.Result result) {
                switch (call.method) {
                    case "invokeNativeMethod":
                        String args = (String) call.arguments;
                        Log.i("flutter"."4. Native execution invokeNativeMethod:"+args);
                        result.success(200);
                        break;
                    default:
                }
            }
        });
        return flutterView;
    }

    public FlutterView getFlutterView(String initialRoute) {
        returnFlutter.createView( this, getLifecycle(), initialRoute ); }}Copy the code

Flutter

class ChannelPage extends StatefulWidget {

  ChannelPage();

  @override
  _ChannelPageState createState() => _ChannelPageState();
}

class _ChannelPageState extends State<ChannelPage> {
  static const platform = const MethodChannel('com.ezbuy.flutter');

  String data;

  @override
  void initState() {
    super.initState();
    data ="The default data"; initChannel(); } @override Widget build(BuildContext context) {// Scaffolding must be wrappedreturn Scaffold(body: new Center(child: new Text(data)));
  }

  void initChannel() {
    platform.setMethodCallHandler((handler) {
      Future<String> future=Future((){
        switch (handler.method) {
          case "invokeFlutterMethod":
            String args = handler.arguments;
            print("2. Flutter perform invokeFlutterMethod:${args}");
            setState(() {
              data = "2. Flutter perform invokeFlutterMethod:${args}";
            });
            invokeNativeMethod();
            return "this is flutter result"; }});return future;
    });
  }

  void invokeNativeMethod() async {
    print("3. InvokeNativeMethod Flutter call");
    int result =
        await platform.invokeMethod("invokeNativeMethod"."hello,native");
    print("5. Original implementation results received:${result}"); }}Copy the code

This section describes how to nest a Flutter into an Android Activity at the View level. The View created by Flutter. CreateView is not very different from a normal View. Just addView, nothing special. For example, the ChannelFlutterActivity layout file I used looks like this:

The final result is:

Other pit

1. When the Flutter project relies on a plugin, the host Android project will report an error that the plugin’s native code cannot be found

My Flutter project relies on the shared_Preferences plugin, causing an error:

The reason: when the Flutter project was exported as an AAR, it did not include the native code in the plugin.

There are two solutions. Instead of the default way of generating AAR, fataar-gradle-plugin is used to make the generated FLUTTER. Aar contains the aar of the nested plugin project directly. It is need to modify the Flutter engineering. Android/Flutter/build. Gradle file. I tried, the result reported the error of cyclic dependence, so I gave up, if this scheme works, please tell me the specific steps.

My solution: HERE I took a simple and straightforward approach, went to the aar of the plugin and copied it into the host Android project. The AAR for this plugin is here:

Copy here:

The downside of this solution, however, is that you need to copy each plug-in later, and the maintenance cost is a bit high. Unlike Fataar, there is only flutter. Aar. Especially in the later stage, aar will definitely be made to be remotely dependent, rather than directly copied, which will be more expensive to maintain.

conclusion

As we can see above, Flutter is actually quite convenient to integrate into the Android project (except for FlutterView input). As for how to integrate Flutter into the ios project, I have not practiced it yet, and I still need to explore with my ios colleagues. If you have filled in any holes and summarized your experience during the integration of Flutter into the ios project, please feel free to share with us.