One, foreword

Flutter is now in its prime. As an iOS app developer, Flutter is determined to learn more about how a powerful cross-platform framework can be used on all platforms, as well as Android development.

In order to be less intrusive, the best solution is to compile Flutter into aar for Android projects. The following will introduce the process of developing A mix of Flutter and Android and compiling Flutter into AAR, as well as the problems encountered in my practice.

Add the Flutter project to Android project

Import both Flutter and Android functions into AndroidStudio during feature development. Debug the mix project every time you compile Android functions. Compile Flutter into AAR file and import it into Android project after function development is completed. The general steps are as follows:

  • Step 1: Create an Android project
  • Step 2 create a Flutter Module
  • Step 3: Modify the project configuration file to introduce the Flutter project into the Android project
  • Step 4: Write the test code and compile the two projects to see the results

Android_Flutter_MixBuilder
android
my_flutter
Android
Flutter



Step 1: Create an Android project

In the firstandroidCreating a DirectoryAndroid project , select NewAndroid Studio project – > selectEmpty Activity.apiSelect version 4.1 (the environment for this practice is 4.1), and finish.




Step 2 create a Flutter Module

There are two ways to create a Flutter Module. One is via AndroidStudio and the other is via the command line.

  • Method 1: Cli

Open the terminal and switch to the newly created one
my_flutterRun the following command:

flutter create -t module my_flutterCopy the code

My_flutter is the name of the module.

  • Method 2: Create a new one using Android Studio

Open the
Android StudioSelect new
Flutter project-> Select New
Flutter ModuleTo finish.


Step 3: Modify the project configuration file to introduce the Flutter project into the Android project

Find the build.gradle file in the Android directory, change the default library address to the domestic Ali cloud maven library address, to prevent unscientific Internet access caused by the update slow problem.

buildscript {  
  repositories {   
     maven { url 'https://maven.aliyun.com/repository/google' } 
     maven { url 'https://maven.aliyun.com/repository/jcenter' }  
     maven { url 'http://maven.aliyun.com/nexus/content/groups/public' } 
  }  
  dependencies {  
      classpath 'com. Android. Tools. Build: gradle: 3.5.3'               
      // NOTE: Do not place your application dependencies here; they belong    
      // in the individual module build.gradle files 
   }
}
allprojects { 
   repositories {  
      maven { url 'https://maven.aliyun.com/repository/google' }  
      maven { url 'https://maven.aliyun.com/repository/jcenter' }  
      maven { url 'http://maven.aliyun.com/nexus/content/groups/public'}}}Copy the code

Go to the build.gradle file in the Android /app directory and declare the following source compatibility.

android { 
    cmpileOptions { 
     sourceCompatibility 1.8 targetCompatibility 1.8}}Copy the code

Add the following code to the setting.gradle file in the root directory (android) :

include ':app'
rootProject.name='myAndroid'// Add the following configurationsetBinding(new Binding([gradle: this]))
evaluate(new File( 
         settingsDir.parentFile,  
         'my_flutter/.android/include_flutter.groovy'
)) Copy the code

“My_flutter” in the code above is the name of my new Flutter Module, Sync the project.



After Sync succeeds, we can see that there is an additional my_Flutterd subproject in the project



After the Flutter Module is added, you need to add the Module’s dependencies to the build.gradle file in the Android /app directory.

implementation project(':flutter')Copy the code



You have successfully added FlutterModule to the Android project. Now we can write some simple code to interact directly with Flutter.

Step 4: Write the test code and compile the two projects to see the results

In order to solve the usage situation in most scenarios, we mainly wrote the code for the page jump between Android and Flutter and the transfer of values during the jump. In the Android project, create the Android native pages FirstNativeActivity and SecondNativeActivity, and the Android shell native page FirstFlutterActivity that hosts the Flutter page. Here are five scenarios in which Android interacts with Flutter:

  • The Android page opens the Flutter page and uploads values
  • The Flutter page opens the Android page and uploads values
  • The Android page returns to the Flutter page and uploads values
  • The Flutter page returns to the Android page and uploads values
  • Solve the problem that the virtual back button of mobile phone system destroys the stack order of normal pages

Before starting the above scenario, introduce the Flutter page in the Native Android shell page FirstFlutterActivity. And define the following content on the Flutter page:

  1. Add a textView to display content from other pages
  2. Add a button to open the next native page
  3. Add a button to return to the previous native page

Ideas: New FlutterView -> Drag a FrameLayout in XML -> Add FlutterView to FrameLayout -> Create FlutterEngine And initialize the engine to point to a route to a Flutter page -> FlutterView loads the content using the FlutterEngine. The above description is similar to how a WebView is loaded.

Introduction of key nouns:

FlutterView: located in IO flutter. Embedding. Android package, in Flutter1.12 version, he is responsible for creating the flutter view. In addition, FlutterView inherits from FrameLayout, so we can treat it as a basic View to operate.

FlutterEngine: An engine that executes Dart code on Android, rendering UI code written by Flutter into FlutterView.

createFlutterViewAdd the code to the view:

FlutterView flutterView = new FlutterView(this);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( 
       ViewGroup.LayoutParams.MATCH_PARENT, 
       ViewGroup.LayoutParams.MATCH_PARENT);
FrameLayout flContainer = findViewById(R.id.layout001);
flContainer.addView(flutterView, lp);Copy the code

Create FlutterEngine and render a Flutter page with route name route1. The route can carry some data (string: message).

FlutterEngine = new flutterEngine (this); String str ="route1? {\"message\":\"" + message + "\"}"; flutterEngine.getNavigationChannel().setInitialRoute(str); flutterEngine.getDartExecutor().executeDartEntrypoint( DartExecutor.DartEntrypoint.createDefault() ); / / page rendering Flutter flutterView attachToFlutterEngine (flutterEngine);Copy the code

The Dart code for Flutter is as follows:

Parses the route to obtain the data carried this time

void main() => runApp(_widgetForRoute(window.defaultRouteName)); Widgetforroute (String url) {// Route name String route = url.indexof ('? ') = = 1? url : url.substring(0, url.indexOf('? ')); // Parameter Json String String paramsJson = url.indexof ('? ') = = 1?'{}' : url.substring(url.indexOf('? ') + 1);
  Map<String, dynamic> mapJson = json.decode(paramsJson);  String message = mapJson["message"]; Switch (route) {case 'route1':
      return MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: Text('Flutter page'),
          ),
          body: Center(child: Text('Page name: $route',style: TextStyle(color: Colors.red), textDirection: TextDirection.ltr),),
        ),
      );
    default:
      return Center(
        child: Text('Unknown route: $route',style: TextStyle(color: Colors.red), textDirection: TextDirection.ltr),
      );
  }}Copy the code

Complete the above code to view the Flutter page in the Android shell. Here is how Android and Flutter interact in the shell page:

We are familiar with the traditional H5 page and native interaction, through the intermediate communication tool object, defined methods or properties to communicate. Flutter also has a special Platform Channel for native Interaction with Android, which has three types:

  • MethodChannel: The most common method pass-through, which helps Flutter and native platforms call methods to each other, is also the focus of this tutorial.
  • BasicMessageChannel: Used to transfer data information.
  • EventChannel: used for events listening and transmission.




Android
Flutter
MethodChannel
Flutter
Android
Flutter


AndroidPart of the code is as follows
The following code is still in place
FirstFlutterActivity
In writing)
:

Let’s start with a MethodChannel by defining it as unique. Note: Here we define two methodChannels, one for sending messages to Flutter and one for receiving callback messages to Flutter.

//Flutter sends messages to Native. Private static final String CHANNEL_NATIVE ="com.example.flutter/native"; //Native sends messages to Flutter. Private static final String CHANNEL_FLUTTER ="com.example.flutter/flutter";Copy the code

Initialize the MethodChannel with the defined name. Note: There are two parameters in the MethodChannel initialization method. The first parameter, BinaryMessenger, can be understood as a binding item for MethodChannel and Flutter page, FlutterEngine’s getDartExecutor() method gives us the first argument to construct a MethodChannel. The second parameter needs to be passed in a unique name that we defined earlier.



Android receives messages from Flutter

To receive Flutter messages, initialize a MethodChannel with the previously defined name CHANNEL_NATIVE. The following code shows the MethodChannel callback parameters: MethodCall call, methodChannel. Result Result. Call gives us the name of the method that the Flutter sends. Result provides methods to tell the Flutter page the result of our processing after processing the logic, such as result.success(), result.notimplemented ().

MethodChannel nativeChannel = new MethodChannel(flutterEngine.getDartExecutor(), CHANNEL_NATIVE);
        nativeChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall call, MethodChannel.Result result) {
                switch (call.method){
                    case "backFirstNative":
                        result.success("Received a message from Flutter.");
                        break;
                    default :
                        result.notImplemented();
                        break; }}});Copy the code


Android sends messages to Flutter

Sending messages to Flutter also initializes a MethodChannel with the previously defined name CHANNEL_FLUTTER. Use the MethodChannel method invokeMethod to send this message to Flutter!

Map<String, Object> result = new HashMap<>();
result.put("message"The @"I am the message from Android, I am going to Flutter."); MethodChannel flutterChannel = new MethodChannel(flutterEngine.getDartExecutor(), CHANNEL_FLUTTER); / / call the method of defining the Flutter end onActivityResult flutterChannel. InvokeMethod ("onActivityResult", result);Copy the code


The code for the Android side of the interaction is described above. The code for the Flutter side is described below. As follows:

Dart: Dart: Dart: dart: dart: dart: dart: dart: dart: dart: dart: dart Define a Widget to display incoming data from Android and create a button to send a message to Android. Dart file we also define a MethodChannel with the same name on Android. Note: We should write the listener code for MethodChannel in the initState() method of the Widget. We can find out what method Android wants to call us this time and what arguments it brings by getting call.method and call.method.arguments in the Flutter callback method method.

class ContentWidget extends StatefulWidget{
  ContentWidget({Key key, this.route,this.message}) : super(key: key);
  String route,message;
  _ContentWidgetState createState() => new _ContentWidgetState();
}
class _ContentWidgetState extends State<ContentWidget>{
  static const nativeChannel = const MethodChannel('com.example.flutter/native');
  static const flutterChannel = const MethodChannel('com.example.flutter/flutter');
  void onDataChange(val) {
    setState(() {
      widget.message = val;
    });
  }
  @override
  void initState(){
    super.initState();
    Future<dynamic> handler(MethodCall call) async{
      switch (call.method){
        case 'onActivityResult':
          onDataChange(call.arguments['message']);
          print('1234'+call.arguments['message']);
          break;
      }
    }
    flutterChannel.setMethodCallHandler(handler);
  }
  Widget build(BuildContext context) {
    // TODO: implement build
    return Center(
      child: Stack(
        children: <Widget>[
          Positioned(
            top: 100,
            left: 0,
            right: 0,
            height: 100,
            child: Text(widget.message,textAlign: TextAlign.center,),
          ),
          Positioned(
            top: 300,
            left: 100,
            right: 100,
            height: 100,
            child: RaisedButton(
                child: Text('Open previous native page'),
                onPressed: (){
                  returnLastNativePage(nativeChannel);
                }
            ),
          ),
          Positioned(
            top: 430,
            left: 100,
            right: 100,
            height: 100,
            child: RaisedButton(
                child: Text('Open next native page'), onPressed: (){ openNextNativePage(nativeChannel); }),)],),); }}Copy the code

The above code is missing methods: returnLastNativePage, openNextNativePage. As follows:

I’m sure you remember when we were inAndroidPage to receiveFlutterCan also be called after the callbackresult.success()To tell theFlutterPage our processing results. Yes, we asynchronously get the information for these callbacks and print it in the following two methods.

Future<Null> returnLastNativePage(MethodChannel channel) async{
  Map<String, dynamic> para = {'message':'Hi, this article is from the Flutter page, go back to the first native page and see me'};
  final String result = await channel.invokeMethod('backFirstNative',para);
  print('This is printed in the Flutter'+ result);
}Copy the code

Future<Null> openNextNativePage(MethodChannel channel) async{
  Map<String, dynamic> para = {'message':'Hi, this article is from the Flutter page, open the second native page and see me'};
  final String result = await channel.invokeMethod('openSecondNative',para);
  print('This is printed in the Flutter'+ result);
}Copy the code


Android and Flutter can now communicate with each other. If you found that the main at compile time. The dart MethodChannel error, so you must be without the introduction of the right header file such as: import ‘package: flutter/services. The dart’. The jump scenarios described earlier turned out to be jumps between Android and updates of the shell page to Flutter. Here’s how to transfer values between Android activities. Android base friends can directly skip to the last view: solve the mobile phone system virtual back button to destroy the normal page stack order.



Here is a brief introduction to jumping between Android pages. Note: Start the Activity using the startActivityForResult method so that the onActivityResult method can receive the callback when the Activity closes. The code is as follows:

FirstFlutterActivityCode:

Open the next oneThe Activity.

/ / jump native page Intent jumpToNativeIntent = new Intent (FirstFlutterActivity. This, SecondNativeActivity. Class); jumpToNativeIntent.putExtra("message", (String) call.argument("message"));
startActivityForResult(jumpToNativeIntent, Activity.RESULT_FIRST_USER);
result.success("Successfully opened the second native page");Copy the code

receiveActivityAnd pass the data toFlutterDisplay.

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    switch (requestCode) {
        case 1:
            if(data ! // NativePageActivity returns data String message = data.getStringExtra("message");
                Map<String, Object> result = new HashMap<>();
                result.put("message", message); // Create MethodChannel, FlutterView here is the View MethodChannel returned by Flutter. CreateView flutterChannel = new MethodChannel(flutterEngine.getDartExecutor(), CHANNEL_FLUTTER); / / call the method of defining the Flutter end flutterChannel. InvokeMethod ("onActivityResult", result);
            }
            break;
        default:
            break;
    }}
Copy the code

SecondNativeActivityCode:

The XML drags a textView with id textView2 to display the information and a button with ID Button001 to return to the previous page.

public class SecondNativeActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second_native);
        Intent intent = getIntent();
        String content = intent.getStringExtra("message");
        TextView textView = findViewById(R.id.textView2);
        textView.setText(content);
        Button btnOpen = findViewById(R.id.button001);
        btnOpen.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                intent.putExtra("message"."Hey, this article is from the second native page, will see me on the Flutter page");
                setResult(RESULT_OK,intent); finish(); }}); }}Copy the code


Solve the problem that the virtual back button of mobile phone system destroys the stack order of normal pages

Scene: Native page A -> Open the native shell page (display contents: Flutter page B) -> Open the native shell page (display contents: Flutter page C) -> click the virtual Back button

The native shell page (displaying content: Page C) returns directly to the native page A.

The above phenomenon is similar to an unhandled WebView page stack management, which is not what we want. We want to make sure that the original shell page (display content: Flutter page C) returns to the original shell page (display content: Flutter page B) after clicking the virtual Return button.

It would be perfect if the subsequent action of clicking the virtual return button were left to Flutter.

We know that clicking the virtual back button will call the method onBackPressed(), in which we send a message to the Flutter (the “backAction” method is called).

@Override
public void onBackPressed() {
    MethodChannel flutterChannel = new MethodChannel(flutterEngine.getDartExecutor(), CHANNEL_FLUTTER);
    flutterChannel.invokeMethod("backAction", null);
}Copy the code

Treatment of Flutter:

Similar to the previous interaction, we added a case ‘backAction’ that uses a method canPop() inside the Flutter. We know that calling pop() at the bottom of the stack will crash the program, and canPop() is a good way to solve this problem. When canPop() is called at the bottom of the stack, it returns a Boolean value telling us whether we can continue to fall back. When we find that the result of canPop() is false, the current Flutter page is the last one and we should inform the Android shell page to revert to the previous native page. The code is as follows:

void initState(){
  super.initState();
  Future<dynamic> handler(MethodCall call) async{
    switch (call.method){
      case 'onActivityResult':
        onDataChange(call.arguments['message']);
        print('1234'+call.arguments['message']);
        break;
      case 'backAction':
        if (Navigator.canPop(context)) {
          Navigator.of(context).pop();
        } else {
          nativeChannel.invokeMethod('backAction');
        }
        break;
    }
  }
  flutterChannel.setMethodCallHandler(handler);
}Copy the code

The Android shell page receives the message and returns to the previous native page

MethodChannel nativeChannel = new MethodChannel(flutterEngine.getDartExecutor(), CHANNEL_NATIVE);
        nativeChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall call, MethodChannel.Result result) {
                switch (call.method){
                    case "backFirstNative":
                        result.success("Received a message from Flutter.");
                        break;
                    case "backAction":
                        finish();
                        result.success("Successfully returned to first native page via virtual button");
                        break;
                    default :
                        result.notImplemented();
                        break; }}});Copy the code


Package the Flutter project in the form of AAR for Android to call

In Flutter1.12, packaging a Flutter has become very simple. After completing the Flutter code, run the command line:

flutter build aarCopy the code

Or go to Build -> Build AAR



When you use the command line to package, the console will ask you how to use the AAR when the package is complete



So, find the Android /app/build.gradle file under the Android project that you want to reference the AAR. Add the code mentioned in points 2, 3, and 4 above. After adding the code, Sync and run the project to see the correct results.

The previous attempts are based on Flutter1.12. If your Flutter version < 1.12, please update the Flutter version first.


— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — complete code address — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — –

https://github.com/JJwow/Android_Flutter_MixBuilder.gitCopy the code