Airing

Airing

哲学系学生 / 小学教师 / 程序员,个人网站: ursb.me
github
email
zhihu
medium
tg_channel
twitter_id

Flutter Boost Hybrid Development Practice and Source Code Analysis

1. Introduction#

Flutter Boost is a Flutter hybrid development framework developed by the Xianyu team. For project background, you can refer to this article from Xianyu: Start Flutter Hybrid Development with It - FlutterBoost.

The article mainly discusses some practical issues with multiple engines, so the hybrid solution currently adopted by Xianyu is to share the same engine. The features of Flutter Boost are as follows:

  • Reusable general hybrid solution
  • Supports more complex hybrid modes, such as supporting homepage tabs
  • Non-intrusive solution: no longer relies on modifying Flutter
  • Supports general page lifecycle
  • Unified and clear design concept

Flutter Boost is implemented using a shared engine model, where the main idea is that the Native container drives the Flutter page container through messages, achieving synchronization between the Native Container and the Flutter Container. Simply put, Xianyu aims to make the Flutter container feel like a browser. You fill in a page address, and the container manages the rendering of the page. On the Native side, we only need to care about how to initialize the container and set the corresponding page flags.

Given that there are no relevant integration documents and usage tutorials online, I took some time to study it over the past few days and organized it into this article for reference. Due to space constraints, this article only studies the integration and source code on the Android side; the iOS part will be supplemented in a future article if there is an opportunity.

Note: The version of Flutter Boost integrated in this article is 1.12.13, corresponding to the supported Flutter SDK version of 1.12.13-hotfixes, which is the latest version at the moment. However, after the Flutter Boost version is updated, the integration and usage methods may change, so please refer to this article with the 1.12.13 version in mind.

2. Integration#

2.1 Create Flutter Module#

Before we begin, we need to ensure that the project directory structure is as follows:

--- flutter_hybrid
		--- flutter_module
		--- FlutterHybridAndroid 
		--- FlutterHybridiOS

That is, the iOS project, Android project, and flutter_module directory are at the same level. This makes management easier and ensures consistency in paths in the subsequent integration code.

Next, let's create the Flutter Module:

cd flutter_hybrid
flutter create -t module flutter_module

It is important to note that if you want to create a flutter module that supports Android X, you need to add the --androidx parameter to the command, like this:

flutter create --androidx -t module flutter_module 

Note: If installing dependencies is too slow, you can switch to a domestic dependency mirror source.

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

Of course, we can also create a Flutter Module visually using the new version of Android Studio (version 3.6.1 or above, and install the Flutter and Dart plugins in the IDE). For specific methods, please refer to the official introduction (https://flutter.dev/docs/development/add-to-app/android/project-setup), which will not be elaborated here. However, I personally recommend using the more general method introduced in this article to create and integrate the Flutter Module.

2.2 Integrate Flutter Module#

After creating the Flutter Module, we need to integrate flutter_module into the Native project. There are two specific ways:

  1. Source dependency
  2. AAR dependency

2.2.1 Source Dependency Integration#

The advantage of source dependency is that it is convenient for development and debugging, which means you just need to add the dependency for flutter_module in the settings.gradle and build.gradle files in the Android project.

First, in the settings.gradle file, add the following code:

include ':app'                                     // Existing content

setBinding(new Binding([gradle: this]))                                
evaluate(new File(                                                     
  settingsDir.parentFile,                                               
  'flutter_module/.android/include_flutter.groovy'                      
))

After adding setBinding and evaluate, we can add the :flutter dependency in app/build.gradle:

...
dependencies {
  implementation project(':flutter')
...
}

We also need to specify the Java version for compilation in the android() configuration in this file to be Java 8, otherwise, it will throw an error:

compileOptions {
  sourceCompatibility 1.8
  targetCompatibility 1.8
}

Finally, execute the gradle sync to download the dependency libraries. If the integration is successful, you will see the flutter_module folder at the same level as the project in the left project directory.

2.2.2 AAR Dependency Integration#

If you need to use remote packaging and the remote machine does not have a Flutter environment, you can package Flutter into an AAR file for dependency. After generating the AAR file, you can reference it in the main project. The Flutter AAR contains the code of the Flutter SDK, so this method does not require a Flutter environment and is suitable for third-party quick integration.

cd .android/
./gradlew flutter:assembleDebug

2.3 Add Flutter Boost Dependency#

First, add the flutter-boost dependency in the Flutter Module project, that is, add the flutter-boost dependency in the dev_dependencies configuration of the pubspec.yaml file:

dev_dependencies:
  flutter_boost:
     git:
        url: 'https://github.com/alibaba/flutter_boost.git'
        ref: '1.12.13'

The above flutter boost supports AndroidX. If you want to support the support library, you need to switch branches:

flutter_boost:
    git:
        url: 'https://github.com/alibaba/flutter_boost.git'
        ref: 'task/task_v1.12.13_support_hotfixes'

After editing, execute the following command in the flutter_module directory to install dependencies.

flutter packages get

Then, in the build.gradle file in the app directory of the Android project, add the :flutter_boost dependency:

dependencies {
    ...
    implementation project(':flutter')
    implementation project(':flutter_boost')
}

Since Flutter Boost is integrated into our project as a Flutter Plugin, we also need to do some work. First, add the following code at the top of the build.gradle file in the app directory:

def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
    localPropertiesFile.withReader('UTF-8') { reader ->
        localProperties.load(reader)
    }
}

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
    flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
}

This requires us to specify the location of our local Flutter SDK in the local.properties file under the Android project (create a new one if this file does not exist):

flutter.sdk = /Users/airing/flutter

Finally, add the following code in the settings.gradle file to introduce the flutter-plugin:

def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()

def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
    pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}

plugins.each { name, path ->
    def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
    include ":$name"
    project(":$name").projectDir = pluginDirectory
}

Similarly, after modifying the dependencies of the Android project, a gradle sync is required.

At this point, Flutter Boost has been successfully integrated. Next, we will use Flutter Boost for hybrid development. Hybrid development mainly involves two scenarios:

  1. Adding a Flutter page in the Native project, i.e., Add a single Flutter screen.
  2. Embedding a Flutter module in the Native page, i.e., Add a Flutter Fragment.

Both methods are explained in practice on the Flutter official website, and here we will mainly look at how to implement them using Flutter Boost and explore its implementation principles.

3. Hybrid Development 1: Flutter View#

image

3.1 Using Flutter Boost in Flutter Module#

First, import the dependency:

import 'package:flutter_boost/flutter_boost.dart';

Then, in the rootWidget that runs in the main method, register two new pages so that the Native project can navigate to them.

import 'package:flutter/material.dart';
import 'package:flutter_boost/flutter_boost.dart';
import 'simple_page_widgets.dart';

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();

    FlutterBoost.singleton.registerPageBuilders({
      'first': (pageName, params, _) => FirstRouteWidget(),
      'second': (pageName, params, _) => SecondRouteWidget(),
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Boost example',
        builder: FlutterBoost.init(),
        home: Container(
            color:Colors.white
        ));
  }
}

The code is quite simple:

  1. Use FlutterBoost.singleton.registerPageBuilders to register pages in initState.
  2. Initialize FlutterBoost in the builder.

3.2 Using Flutter Boost in Android Project#

To add a Flutter page in the Android project, you need to add a Flutter Activity (for iOS, it is to add a new FlutterViewController; we will not elaborate on the iOS implementation here, interested readers can refer to the example code and source code of Flutter Boost).

Here, we add a Flutter Boost Activity in the Application configuration of AndroidManifest.xml:

<activity
  android:name="com.idlefish.flutterboost.containers.BoostFlutterActivity"
  android:theme="@style/Theme.AppCompat"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize" >
  <meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/page_loading"/>
</activity>

In addition, you also need to add the flutterEmbedding version setting in AndroidManifest.xml:

<meta-data android:name="flutterEmbedding"
	android:value="2">
</meta-data>

Next, initialize FlutterBoost, preferably in the onCreate method of the Application:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        INativeRouter router = new INativeRouter() {
            @Override
            public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
               String  assembleUrl=Utils.assembleUrl(url,urlParams);
                PageRouter.openPageByUrl(context,assembleUrl, urlParams);
            }

        };

        FlutterBoost.BoostLifecycleListener boostLifecycleListener= new FlutterBoost.BoostLifecycleListener(){

            @Override
            public void beforeCreateEngine() {

            }

            @Override
            public void onEngineCreated() {

            }

            @Override
            public void onPluginsRegistered() {

            }

            @Override
            public void onEngineDestroy() {

            }

        };

        Platform platform = new FlutterBoost
                .ConfigBuilder(this,router)
                .isDebug(true)
                .whenEngineStart(FlutterBoost.ConfigBuilder.ANY_ACTIVITY_CREATED)
                .renderMode(FlutterView.RenderMode.texture)
                .lifecycleListener(boostLifecycleListener)
                .build();
        FlutterBoost.instance().init(platform);
    }
}

Initializing FlutterBoost in the Android project requires four steps:

  1. Register the routing method (which will be discussed later regarding the implementation of PageRouter).
  2. Add lifecycle listener functions for Flutter Boost, which can be called back before the Flutter Engine is created, after it is created, after it is destroyed, and after Flutter Plugin registration.
  3. Declare Flutter Boost configuration, setting up routing and lifecycle functions.
  4. Initialize Flutter Boost.

Next, implement a page routing utility class PageRouter in the Android project. Here is the implementation from the Flutter Boost example code, which is quite comprehensive:

public class PageRouter {

    public final static Map<String, String> pageName = new HashMap<String, String>() {{

        put("first", "first");
        put("second", "second");
        put("tab", "tab");
        put("sample://flutterPage", "flutterPage");
    }};

    public static final String NATIVE_PAGE_URL = "sample://nativePage";
    public static final String FLUTTER_PAGE_URL = "sample://flutterPage";
    public static final String FLUTTER_FRAGMENT_PAGE_URL = "sample://flutterFragmentPage";

    public static boolean openPageByUrl(Context context, String url, Map params) {
        return openPageByUrl(context, url, params, 0);
    }

    public static boolean openPageByUrl(Context context, String url, Map params, int requestCode) {

        String path = url.split("\\?")[0];

        Log.i("openPageByUrl",path);

        try {
            if (pageName.containsKey(path)) {
                Intent intent = BoostFlutterActivity.withNewEngine().url(pageName.get(path)).params(params)
                        .backgroundMode(BoostFlutterActivity.BackgroundMode.opaque).build(context);
                if(context instanceof Activity){
                    Activity activity=(Activity)context;
                    activity.startActivityForResult(intent,requestCode);
                }else{
                    context.startActivity(intent);
                }
                return true;
            } else if (url.startsWith(FLUTTER_FRAGMENT_PAGE_URL)) {
                context.startActivity(new Intent(context, FlutterFragmentPageActivity.class));
                return true;
            } else if (url.startsWith(NATIVE_PAGE_URL)) {
                context.startActivity(new Intent(context, NativePageActivity.class));
                return true;
            }

            return false;

        } catch (Throwable t) {
            return false;
        }
    }
}

3.3 Opening Flutter Page in Native Project#

The call is quite simple. Bind the onClick listener to a button on the Native page to open the registered Flutter page called "first" and pass a map parameter:

@Override
public void onClick(View v) {
    Map params = new HashMap();
    params.put("test1","v_test1");
    params.put("test2","v_test2");

    PageRouter.openPageByUrl(this, "first", params);
}

Reviewing the code where we registered the page in Flutter in section 3.1, we notice a params parameter, which is indeed the parameters passed from Native when opening Flutter. We can print it out or pass it to the widget for additional processing:

FlutterBoost.singleton.registerPageBuilders({
      'first': (pageName, params, _) => {
        print("flutterPage params:$params");

        return FirstRouteWidget(params:params);
      },
      'second': (pageName, params, _) => SecondRouteWidget(),
});

3.4 Opening Native Page from Flutter Page#

Similarly, we may encounter a scenario where after opening a Flutter page from Native, we need to open a new Native page from Flutter. To do this, use FlutterBoost.singleton.open as follows:

// The parameters at the end will be concatenated to the query part of the URL in the native IPlatform.startActivity method.
// For example: sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton
     .open("sample://nativePage", urlParams: <dynamic,dynamic>{
      "query": {"aaa": "bbb"}
}),

Of course, this method not only supports opening Native pages but can also open a new Flutter page; you just need to write the route name correctly, which will not be elaborated here.

Note: Thanks to Flutter's JIT compilation mode, we can use the flutter attach command to achieve hot reload functionality, allowing us to develop Flutter pages without recompiling the project.

4. Hybrid Development 2: Flutter Fragment#

image

Assuming there is an Activity in the project configured as follows:

<activity
     android:name=".FlutterFragmentPageActivity"
     android:theme="@style/Theme.AppCompat"
     android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
     android:hardwareAccelerated="true"
     android:windowSoftInputMode="adjustResize">
     <meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/page_loading"/>
</activity>

In the corresponding layout, we need to add a FrameLayout component as a placeholder:

<FrameLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/fragment_stub"/>

Finally, in the code, obtain the corresponding URL's Flutter widget and place it in the placeholder component:

@Override
public void onClick(View v) {
    FlutterFragment mFragment = new FlutterFragment.NewEngineFragmentBuilder().url("flutterFragment").build();
    getSupportFragmentManager()
        .beginTransaction()
        .replace(R.id.fragment_stub, mFragment)
        .commit();
}

5. Flutter Boost Source Code Analysis#

This section will briefly analyze the principles of Flutter Boost. Only by understanding the fundamentals can we use it skillfully. Due to space constraints, it is not possible to analyze all the source code; this section will only analyze representative source code, and the rest of the principles are basically consistent, leaving it to the reader to read on their own.

We will start from the Dart side, focusing on two APIs: one is the registerPageBuilders for registering pages, and the other is open for opening pages, to see how Flutter Boost implements them.

5.1 Registering Pages#

In the process of using Flutter Boost, the first step is to register pages in Flutter by calling the registerPageBuilders function. Let's take a look at how this function is implemented.

In the flutter_boost.dart file, we can easily find the entry of this function:

///Register a map builders
void registerPageBuilders(Map<String, PageBuilder> builders) {
  ContainerCoordinator.singleton.registerPageBuilders(builders);
}

It calls the registerPageBuilders of the ContainerCoordinator singleton, so let's look at the implementation of this function in the container_coordinator.dart file:

final Map<String, PageBuilder> _pageBuilders = <String, PageBuilder>{};
  PageBuilder _defaultPageBuilder;

///Register page builder for a key.
void registerPageBuilder(String pageName, PageBuilder builder) {
  if (pageName != null && builder != null) {
    _pageBuilders[pageName] = builder;
  }
}

Here, we can find that PageBuilder is defined as a Widget, so this function actually puts the registered Widget into a Map, where the specified route name is its key. Next, we need to focus on how _pageBuilders is used after being defined:

BoostContainerSettings _createContainerSettings(
      String name, Map params, String pageId) {
    Widget page;

    final BoostContainerSettings routeSettings = BoostContainerSettings(
        uniqueId: pageId,
        name: name,
        params: params,
        builder: (BuildContext ctx) {
          //Try to build a page using keyed builder.
          if (_pageBuilders[name] != null) {
            page = _pageBuilders[name](name, params, pageId);
          }

          //Build a page using default builder.
          if (page == null && _defaultPageBuilder != null) {
            page = _defaultPageBuilder(name, params, pageId);
          }

          assert(page != null);
          Logger.log('build widget:$page for page:$name($pageId)');

          return page;
        });

    return routeSettings;
  }

We can see that in _createContainerSettings, after building the widget, it returns a routeSetting, which is called by pushContainer in _nativeContainerWillShow, and _nativeContainerWillShow is called in _onMethodCall.

bool _nativeContainerWillShow(String name, Map params, String pageId) {
    if (FlutterBoost.containerManager?.containsContainer(pageId) != true) {
      FlutterBoost.containerManager
          ?.pushContainer(_createContainerSettings(name, params, pageId));
    }
    return true;
  }
Future<dynamic> _onMethodCall(MethodCall call) {
    Logger.log("onMetohdCall ${call.method}");

    switch (call.method) {
      // Omitted unrelated code
      case "willShowPageContainer":
        {
          String pageName = call.arguments["pageName"];
          Map params = call.arguments["params"];
          String uniqueId = call.arguments["uniqueId"];
          _nativeContainerWillShow(pageName, params, uniqueId);
        }
        break;
    }
  	// Omitted unrelated code
}

The above two pieces of code serve to listen for communication from Native on the Dart side. If Native passes a message indicating that a page container needs to be opened (willShowPageContainer), the FlutterBoost container manager will open a new container based on the user-registered route configuration. Here, pushContainer mainly performs routing management and binding listeners, and we will not delve into this part of the logic; the main focus is on the mutual communication between Native and Dart.

5.2 Communication#

ContainerCoordinator(BoostChannel channel) {
    assert(_instance == null);

    _instance = this;

    channel.addEventListener("lifecycle",
        (String name, Map arguments) => _onChannelEvent(arguments));

    channel.addMethodHandler((MethodCall call) => _onMethodCall(call));
  }

The communication in Flutter Boost is handled by BoostChannel, which is essentially a layer of encapsulation over MethodChannel, and MethodChannel is one of the solutions for communication between Native and Flutter. Interested readers can refer to the relevant materials on MethodChannel for further understanding.

image

You can read the Flutter official introduction to MethodChannel: https://flutter.dev/docs/development/platform-integration/platform-channels

5.3 Opening Pages#

Finally, let's look at the implementation of the open function, which is also easy to find in the library:

Future<Map<dynamic, dynamic>> open(String url,
      {Map<dynamic, dynamic> urlParams, Map<dynamic, dynamic> exts}) {
    Map<dynamic, dynamic> properties = new Map<dynamic, dynamic>();
    properties["url"] = url;
    properties["urlParams"] = urlParams;
    properties["exts"] = exts;
    return channel.invokeMethod<Map<dynamic, dynamic>>('openPage', properties);
  }

We can see that its job is to package the parameters and send the openPage message to Native. Now let's see how the Native side processes this message after receiving it! In the Android side of the Flutter Boost source code, we can find the FlutterBoostPlugin.java file, which contains the MethodChannel logic to listen for messages from the Dart side:

class BoostMethodHandler implements MethodChannel.MethodCallHandler {

        @Override
        public void onMethodCall(MethodCall methodCall, final MethodChannel.Result result) {

            FlutterViewContainerManager mManager = (FlutterViewContainerManager) FlutterBoost.instance().containerManager();
            switch (methodCall.method) {
                // Omitted unrelated branches
                case "openPage": {
                    try {
                        Map<String, Object> params = methodCall.argument("urlParams");
                        Map<String, Object> exts = methodCall.argument("exts");
                        String url = methodCall.argument("url");

                        mManager.openContainer(url, params, exts, new FlutterViewContainerManager.OnResult() {
                            @Override
                            public void onResult(Map<String, Object> rlt) {
                                if (result != null) {
                                    result.success(rlt);
                                }
                            }
                        });
                    } catch (Throwable t) {
                        result.error("open page error", t.getMessage(), t);
                    }
                }
                break;
                default: {
                    result.notImplemented();
                }
            }
        }
    }

Upon receiving the openPage message from Dart, the Android side's container manager (FlutterViewContainerManager) will open a container based on the configuration data brought by Dart, and this openContainer, as can be seen from the source code, is ultimately an abstract method that requires us to implement it on the business side. Looking back at the first step of initializing Flutter Boost in the Android project in section 3.2, we implemented this openContainer, which is ultimately encapsulated by our PageRouter utility class, i.e., context.startActivity().

Thus, we have integrated Flutter Boost in the Android project to achieve hybrid development of Flutter in the Android project. This article only provides a preliminary analysis of the Flutter Boost source code; in the future, I will provide a detailed analysis and the integration and usage on the iOS side. Stay tuned.

Appendix: Flutter Boost Integration Practice (iOS Version)#

1. Introduction#

After integrating Flutter Boost into Android, let's see how to integrate Flutter Boost into the iOS project (Objective-C).

This article will briefly outline the process of integrating Flutter Boost into the iOS project as a supplement to the previous article.

Refer to the previous article: Flutter Boost Hybrid Development Practice and Source Code Analysis (Taking Android as an Example). The Flutter Module is still generated as in the previous article, and the directory structure remains as previously described, so it will not be elaborated here.

2. Integration#

2.1 Project Preparation#

Prepare a blank project with Cocoapods, and configure the previously prepared Flutter Module as a dependency in the Podfile:

flutter_application_path = '../flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

target 'FlutterHybridiOS' do
	install_all_flutter_pods(flutter_application_path)
end

Then run pod install in the root directory of the project to integrate the Flutter Module. If you see several new modules in our Pods, it indicates successful integration.

image.png

If you are not familiar with Cocoapods, please refer to the CocoaPods User Guide.

2.2 Implementing the Routing Class#

You can directly refer to the example provided by Flutter Boost's official documentation: https://github.com/alibaba/flutter_boost/blob/master/example/ios/Runner/PlatformRouterImp.h.

PlatformRouterImp.h:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <flutter_boost/FlutterBoost.h>

NS_ASSUME_NONNULL_BEGIN

@protocol FLBPlatform;

/**
 * Implement platform-side page opening and closing. It is not recommended to use it directly for page opening; it is recommended to use the open and close methods in FlutterBoostPlugin to open or close pages;
 * FlutterBoostPlugin has the ability to return data from pages.
 */
@interface PlatformRouterImp : NSObject<FLBPlatform>
@property (nonatomic,strong) UINavigationController *navigationController;
@end

NS_ASSUME_NONNULL_END

PlatformRouterImp.m:

#import "PlatformRouterImp.h"
#import <flutter_boost/FlutterBoost.h>

@interface PlatformRouterImp()
@end

@implementation PlatformRouterImp

#pragma mark - Boost 1.5
- (void)open:(NSString *)name
   urlParams:(NSDictionary *)params
        exts:(NSDictionary *)exts
  completion:(void (^)(BOOL))completion
{
    BOOL animated = [exts[@"animated"] boolValue];
    FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
    [vc setName:name params:params];
    [self.navigationController pushViewController:vc animated:animated];
    if(completion) completion(YES);
}

- (void)present:(NSString *)name
   urlParams:(NSDictionary *)params
        exts:(NSDictionary *)exts
  completion:(void (^)(BOOL))completion
{
    BOOL animated = [exts[@"animated"] boolValue];
    FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
    [vc setName:name params:params];
    [self.navigationController presentViewController:vc animated:animated completion:^{
        if(completion) completion(YES);
    }];
}

- (void)close:(NSString *)uid
       result:(NSDictionary *)result
         exts:(NSDictionary *)exts
   completion:(void (^)(BOOL))completion
{
    BOOL animated = [exts[@"animated"] boolValue];
    animated = YES;
    FLBFlutterViewContainer *vc = (id)self.navigationController.presentedViewController;
    if([vc isKindOfClass:FLBFlutterViewContainer.class] && [vc.uniqueIDString isEqual: uid]){
        [vc dismissViewControllerAnimated:animated completion:^{}];
    }else{
        [self.navigationController popViewControllerAnimated:animated];
    }
}
@end

As you can see, Flutter Boost supports regular push, modal pop-up, and manual pop.

2.3 Binding Route Management#

AppDelegate.h:

#import <UIKit/UIKit.h>
#import <flutter_boost/FlutterBoost.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (nullable, nonatomic, strong) UIWindow *window;
@end

AppDelegate.m:

#import "AppDelegate.h"
#import "PlatformRouterImp.h"
#import <flutter_boost/FlutterBoost.h>

@interface AppDelegate ()

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    PlatformRouterImp *router = [PlatformRouterImp new];
    [FlutterBoostPlugin.sharedInstance startFlutterWithPlatform:router
                                                        onStart:^(FlutterEngine *engine) {
                                                            
                                                        }];
	UITabBarController *tabVC = [[UITabBarController alloc] init];
	UINavigationController *rvc = [[UINavigationController alloc] initWithRootViewController:tabVC];
    router.navigationController = rvc;
    
	return YES;
}

3. Usage#

- (void)openClick:(UIButton *)button
{
    [FlutterBoostPlugin open:@"first" urlParams:@{kPageCallBackId:@"MycallbackId#1"} exts:@{@"animated":@(YES)} onPageFinished:^(NSDictionary *result) {
        NSLog(@"call me when page finished, and your result is:%@", result);
    } completion:^(BOOL f) {
        NSLog(@"page is opened");
    }];
}

- (void)openPresentClick:(UIButton *)button
{
    [FlutterBoostPlugin open:@"second" urlParams:@{@"present":@(YES),kPageCallBackId:@"MycallbackId#2"} exts:@{@"animated":@(YES)} onPageFinished:^(NSDictionary *result) {
        NSLog(@"call me when page finished, and your result is:%@", result);
    } completion:^(BOOL f) {
        NSLog(@"page is presented");
    }];
}

Similarly, you can open the registered route names in the Flutter Module from the Native side in two different ways.

Thus, we have successfully integrated Flutter Boost into the iOS project, and now let's embark on our hybrid journey!

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.