I. Environment construction

First, developers need to follow the native Android and iOS build process to build the development environment. Then go to the Flutter website to download the latest SDK and decompress it to your own folder. If you have download problems, you can use a temporary image created by Flutter officials for Chinese developers.

export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
Copy the code

To facilitate the use of the command line, you need to configure additional environment variables. First, use the vim command to open the terminal.

vim ~/.bash_profile  
Copy the code

Then add the following code to the.bash_profile file and use the source ~/.bash_profile command to make the file changes take effect.

The export PATH = / Users/MAC/Flutter, Flutter/bin: $PATH / / refresh. Following the source ~ /. FollowingCopy the code

Once you’ve done that, use theflutter doctorCommand to check whether the environment is correct. The following information is displayed if the environment is correct:

Create a Flutter AAR package

There are two main ways native Android can integrate Flutter. One is to create a Flutter Module and rely on it as a native Module. Another option is to package the Flutter Module as an AAR and then rely on the AAR package in the native project. The aar is officially recommended for access.

There are two ways to create a Flutter AAR, either using Android Studio or directly from the command line. Create the Flutter Module using the command line as follows:

flutter create -t module flutter_module
Copy the code

Then, into the flutter_module, perform flutter build aar command to generate the aar package, without any error, in/flutter_module /. Android/flutter/build/outputs directory to generate the corresponding aar package, The diagram below.

Build/lose/lose/lose/lose/lose/lose/lose/lose/lose/lose │ │ ├─ Flutter_release-1.0.aar. Md5 │ │ ├─ Flutter_release-1.0.aar. Sha1 │ │ ├─ Flutter_release-1.0.aar ├─ ├─ Maven-metadata.txt. Md5 │ ├─ Maven-metadata.txt. Md5 │ ├─ Maven-metadata.txt └ ─ ─ maven - metadata. XML. Sha1 ├ ─ ─ flutter_profile │ ├ ─ ─... └ ─ ─ flutter_debug └ ─ ─...Copy the code

Of course, we can also use Android Studio to generate aar packages. Select File -> New -> New Flutter Project -> New Flutter Module to generate a Flutter Module Project.

Then select Build -> build AAR to generate the AAR package.

The next step is to integrate the AAR into the native Android project.

Add Flutter dependencies

3.1 Adding AAR Dependencies

Official recommendation

The way to integrate an AAR is just as big as the way to integrate a regular AAR. First, create a new libs folder in your app directory and add the following configuration to build.gradle.

android { ... buildTypes { profile { initWith debug } } String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ? : "https://storage.googleapis.com" repositories { maven { url '/Users/mac/Flutter/module_flutter/build/host/outputs/repo' } maven { url "$storageUrl/download.flutter.io" } } } dependencies { debugImplementation 'com. XZH. Module_flutter: flutter_debug: 1.0' profileImplementation 'com. XZH. Module_flutter: flutter_profile: 1.0' ReleaseImplementation 'com. XZH. Module_flutter: flutter_release: 1.0'}Copy the code

Local Libs mode

Of course, we can also copy the generated AAR package to the local LIBS and add local dependencies by opening app/build.grade, as shown below.

repositories { flatDir { dirs 'libs' } } dependencies { ... Implementation fileTree(dir: 'libs', include: ['*.jar']) implementation(name: 'flutter_debug-1.0', ext: 'the aar') implementation 'IO. Flutter: flutter_embedding_debug: 1.0.0 - f0826da7ef2d301eb8f4ead91aaf026aa2b52881' implementation 'the IO. Flutter: armeabi_v7a_debug: 1.0.0 - f0826da7ef2d301eb8f4ead91aaf026aa2b52881' implementation 'the IO. Flutter: arm64_v8a_debug: 1.0.0 - f0826da7ef2d301eb8f4ead91aaf026aa2b52881' implementation 'the IO. Flutter: x86_64_debug: 1.0.0 - f0826da7ef2d301eb8f4ead91aaf026aa2b52881'}Copy the code

Build /host/outputs/repo flutter_release-1.0.pom

< the groupId > com. Example. Flutter_library < / groupId > < artifactId > flutter_release < / artifactId > < version > 1.0 < / version > <packaging>aar</packaging> <dependencies> <dependency> <groupId>io.flutter.plugins.sharedpreferences</groupId> < artifactId > shared_preferences_release < / artifactId > < version > 1.0 < / version > < scope > compile < / scope > < / dependency > <dependency> <groupId>io.flutter</groupId> <artifactId>flutter_embedding_release</artifactId> < version > a72c5d53cc6d00c840987f9059faed511a 1.0.0-626244 < / version > < scope > compile < / scope > < / dependency >Copy the code

When copying, pay attention to the context of our local AAR package, which corresponds one to one. Next, you need to add the following dependencies in the outer layer of build.gradle in order for them to work properly.

Jcenter buildscript {repositories {Google () () the maven {url "http://download.flutter.io" / / flutter rely on}} dependencies { The classpath 'com. Android. Tools. Build: gradle: 4.0.0'}}Copy the code

If the native Android project uses a componentized development approach, it is usually added to a module/lib dependency, such as module_flutter.

Configure repositories {flatDir {dirs 'libs' // AAR directory}} Configure repositories {// Detailed path under the main App flatDir { dirs 'libs', '.. /module_flutter/libs' } }Copy the code

3.2 Source code Dependency

In addition to using AAR, we can also rely on the Source code of the Flutter module. First, we create a Module in the native Android project, as shown below.By default, the following code is generated in settings.gradle.

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

Then, add the source dependencies to the app/build.gradle file.

dependencies {
  implementation project(':flutter')
}
Copy the code

3.3 Compiling aar using fat-AAR

If third-party libraries are introduced into Flutter, then multiple projects will need to use fat-AAR when using Flutter. First, add the fat-aar dependency to.android/build.gradle.

dependencies { ... Com. Making. Kezong: fat - the aar: 1.3.6}Copy the code

Then, in the android/Flutter/build. Add the following gradle plugin and rely on.

Add flutter_embedding. Jar debug embed "IO. Flutter: flutter_embedding_debug: 1.0.0 - eed171ff3538aa44f061f3768eec3a5908e8e852" / / add flutter_embedding jar release Embed "IO flutter: flutter_embedding_release: 1.0.0 - e1e6ced81d029258d449bdec2ba3cddca9c2ca0c" / / add each CPU version flutter. So Embed "IO flutter: arm64_v8a_debug: 1.0.0 - eed171ff3538aa44f061f3768eec3a5908e8e852" embed "IO. Flutter: armeabi_v7a_debug: 1.0.0 - eed171ff3538aa44f061f3768eec3a5908e8e852" embed "IO. Flutter: x86_64_debug: 1.0.0 - eed171ff3538aa44f061f3768eec3a5908e8e852" embed "IO. Flutter: x86_debug: 1.0.0 - eed171ff3538aa44f061f3768eec3a5908e8e852"Copy the code

At this point, if we run the project, we may get an error saying Cannot fit requested classes in a single dex file. This is an old subcontracting problem, which means that the dex exceeds 65K and one dex is too large to fit in requires multiple dex. To solve this problem, simply add multidex to app/build.gradle.

Android {defaultConfig {··· multiDexEnabled true}} dependencies {// Implementation 'androidx. Multidex: multidex: 2.0.1'}Copy the code

Jump the Flutter

5.1 start FlutterActivity

After incorporating Flutter, we then register FlutterActivity in androidmanifest.xml to implement a simple jump.

<activity android:name="io.flutter.embedding.android.FlutterActivity" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|dens ity|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize" android:exported="true" />Copy the code

Then add a jump code to any page, such as.

myButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startActivity( FlutterActivity.createDefaultIntent(this) ); }});Copy the code

However, when I run the project, I still get an error when executing the jump, with the following error message.

   java.lang.RuntimeException: Unable to start activity ComponentInfo{com.snbc.honey_app/io.flutter.embedding.android.FlutterActivity}: java.lang.IllegalStateException: ensureInitializationComplete must be called after startInitialization
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2946)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3081)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1831)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:201)
        at android.app.ActivityThread.main(ActivityThread.java:6806)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)
Copy the code

The reader error should be an initialization problem, but the official documentation does not mention any code related to the initialization step. Check the official issue of Flutter to add a line of initialization code:

public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); FlutterMain.startInitialization(this); }}Copy the code

Then, I run it again and find the following error.

java.lang.NoClassDefFoundError: Failed resolution of: Landroid/arch/lifecycle/DefaultLifecycleObserver; at io.flutter.embedding.engine.FlutterEngine.<init>(FlutterEngine.java:152) at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.setupFlutterEngine(FlutterActivityAndFragmentDelegate.ja va:221) at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onAttach(FlutterActivityAndFragmentDelegate.java:145) at  io.flutter.embedding.android.FlutterActivity.onCreate(FlutterActivity.java:399) at android.app.Activity.performCreate(Activity.java:7224) at android.app.Activity.performCreate(Activity.java:7213) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1272) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2926) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3081) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1831) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:201) at android.app.ActivityThread.main(ActivityThread.java:6806) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873) Caused by: java.lang.ClassNotFoundException: Didn't find class "android.arch.lifecycle.DefaultLifecycleObserver" on path: DexPathList[[zip file "/data/app/com.example.myapplication-kZH0dnJ-qI1ow1NqGOB2ug==/base.apk"],nativeLibraryDirectories=[/data/app/com.example .myapplication-kZH0dnJ-qI1ow1NqGOB2ug==/lib/arm64, /data/app/com.example.myapplication-kZH0dnJ-qI1ow1NqGOB2ug==/base.apk!/lib/arm64-v8a, /system/lib64, /vendor/lib64]]Copy the code

The final log indicates that Lifecycle is missing, so add a dependency on Lifecycle as follows.

Implementation 'android. Arch. Lifecycle: common - java8:1.1.0'Copy the code

And then it runs again without any problems.

5.2 Starting with FlutterEngine

By default, each FlutterActivity is created with a FlutterEngine, and each FlutterEngine has an initialization operation. This means there is a delay in starting a standard FlutterActivity. To reduce this delay, we can create a FlutterEngine before starting FlutterActivity, and then use the FlutterEngine when jumping to FlutterActivity. The most common approach is to initialize the FlutterEngine in the Application first, for example.

class MyApplication : Application() {
    
    lateinit var flutterEngine : FlutterEngine

    override fun onCreate() {
        super.onCreate()
        flutterEngine = FlutterEngine(this)
        flutterEngine.dartExecutor.executeDartEntrypoint(
            DartExecutor.DartEntrypoint.createDefault()
        )
        FlutterEngineCache
            .getInstance()
            .put("my_engine_id", flutterEngine)
    }
}
Copy the code

Then, we can use the buffered FlutterEngine to jump to FlutterActivity. Since the engine_id has been added when FlutterEngine is initialized, it needs to be started with the engine_id.

myButton.setOnClickListener {
  startActivity(
    FlutterActivity
      .withCachedEngine("my_engine_id")
      .build(this)
  )
}
Copy the code

Of course, we can jump to a default route at startup by calling the setInitialRoute method at startup.

class MyApplication : Application() {
  lateinit var flutterEngine : FlutterEngine
  override fun onCreate() {
    super.onCreate()
    // Instantiate a FlutterEngine.
    flutterEngine = FlutterEngine(this)
    // Configure an initial route.
    flutterEngine.navigationChannel.setInitialRoute("your/route/here");
    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.dartExecutor.executeDartEntrypoint(
      DartExecutor.DartEntrypoint.createDefault()
    )
    // Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine)
  }
}
Copy the code

Communicate with Flutter

How does a Flutter jump to a native Activity, or how does a Flutter destroy itself and return to a native page? This uses Flutter and the native Android communication mechanism called Channel, which is MethodChannel, EventChannel, and BasicMessageChannel respectively.

  • MethodChannel: Used to pass method calls and is the more common PlatformChannel.
  • EventChannel: Used to transmit events.
  • BasicMessageChannel: Used to transfer data.

For this simple jump, you can just use MethodChannel. First, we create a new PluginManager class in the Flutter_module and add the following code.

import 'package:flutter/services.dart';

class PluginManager {
  static const MethodChannel _channel = MethodChannel('plugin_demo');

  static Future<String> pushFirstActivity(Map params) async {
    String resultStr = await _channel.invokeMethod('jumpToMain', params);
    return resultStr;
  }

}
Copy the code

Then, when we click the return button on the Flutter entry page, we add a return method by calling the PluginManager to send a message as follows.

Future<void> backToNative() async { String result; try { result = await PluginManager.pushFirstActivity({'key': 'value'}); } on PlatformException {result = 'failure '; } print('backToNative: '+result); }Copy the code

Next, re-compile the AAR package using the Flutter Build AAR and add the following code to the configureFlutterEngine method of the Flutter entry page of native Android.

class FlutterContainerActivity : FlutterActivity() { private val CHANNEL = "plugin_demo" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } override fun configureFlutterEngine(flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) MethodChannel(flutterEngine.dartExecutor, CHANNEL).setMethodCallHandler { call, result -> if (call.method == "jumpToMain") { val params = call.argument<String>("key") Toast.maketext (this," return native page ", toast.length_short).show() finish() result.success(params)} else {result.notimplemented () }}}}Copy the code

To re-run the native project, click the back button in the upper left corner of the Flutter to return to the native page. Other hybrid jumps can also be handled in this way.

For mixed routing and FlutterEngine multi-instance issues in mixed development, see FlutterBoost.