Flutter is very popular recently. I believe that handsome people have already had a preliminary understanding of Flutter. However, since Flutter is currently used as the default framework to take over the development of the whole App, it is not flexible enough. On the one hand, using pure Flutter development requires careful evaluation before the project starts to see if there are any functions that are difficult to implement. On the other hand, existing apps that want to use Flutter are difficult to convert completely. It is easy to think that adding Flutter as part of the screen/function implementation on the basis of the existing App is an ideal solution, which is also more conducive to technical experimentation and risk control. In fact, currently There are two official Flutter solutions for adding a Flutter Module to existing apps. There are also some third-party solutions. Recently, I made some attempts and shared some results. Note that the introduction of the Flutter Module to existing apps is experimental. APIs and toolchain are not stable and need to be switched to the Master branch.

Android

Create a Flutter Module

Suppose under some/path/MyApp is the Android project directory

cd some/path
flutter create -t module --org com.example flutter_to_app
Copy the code

A Flutter Module is generated in some/path/flutter_to_app

Host App Settings

Gradle needs to be set in app/build.gradle

android {
  //...
  compileOptions {
    sourceCompatibility 1.8 targetCompatibility 1.8}}Copy the code

Make App rely on Flutter Module

There are two options, relying directly on source code and AAR artifacts.

1. Rely on the generated AAR

cd ~/Documents/Android/flutter_to_app
flutter build aar
Copy the code
// MyApp/app/build.gradle android { // ... } Repositories {maven {// Relative or absolute path urls can be used'some/path/flutter_to_app/build/host/outputs/repo'
  }
}

dependencies {
  // ...
  releaseCompile ('com. Example. Flutter_to_app: flutter_release: 1.0 @ aar') {
    transitive = true}}Copy the code

Debug dependencies can be generated with flutter build aar –debug

// MyApp/app/build.gradle

dependencies {
  // ...
  debugCompile ('com. Example. My_flutter: flutter_debug: 1.0 @ aar') {
    transitive = true}}Copy the code

2. Rely directly on the source code

Relying on aar is a bit cumbersome and requires compiling in Module, so you can also rely directly on source compilation

Join in the host App Settings. gradle

// MyApp/settings.gradle
include ':app'.setBinding(new Binding([gradle: this]))                                 
evaluate(new File(                                                     
 settingsDir.parentFile,                                                
  'flutter_to_app/.android/include_flutter.groovy'                          
))  
Copy the code

The File() path above is the path of the Flutter Module relative to the host app. Binding and include_flutter. Groovy scripts import the flutter module itself and its associated plugin.

Finally, dependency modules:

// MyApp/app/build.gradle
dependencies {
  implementation project(':flutter')}Copy the code

Use the Flutter Module in Android projects

At present, there are two ways to achieve, respectively in

  1. io.flutter.facade.*
  2. io.flutter.embedding.android.*

Under the two packages, the first one has been deprecated and the second one is still in the technical preview phase, so the APIS of both versions are not stable yet, but you can take a look at the two approaches.

The old way (IO. Flutter. Facade)

By using Flutter. CreateView:

fab.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View view) {
    View flutterView = Flutter.createView(
      MainActivity.this,
      getLifecycle(),
      "route1"); FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(600, 800); layout.leftMargin = 100; layout.topMargin = 200; addContentView(flutterView, layout); }});Copy the code

By using Flutter. CreateFragment:

// MyApp/app/src/main/java/some/package/SomeActivity.java
fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { FragmentTransaction tx = getSupportFragmentManager().beginTransaction();  tx.replace(R.id.someContainer, Flutter.createFragment("route1")); tx.commit(); }});Copy the code

Creating views and fragments is easy, but when tested, starting a View (FlutterFragment actually generates a View from createView) takes time and the experience is not seamless.

New way (IO. Flutter. Embedding. Android. *)

Through FlutterView (inherited from FrameLayout)

FlutterView = new FlutterView(this); FrameLayout frameLayout = findViewById(R.id.framelayout); frameLayout.addView(flutterView); // Create a FlutterView that will not render. / / after calling the following code will render flutterView attachToFlutterEngine (flutterEngine);Copy the code

Open through FlutterFragment

Through XML
	<fragment
    android:id="@+id/flutterfragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:name="io.flutter.embedding.android.FlutterFragment"
    />
Copy the code
Direct instantiation
flutterFragment = new FlutterFragment.createDefault();
Copy the code

Open with FlutterActivity

Register in androidmanifest.xml
    <activity
        android:name="io.flutter.embedding.android.FlutterActivity"
        android:theme="@style/LaunchTheme"
        android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
        android:hardwareAccelerated="true"
        android:windowSoftInputMode="adjustResize"
        android:exported="true"
        />
Copy the code
Default boot mode
// The default route is'/'
    Intent defaultFlutter = new FlutterActivity.createDefaultIntent(currentActivity);
    startActivity(defaultFlutter);
Copy the code
Start to the specified route
    Intent customFlutter = new FlutterActivity.IntentBuilder()
      .initialRoute("someOtherRoute")
      .build(currentActivity);
    startActivity(customFlutter);
Copy the code

FlutterEngine Cache mechanism

In fact, through the API and source code, you can see that the new IO relating to Flutter, Flutter, embedding. Android. * completely redesign the way Native calls, can be seen from the package name (embedding) was embedded into Native, One important change is the addition of the caching mechanism of FlutterEngine. The long response time of Flutter startup in the old way includes the time required to start the FlutterEngine, which can be understood as cold startup. Also, starting a Flutter from different native activities/viewControllers requires starting a new FlutterEngine, so not only does it take a long time to start a Flutter for the first time, but it takes the same time to start a Flutter every time. Take the following situation

Native A -> Flutter B -> Native C -> Flutter D

This instantiates both FlutterEngines when booted from Native A and Native B.

This is not only slower, but also more expensive in terms of resources. To solve this problem, the new solution introduces the FlutterEngine caching mechanism.

1. Use FlutterEngineCache

FlutterEngine = new FlutterEngine(context); // Instantiate FlutterEngine. / / preheating flutterEngine. GetDartExecutor (.) executeDartEntrypoint (DartEntrypoint. CreateDefault ()); FlutterEngineCache FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine); // Start an Activity with Intent Intent = flutterActivity.withcachedEngine ("my_engine_id") .build(); startActivity(intent); FlutterFragment = FlutterFragment. WithCachedEngine (FlutterFragment = FlutterFragment."my_engine_id")
  .build();
Copy the code

2. Inherit FlutterFragment/FlutterActivity

Handle yourself where FlutterEngine is stored

public class MyFlutterFragment extends FlutterFragment { @Override @Nullable protected FlutterEngine ProvideFlutterEngine (@nonNULL Context Context) {// Store FlutterEngine instancesreturnMyFlutterEngine.getFlutterEngine(); For example, in Applicationreturn((MyApp) context.getApplication).getFlutterEngine(); }}Copy the code
public class MyFlutterActivity extends FlutterActivity { @Nullable @Override public FlutterEngine provideFlutterEngine(@NonNull Context context) { FlutterEngine flutterEngine; / / to storage FlutterEngine instance FlutterEngine = MyFlutterEngineCache getFlutterEngine (); FlutterEngine = ((MyApp) getApplication()).getFlutterEngine();returnflutterEngine; }}Copy the code

3. Implement the FlutterEngineProvider interface in the Activity

public class MyActivity extends Activity implements FlutterEngineProvider { @Override @Nullable FlutterEngine ProvideFlutterEngine (@nonNULL Context Context) {// Store FlutterEngine instancesreturnMyFlutterEngine.getFlutterEngine(); For example, in Applicationreturn((MyApp) context.getApplication).getFlutterEngine(); }}Copy the code

FlutterBoost scheme

A new generation of Flutter-Native hybrid solutions. FlutterBoost is a Flutter plug-in that makes it easy to provide a Flutter hybrid integration solution for existing native applications. The idea behind FlutterBoost is to use Flutter like a Webview. Managing both Native and Flutter pages in an existing application is not easy. FlutterBoost handles the mapping and jumping of pages for you, as long as you care about the page name and parameters (usually urls).

FlutterBoost is the open source solution of Idle Fish to deal with Flutter-Native hybrid development, which is a popular solution. However, compared with the official solution, I think there are two important similarities and differences:

  1. One of the main goals of the Flutter library was to solve the problem that FlutterEngine could not be reused (the Flutter team did not have a solution to handle FlutterEngine reuse at that time). Now the Flutter team’s new solution could solve this problem, too.
  2. The current official solution for Flutter is much smaller in granularity. You can call a Flutter from a View, which means that you can replace only one chart in the screen with a Flutter.

Finally, one of the two official schemes has been abandoned and the other is still in the experimental stage. Now the Milestone of the latest scheme is In December, so the feasibility will be evaluated again then. However, the major domestic manufacturers have their own solutions, so the use of official solutions still needs careful evaluation.