preface
In “Flutter Mixed Development Topic 1”, we introduced the mixed development solutions provided by Flutter officials. However, some problems of Flutter have not been solved. For example, native and superimposed Flutter page jumps cause memory explosion due to the repeated creation of the Flutter Engine, global variables in the Flutter application cannot be shared between individual pages, memory leak on iOS platform, etc. So far, there hasn’t been a lot of time spent on optimizing the hybrid development solution.
Many large domestic manufacturers started to study Flutter last year and completed the integration in their existing projects. Among them, Ali Xianyu team was relatively early in the research and put more effort into Flutter. Xianyu APP has integrated Flutter in 2018, and was initially used to open the most product details pages. In the early stage, Xianyu developed a set of hybrid development plug-in hybridStackManager to solve the problem of hybrid development. The open source address of the project is github.com/alibaba-flu… , which requires modification of the source code of the Flutter framework and has limitations in complex page scenarios. Therefore, the Xianfish team developed a new generation of hybrid development technology solution FlutterBoost, which was opened for open source in early March this year.
FlutterBoost introduction
Check out the Idle Fish team’s article to learn more about FlutterBoost
Mp.weixin.qq.com/s/v-wwruadJ…
FlutterBoost integration
Because FlutterBoost is packaged as a plug-in, integration is very simple, requiring only a small amount of code access to the project. The following takes a Demo project as an example to understand the access mode in detail.
Examples of engineering
We have a native Android project called FBDemo, and we need to develop new pages based on this project to introduce Flutter. We can create a Flutter Module project named Flutter_boost_module in the same directory as FBDemo. Introduce and integrate the Flutter Module project into the native project as described in The Flutter Hybrid Development Project 1. At this point we can develop new Flutter pages in the Flutter Module project. The following is a step-by-step explanation of how FlutterBoost can be integrated
The Flutter Module project integrates FlutterBoost
Add the dependent plug-in configuration in the pubspec.yaml file of the Flutter_BOOst_module project
Dependencies: flutter_boost: ^ 0.0.411Copy the code
After the configuration, run the flutter packages get command to download the dependent plug-ins to the local device.
FlutterBoost is integrated with android native projects
The Flutter Module project introduces the FlutterBoost plugin and synchronizes the native projects in Android Studio. After the synchronization, the project structure is as follows
We can then import the Android project code for FlutterBoost by adding the following project dependencies in the build.gradle directory of the app
dependencies {
...
implementation project(':flutter_boost')}Copy the code
The Flutter Module project uses FlutterBoost
Suppose we use Flutter to create two page widgets: FirstPage and SecondPage.
First we need to register the two pages in the rootWidget running in the main method.
@override
void initState() {
super.initState();
FlutterBoost.singleton.registerPageBuilders({
'flutterbus://flutterFirstPage': (pageName, params, _) {
print("first flutterPage params:$params"); .return FirstPage();
},
'flutterbus://flutterSecondPage': (pageName, params, _) {
print("second flutterPage params:$params"); .returnSecondPage(); }}); FlutterBoost.handleOnStartPage(); } @override Widget build(BuildContext context) {return MaterialApp(
title: 'Flutter Boost example',
builder: FlutterBoost.init(),
home: Container());
}
Copy the code
FlutterBoost is available in android native projects
The Flutter engine is loaded and FlutterBoostPlugin is initialized
First, according to the example given by FlutterBoost, the Application of our native project needs to inherit from FlutterApplication, which is not required. FlutterApplication initialloads the flutter. So library in the onCreate method, which we can add ourselves where appropriate.
Second, the Example of FlutterBoost initializes the FlutterBoostPlugin in the custom Application onCreate method, which we can extract into a separate class.
Based on the above two points, I implemented a utility class to perform FlutterBoost initialization
Public class FlutterMediator {public static void init(final Application app) { Mainly in the Flutter engine file FlutterMain. StartInitialization (app); FlutterBoostPlugin.init(newIPlatform() {
@Override
public Application getApplication() {
return app;
}
@Override
public Activity getMainActivity() {
return MainActivity.sRef.get();
}
@Override
public boolean isDebug() {
return true;
}
@Override
public boolean startActivity(Context context, String url, int requestCode) {
Debuger.log("startActivity url="+url);
return PageRouter.openPageByUrl(context,url,requestCode);
}
@Override
public Map getSettings() {
returnnull; }}); }}Copy the code
Thus, we only need to call Fluttermediator.init (this) in the onCreate method of our custom Application in the native project; Method to complete the initialization of FlutterBoost. Where MainActivity should always exist at the bottom of the stack, usually our home page.
The Flutter page corresponds to the Native container
After FlutterBoost is initialized, we need to create a corresponding Native Container for the FirstPage and SecondPage pages in Flutter, that is, the Container defined in FlutterBoost. It can be an Activity or a Fragment, and here we’re using an Activity,
// The Native container public class FlutterFirstPageActivity extends BoostFlutterActivity {private int id = 0; private String name; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent();if(intent ! = null) { String url = intent.getStringExtra("url");
Map map = UrlUtil.parseParams(url);
id = Integer.parseInt(map.get("id").toString());
name = map.get("name").toString();
}
}
@Override
public String getContainerName() {
return PageRouter.FLUTTER_FIRST_PAGE_URL;
}
@Override
public Map getContainerParams() {
Map map = new HashMap();
map.put("id", id);
map.put("name", name);
returnmap; } @Override public void onRegisterPlugins(PluginRegistry registry) { GeneratedPluginRegistrant.registerWith(registry); }}Copy the code
FlutterBoost has already implemented BoostFlutterActivity, the container of Activity type, which implements IFlutterViewContainer interface. When we define the container, we only need to inherit the Activity and implement three methods
getContainerName
PageBuilder is the name of the container that contains the Flutter layer.getContainerParams
Is the parameters that the container needs to pass to the corresponding Widget of the Flutter layer. The parameters received by the page jump are transferred to the Flutter page here, and the data needs to be wrapped in the Map.onRegisterPlugins
Is to register the plug-in for the page;
Page hop route
In the FlutterBoostPlugin initialization above we see a line of PageRouter. OpenPageByUrl (context, URL,requestCode); Code that handles the operation of a Flutter page opening another page based on the URL. PageRouter is a page routing class defined in our native layer
public class PageRouter {
public static final String NATIVE_FIRST_PAGE_URL = "flutterbus://nativeFirstPage";
public static final String NATIVE_SECOND_PAGE_URL = "flutterbus://nativeSecondPage";
public static final String FLUTTER_FIRST_PAGE_URL = "flutterbus://flutterFirstPage";
public static final String FLUTTER_SECOND_PAGE_URL = "flutterbus://flutterSecondPage";
public static boolean openPageByUrl(Context context, String url) {
return openPageByUrl(context, url, 0);
}
public static boolean openPageByUrl(Context context, String url, int requestCode) {
try {
Intent intent;
if (url.startsWith(NATIVE_FIRST_PAGE_URL)) {
intent = new Intent(context, FirstNativeActivity.class);
intent.putExtra("url", url);
context.startActivity(intent);
return true;
} else if (url.startsWith(NATIVE_SECOND_PAGE_URL)) {
intent = new Intent(context, SecondNativeActivity.class);
intent.putExtra("url", url);
if(context instanceof Activity) {
((Activity)context).startActivityForResult(intent, requestCode);
}
return true;
} else if(url.startsWith(FLUTTER_FIRST_PAGE_URL)) {
intent = new Intent(context, FlutterFirstPageActivity.class);
intent.putExtra("url", url);
context.startActivity(intent);
return true;
} else if (url.startsWith(FLUTTER_SECOND_PAGE_URL)) {
intent = new Intent(context, FlutterSecondPageActivity.class);
intent.putExtra("url", url);
if(context instanceof Activity) {
((Activity)context).startActivityForResult(intent, requestCode);
}
return true;
} else {
return false;
}
} catch (Throwable t) {
return false; }}}Copy the code
The Flutter page and Native page jump
After the above preparations and page routing classes are defined, we can call corresponding methods on the Flutter layer and the Native layer to perform page hopping operations based on the URL of the page to be jumped. You can either jump from a Native page to a Native page or jump from a Native page to a Flutter page. You can jump to Native pages from a Flutter page or to a Flutter page, as shown in the following example
The Native page jumps to the Flutter page
To jump to a Native page is to open a corresponding Native container of a Flutter page. We can jump to a Flutter page based on the route, for example, from MainActivity to the FirstWidget page of the Flutter page in one code
PageRouter.openPageByUrl(this, PageRouter.FLUTTER_FIRST_PAGE_URL+"? id=123&name=bruce");
Copy the code
In the code above, the corresponding URL contains parameters. In many cases, you do not need to upload parameters to a Flutter page. Here is an example of uploading parameters to a Flutter page. We need to parse url parameters in Flutter Native container FlutterFirstPageActivity, and then wrap them into Map in the following override method and pass them to Flutter end via PlatformChannel
@Override
public Map getContainerParams() {
Map map = new HashMap();
map.put("id", id);
map.put("name", name);
return map;
}
Copy the code
The Flutter page jumps to the Native page
We just need to jump to the Flutter side using the methods provided by FlutterBoost. For example, I need to jump from the FirstWidget to the FirstNativeActivity page. The page should face the url for “flutterbus: / / nativeFirstPage”, we can perform the following code
FlutterBoost.singleton.openPage("flutterbus://nativeFirstPage", {
"query": {"description": "Hello everyone, I'm from the First Flutter page!!!!!!!!"}});Copy the code
The value corresponding to query is the parameter to be passed to the next page, which is not required or may not be passed.
Jump to the Flutter page
There are actually two ways to do this. If you use the page jump defined by FlutterBoost, you need to use the following method
FlutterBoost.singleton.openPage("flutterbus://flutterSecondPage"{});Copy the code
After integrating FlutterBoost, it is recommended to use the pages defined by FlutterBoost to jump in openPage mode.
You can also jump to a Flutter using the Navigator
Navigator.of(context).push(MaterialPageRoute(builder: (context){
return SecondPage(enterType: 1,);
}));
Copy the code
If two jump mode combination can returned to the page appear some problems, because FlutterBoost provides a way to close the current page FlutterBoost. Singleton. ClosePageForContext (context); Therefore, enterType is defined in the Widget page to distinguish between them. By default, FlutterBoost jump is used. If Navigator jump onto the Widget page, You need to pass enterType=1, which will be processed using the following method when you return to the current page
void exitPage(BuildContext context) {
if (enterType == 0) {
FlutterBoost.singleton.closePageForContext(context);
} else{ Navigator.pop(context); }}Copy the code
Page jump return value problem
FlutterBoost realizes the function of Flutter page to jump to Native page and receive returned value. The specific method is as follows
FlutterBoost.singleton.openPage(
"flutterbus://nativeSecondPage",
{
"query": {"requestCode": 1000,"type": "second"}
},
resultHandler: (String key, Map<dynamic, dynamic> result) {
print("==============> key: $key, result: $result");
});
Copy the code
The resultHandler parameter in the openPage method is the callback function that receives the return value. However, there are bugs in this method after testing, and there are two main fixes
Solution 1
This solution adopts the traditional method of Activity jump and return results, that is, Flutter jump specifies the Native page in the openPageByUrl method of the Native PageRouter using the following jump method
if(context instanceof Activity) {
((Activity)context).startActivityForResult(new Intent(context, NativePageActivity.class), requestCode);
}
Copy the code
FlutterBoost has implemented the whole process of return result processing, but needs to fix the following two bugs before it can be used properly.
1. Type conversion error
Dart method onPageResult resultData parameter is Map
, The data type that is being parsed through PlatformChannel is Map
, so a conversion error will be reported, and the console will print this sentence
. E/FlutterBoost#: onNativePageResult call error
Copy the code
To solve this problem, change the resultData parameter type Map
from onPageResult to Map
.
2. The key corresponding to the callback method is incorrect
For problem 1, the above error will not be reported after modifying the source code of Flutter_Boost, but the callback method that receives the result still cannot go. Another bug is found after checking, when we open the page through the openPage method, The result is to store the callback in the setPageResultHandler method of the PageResultMediator class in a Map
object _Handlers, The PageResultMediator class is implemented as follows
typedef void PageResultHandler(String key , Map<dynamic,dynamic> result);
typedef VoidCallback = void Function();
class PageResultMediator{
Map<String,PageResultHandler> _handlers = Map();
void onPageResult(String key , Map<dynamic,dynamic> resultData){
if(key == null) return;
Logger.log("did receive page result $resultData for page key $key");
if(_handlers.containsKey(key)){
_handlers[key](key,resultData);
_handlers.remove(key);
}
}
VoidCallback setPageResultHandler(String key, PageResultHandler handler){
if(key == null || handler == null) return() {}; _handlers[key] = handler;return(){ _handlers.remove(key); }; }}Copy the code
The Map key is we jump in the corresponding to the url of the page, or in the code above flutterbus: / / nativeSecondPage, After processing the returned data in the Native page, the key passed in through the PlatformChannel method is the uniqueId corresponding to the Native container. The specific code is as follows
Therefore, no previous callback method can be found in a Flutter based on this uniqueId, so the callback function does not go to. Therefore, the source code of Flutter_Boost was simply modified by placing the url of the previous page in the Result parameter of the onResult method and then taking it out. The modified code is as follows
This code block is located in the ContainerRecord.java class
@Override
public void onResult(Map Result) {
Map result = (Map) Result.get("result");
String key = result.get("currentUrl").toString();
NavigationService.onNativePageResult(
genResult("onNativePageResult"),
mUniqueId,
key,
Result,
mContainer.getContainerParams()
);
}
Copy the code
Repair Plan 2
This scheme adopts the return result processing method implemented in FlutterBoost, instead of using the original jump page to get results, ordinary Activity jump is adopted when the jump page, namely
context.startActivity(new Intent(context, NativePageActivity.class));
Copy the code
The scheme FlutterBoost also has corresponding implementation, but there are still two bugs.
1. Type conversion error
This problem is the same as problem 1 in plan 1. Please refer to Plan 1 for modification.
2. The needResult parameter cannot be obtained at the native end
By reading the source code of FlutterBoost, we found that if the resultHandler parameter was passed when the Flutter end jumped to the page, a parameter with needResult of true would be added to the params passed to the native layer. However, when the native layer processes the request to open the page, it will first determine whether the result data is needed, as shown in the following code block
This code is in the FlutterBoostplugin.java class
public static void openPage(Context context, String url, final Map params, int requestCode) {
...
//Handling page result.
if (needResult(params)){
sInstance.mMediator.setHandler(url, new PageResultHandler() {
@Override
public void onResult(String key, Map resultData) {
NavigationService.onNativePageResult(new MessageResult<Boolean>() {
...
},"no use",key,resultData,params); }}); } sInstance.mPlatform.startActivity(ctx, concatUrl(url, params), requestCode); } private Boolean needResult(Map params){if(params == null) return false;
final String key = "needResult";
if(params.containsKey(key)){
if(params.get(key) instanceof Boolean){
return(Boolean) params.get(key); }}return false;
}
Copy the code
In the if statement of the method, the needResult method is used to check whether the Mediator contains the needResult parameter and its value is true. If true, the result callback is cached into the mMediator object, but the needResult parameter is not retrieved. Because params passed into Flutter have been processed before the openPage call as follows
The method is in the NavigationService_OpenPage.java class
private boolean onCall(MessageResult<Boolean> result,String pageName,Map params,Boolean animated){
Map pageParams = null;
int requestCode = 0;
if(params ! = null && params.get("query") != null) {
pageParams = (Map)params.get("query");
}
if(params ! = null && params.get("requestCode") != null) {
requestCode = (int)params.get("requestCode");
}
FlutterBoostPlugin.openPage(null,pageName,pageParams,requestCode);
result.success(true);
return true;
}
Copy the code
PageParams fetches query from params and ignores needResult. PageParams can’t find needResult from Params.
If the problem is found, we simply add a Boolean needResult to the openPage method. In the onCall method, we check whether the needResult parameter exists, and if it is true, we pass it to openPage.
The problem is fixed. If the jumped Native page returns the result to the Flutter page, just do the following when the Native page returns
Map map = new HashMap();
map.put("value"."bruce");
FlutterBoostPlugin.onPageResult(PageRouter.NATIVE_PAGE_URL, map);
Copy the code
After the bugs of the above two solutions are fixed, the function of the Flutter page to jump to the Native page and get the return value can be used normally. We hope that the Idle fish team can fix this problem in time.
As for the function of the Native page to jump to the Flutter page and return the result data, Flutter_Boost has not been implemented at present. Through reading the source code, we found relevant codes, but they are not perfect yet. We also expect the Xianyu team to improve this function quickly, because page jump to return data is a scene we often come across.
Write in the last
These are the steps to integrate Flutter_Boost into the existing Android native project. Overall, the integration and use of Flutter_Boost is relatively simple, and the Xianyu team has avoided the intrusion of the original code during the integration as much as possible. Due to the limited space, the code posted in this paper is not very perfect. If you need a demo, you can send a message on wechat public account to obtain it.
Follow the “Flutter Programming Guide” wechat official account to reply to “Widgets”, “Dart”, “storage” and “plug-ins” for more accurate information. You can also reply to “Mixed development” to get more selected articles on the mixed development practices of Alibaba, Tencent and other big companies in China.