Commit 83982112 authored by Yacumima's avatar Yacumima

use embedding engine

parent f4427cf1
......@@ -24,71 +24,72 @@
package com.taobao.idlefish.flutterboost;
import android.app.Activity;
import android.content.Context;
import android.os.Looper;
import android.support.annotation.NonNull;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterViewProvider;
import com.taobao.idlefish.flutterboost.interfaces.IPlatform;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterEngineProvider;
public class FlutterViewProvider implements IFlutterViewProvider {
import io.flutter.app.FlutterPluginRegistry;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.platform.PlatformViewRegistry;
import io.flutter.view.FlutterMain;
import io.flutter.view.FlutterNativeView;
import io.flutter.view.FlutterView;
import io.flutter.view.TextureRegistry;
private final IPlatform mPlatform;
public class BoostEngineProvider implements IFlutterEngineProvider {
private BoostFlutterNativeView mFlutterNativeView = null;
private BoostFlutterView mFlutterView = null;
FlutterViewProvider(IPlatform platform){
mPlatform = platform;
}
static final BoostEngineProvider sInstance = new BoostEngineProvider();
private BoostEngine mEngine = null;
private BoostEngineProvider() {
}
@Override
public BoostFlutterView createFlutterView(IFlutterViewContainer container) {
Activity activity = mPlatform.getMainActivity();
public FlutterEngine createEngine(Context context) {
if(activity == null) {
Debuger.log("create Flutter View not with MainActivity");
activity = container.getActivity();
}
Utils.assertCallOnMainThread();
if (mFlutterView == null) {
mFlutterView = new BoostFlutterView(activity, null, createFlutterNativeView(container));
}
return mFlutterView;
}
if (mEngine == null) {
FlutterShellArgs flutterShellArgs = new FlutterShellArgs(new String[0]);
FlutterMain.ensureInitializationComplete(
context.getApplicationContext(), flutterShellArgs.toArray());
@Override
public BoostFlutterNativeView createFlutterNativeView(IFlutterViewContainer container) {
if (mFlutterNativeView == null) {
mFlutterNativeView = new BoostFlutterNativeView(container.getActivity().getApplicationContext());
mEngine = new BoostEngine(context.getApplicationContext());
mEngine.startRun();
}
return mFlutterNativeView;
return mEngine;
}
@Override
public BoostFlutterView tryGetFlutterView() {
return mFlutterView;
public FlutterEngine tryGetEngine() {
return mEngine;
}
@Override
public void stopFlutterView() {
final BoostFlutterView view = mFlutterView;
if(view != null) {
view.boostStop();
}
public static class BoostEngine extends FlutterEngine {
final Context mContext;
public BoostEngine(@NonNull Context context) {
super(context);
mContext = context;
}
public void startRun() {
if (!getDartExecutor().isExecutingDart()) {
getNavigationChannel().setInitialRoute("/");
@Override
public void reset() {
if(mFlutterNativeView != null) {
mFlutterNativeView.boostDestroy();
mFlutterNativeView = null;
DartExecutor.DartEntrypoint entryPoint = new DartExecutor.DartEntrypoint(
mContext.getResources().getAssets(),
FlutterMain.findAppBundlePath(mContext),
"main");
getDartExecutor().executeDartEntrypoint(entryPoint);
}
if(mFlutterView != null) {
mFlutterView.boostDestroy();
mFlutterView = null;
}
}
}
package com.taobao.idlefish.flutterboost;
import android.app.Activity;
import android.content.Context;
import io.flutter.app.FlutterPluginRegistry;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.platform.PlatformViewRegistry;
import io.flutter.view.FlutterView;
import io.flutter.view.TextureRegistry;
public class BoostPluginRegistry extends FlutterPluginRegistry {
private final FlutterEngine mEngine;
private final Activity mActivity;
public BoostPluginRegistry(FlutterEngine engine, Activity activity) {
super(engine, activity);
mEngine = engine;
mActivity = activity;
}
public PluginRegistry.Registrar registrarFor(String pluginKey) {
return new BoostRegistrar(mActivity,mEngine,super.registrarFor(pluginKey));
}
public static class BoostRegistrar implements PluginRegistry.Registrar {
private final PluginRegistry.Registrar mRegistrar;
private final FlutterEngine mEngine;
private final Activity mActivity;
BoostRegistrar(Activity activity, FlutterEngine engine, PluginRegistry.Registrar registrar) {
mRegistrar = registrar;
mEngine = engine;
mActivity = activity;
}
@Override
public Activity activity() {
return mActivity;
}
@Override
public Context context() {
return mRegistrar.context();
}
@Override
public Context activeContext() {
return mRegistrar.activeContext();
}
@Override
public BinaryMessenger messenger() {
return mEngine.getDartExecutor();
}
@Override
public TextureRegistry textures() {
return mEngine.getRenderer();
}
@Override
public PlatformViewRegistry platformViewRegistry() {
return mRegistrar.platformViewRegistry();
}
@Override
public FlutterView view() {
throw new RuntimeException("should not use!!!");
}
@Override
public String lookupKeyForAsset(String s) {
return mRegistrar.lookupKeyForAsset(s);
}
@Override
public String lookupKeyForAsset(String s, String s1) {
return mRegistrar.lookupKeyForAsset(s,s1);
}
@Override
public PluginRegistry.Registrar publish(Object o) {
return mRegistrar.publish(o);
}
@Override
public PluginRegistry.Registrar addRequestPermissionsResultListener(PluginRegistry.RequestPermissionsResultListener requestPermissionsResultListener) {
return mRegistrar.addRequestPermissionsResultListener(requestPermissionsResultListener);
}
@Override
public PluginRegistry.Registrar addActivityResultListener(PluginRegistry.ActivityResultListener activityResultListener) {
return mRegistrar.addActivityResultListener(activityResultListener);
}
@Override
public PluginRegistry.Registrar addNewIntentListener(PluginRegistry.NewIntentListener newIntentListener) {
return mRegistrar.addNewIntentListener(newIntentListener);
}
@Override
public PluginRegistry.Registrar addUserLeaveHintListener(PluginRegistry.UserLeaveHintListener userLeaveHintListener) {
return mRegistrar.addUserLeaveHintListener(userLeaveHintListener);
}
@Override
public PluginRegistry.Registrar addViewDestroyListener(PluginRegistry.ViewDestroyListener viewDestroyListener) {
return mRegistrar.addViewDestroyListener(viewDestroyListener);
}
}
}
......@@ -23,27 +23,26 @@
*/
package com.taobao.idlefish.flutterboost;
import android.os.Handler;
import android.content.Intent;
import com.taobao.idlefish.flutterboost.NavigationService.NavigationService;
import com.taobao.idlefish.flutterboost.interfaces.IContainerManager;
import com.taobao.idlefish.flutterboost.interfaces.IContainerRecord;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import java.util.HashMap;
import java.util.Map;
import fleamarket.taobao.com.xservicekit.handler.MessageResult;
public class ContainerRecord implements IContainerRecord {
private final IContainerManager mManager;
private final FlutterViewContainerManager mManager;
private final IFlutterViewContainer mContainer;
private final String mUniqueId;
private final Handler mHandler = new Handler();
private int mState = STATE_UNKNOW;
private MethodChannelProxy mProxy = new MethodChannelProxy();
public ContainerRecord(IContainerManager manager, IFlutterViewContainer container) {
ContainerRecord(FlutterViewContainerManager manager, IFlutterViewContainer container) {
mUniqueId = System.currentTimeMillis() + "-" + hashCode();
mManager = manager;
mContainer = container;
......@@ -66,54 +65,119 @@ public class ContainerRecord implements IContainerRecord {
@Override
public void onCreate() {
Utils.assertCallOnMainThread();
if(mState != STATE_UNKNOW) {
Debuger.exception("state error");
}
mState = STATE_CREATED;
mContainer.getBoostFlutterView().boostResume();
mContainer.getBoostFlutterView().onResume();
mProxy.create();
}
@Override
public void onAppear() {
Utils.assertCallOnMainThread();
if(mState != STATE_CREATED && mState != STATE_DISAPPEAR) {
Debuger.exception("state error");
}
mState = STATE_APPEAR;
mContainer.getBoostFlutterView().boostResume();
mManager.pushRecord(this);
mContainer.getBoostFlutterView().onAttach();
mProxy.appear();
}
@Override
public void onDisappear() {
mProxy.disappear();
Utils.assertCallOnMainThread();
if(mState != STATE_APPEAR) {
Debuger.exception("state error");
}
mState = STATE_DISAPPEAR;
/**
* Bug workaround:
* If current container is finishing, we should call destroy flutter page early.
*/
if(mContainer.isFinishing()) {
mHandler.post(new Runnable() {
mProxy.disappear();
mContainer.getBoostFlutterView().onDetach();
mManager.popRecord(this);
}
@Override
public void run() {
public void onDestroy() {
Utils.assertCallOnMainThread();
if(mState != STATE_DISAPPEAR) {
Debuger.exception("state error");
}
mState = STATE_DESTROYED;
mProxy.destroy();
mManager.removeRecord(this);
if(!mManager.hasContainerAppear()) {
mContainer.getBoostFlutterView().onPause();
mContainer.getBoostFlutterView().onStop();
}
});
}
@Override
public void onBackPressed() {
Utils.assertCallOnMainThread();
if(mState == STATE_UNKNOW || mState == STATE_DESTROYED) {
Debuger.exception("state error");
}
Map<String, String> map = new HashMap<>();
map.put("type", "backPressedCallback");
map.put("name", mContainer.getContainerName());
map.put("uniqueId", mUniqueId);
NavigationService.getService().emitEvent(map);
mContainer.getBoostFlutterView().onBackPressed();
}
@Override
public void onDestroy() {
mProxy.destroy();
mState = STATE_DESTROYED;
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
mContainer.getBoostFlutterView().onRequestPermissionsResult(requestCode,permissions,grantResults);
}
@Override
public void onResult(Map Result) {
NavigationService.onNativePageResult(
genResult("onNativePageResult"),
mUniqueId,
mUniqueId,
Result,
mContainer.getContainerParams()
);
public void onNewIntent(Intent intent) {
mContainer.getBoostFlutterView().onNewIntent(intent);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mContainer.getBoostFlutterView().onActivityResult(requestCode,resultCode,data);
}
@Override
public void onUserLeaveHint() {
mContainer.getBoostFlutterView().onUserLeaveHint();
}
@Override
public void onTrimMemory(int level) {
mContainer.getBoostFlutterView().onTrimMemory(level);
}
@Override
public void onLowMemory() {
mContainer.getBoostFlutterView().onLowMemory();
}
private class MethodChannelProxy {
private int mState = STATE_UNKNOW;
......
......@@ -28,16 +28,14 @@ import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import com.alibaba.fastjson.JSON;
import com.taobao.idlefish.flutterboost.NavigationService.NavigationService;
import com.taobao.idlefish.flutterboost.interfaces.IContainerRecord;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import com.taobao.idlefish.flutterboost.loader.ServiceLoader;
import com.taobao.idlefish.flutterboost.interfaces.IContainerManager;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterViewProvider;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterEngineProvider;
import com.taobao.idlefish.flutterboost.interfaces.IPlatform;
import java.io.UnsupportedEncodingException;
......@@ -50,7 +48,6 @@ import fleamarket.taobao.com.xservicekit.handler.MessageResult;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.view.FlutterView;
public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Application.ActivityLifecycleCallbacks {
......@@ -70,12 +67,12 @@ public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Appl
channel.setMethodCallHandler(sInstance);
}
public static IFlutterViewProvider viewProvider() {
public static IFlutterEngineProvider engineProvider() {
if (sInstance == null) {
throw new RuntimeException("FlutterBoostPlugin not init yet");
}
return sInstance.mViewProvider;
return BoostEngineProvider.sInstance;
}
public static IContainerManager containerManager() {
......@@ -104,7 +101,6 @@ public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Appl
private final IPlatform mPlatform;
private final IContainerManager mManager;
private final IFlutterViewProvider mViewProvider;
private final PageResultMediator mMediator;
......@@ -112,7 +108,6 @@ public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Appl
private FlutterBoostPlugin(IPlatform platform) {
mPlatform = platform;
mViewProvider = new FlutterViewProvider(platform);
mManager = new FlutterViewContainerManager();
mMediator = new PageResultMediator();
}
......@@ -140,10 +135,6 @@ public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Appl
ctx = currentActivity();
}
if (ctx == null) {
ctx = sInstance.mPlatform.getMainActivity();
}
if (ctx == null) {
ctx = sInstance.mPlatform.getApplication();
}
......@@ -248,7 +239,7 @@ public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Appl
if (mCurrentActiveActivity == null) {
Debuger.log("Application entry foreground");
if (mViewProvider.tryGetFlutterView() != null) {
if (BoostEngineProvider.sInstance.tryGetEngine() != null) {
Map<String, String> map = new HashMap<>();
map.put("type", "foreground");
NavigationService.getService().emitEvent(map);
......@@ -272,7 +263,7 @@ public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Appl
if (mCurrentActiveActivity == activity) {
Debuger.log("Application entry background");
if (mViewProvider.tryGetFlutterView() != null) {
if (BoostEngineProvider.sInstance.tryGetEngine() != null) {
Map<String, String> map = new HashMap<>();
map.put("type", "background");
NavigationService.getService().emitEvent(map);
......@@ -291,7 +282,7 @@ public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Appl
if (mCurrentActiveActivity == activity) {
Debuger.log("Application entry background");
if (mViewProvider.tryGetFlutterView() != null) {
if (BoostEngineProvider.sInstance.tryGetEngine() != null) {
Map<String, String> map = new HashMap<>();
map.put("type", "background");
NavigationService.getService().emitEvent(map);
......@@ -321,15 +312,15 @@ public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Appl
activity.setResult(Activity.RESULT_OK, intent);
}
public static void onBoostResult(IFlutterViewContainer container, int requestCode, int resultCode, Intent intent) {
Map map = new HashMap();
if (intent != null) {
map.put("result", intent.getSerializableExtra(IFlutterViewContainer.RESULT_KEY));
}
map.put("requestCode", requestCode);
map.put("responseCode", resultCode);
containerManager().onContainerResult(container, map);
}
// public static void onBoostResult(IFlutterViewContainer container, int requestCode, int resultCode, Intent intent) {
// Map map = new HashMap();
// if (intent != null) {
// map.put("result", intent.getSerializableExtra(IFlutterViewContainer.RESULT_KEY));
// }
// map.put("requestCode", requestCode);
// map.put("responseCode", resultCode);
// containerManager().onContainerResult(container, map);
// }
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Alibaba Group
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.taobao.idlefish.flutterboost;
import com.taobao.idlefish.flutterboost.interfaces.IContainerRecord;
import com.taobao.idlefish.flutterboost.interfaces.IContainerManager;
public class Instrument {
private final IContainerManager mManager;
Instrument(IContainerManager manager) {
mManager = manager;
}
public void performCreate(IContainerRecord record) {
final int currentState = record.getState();
if (currentState != IContainerRecord.STATE_UNKNOW) {
Debuger.exception("performCreate state error, current state:" + currentState);
return;
}
record.onCreate();
}
public void performAppear(IContainerRecord record) {
final int currentState = record.getState();
if (currentState != IContainerRecord.STATE_CREATED
&& currentState != IContainerRecord.STATE_DISAPPEAR) {
Debuger.exception("performAppear state error, current state:" + currentState);
return;
}
record.onAppear();
}
public void performDisappear(IContainerRecord record) {
final int currentState = record.getState();
if (currentState != IContainerRecord.STATE_APPEAR) {
Debuger.exception("performDisappear state error , current state:" + currentState);
return;
}
record.onDisappear();
}
public void performDestroy(IContainerRecord record) {
final int currentState = record.getState();
if (currentState != IContainerRecord.STATE_DISAPPEAR) {
Debuger.exception("performDestroy state error, current state:" + currentState);
}
record.onDestroy();
}
}
......@@ -39,7 +39,7 @@
private boolean onCall(MessageResult<Boolean> result,String uniqueId,String pageName,Map params,Boolean animated){
FlutterBoostPlugin.containerManager().destroyContainerRecord(pageName,uniqueId);
FlutterBoostPlugin.containerManager().closeContainer(uniqueId,null);
result.success(true);
return true;
}
......
......@@ -38,8 +38,8 @@
private boolean onCall(MessageResult<Boolean> result,String uniqueId,String key,Map resultData,Map params){
FlutterBoostPlugin.containerManager().setContainerResult(uniqueId,resultData);
result.success(true);
// FlutterBoostPlugin.containerManager().setContainerResult(uniqueId,resultData);
// result.success(true);
return true;
}
......
......@@ -27,6 +27,7 @@
import com.taobao.idlefish.flutterboost.FlutterBoostPlugin;
import com.taobao.idlefish.flutterboost.FlutterViewContainerManager;
import com.taobao.idlefish.flutterboost.interfaces.IContainerRecord;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import java.util.ArrayList;
import java.util.HashMap;
......@@ -47,7 +48,7 @@
.containerManager().getCurrentTopRecord();
if(record == null) {
record = FlutterBoostPlugin.containerManager().getLastRecord();
record = FlutterBoostPlugin.containerManager().getLastGenerateRecord();
}
pageInfo.put("name",record.getContainer().getContainerName());
......
......@@ -25,9 +25,16 @@ package com.taobao.idlefish.flutterboost;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Looper;
public class Utils {
public static void assertCallOnMainThread() {
if (Looper.myLooper() != Looper.getMainLooper()) {
Debuger.exception("must call method on main thread");
}
}
public static boolean checkImageValid(final Bitmap bitmap) {
if (null == bitmap) {
return false;
......
......@@ -24,161 +24,123 @@
package com.taobao.idlefish.flutterboost.containers;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import com.taobao.idlefish.flutterboost.BoostFlutterView;
import com.taobao.idlefish.flutterboost.FlutterBoostPlugin;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import com.taobao.idlefish.flutterboost.interfaces.IOperateSyncer;
import java.util.HashMap;
import java.util.Map;
import io.flutter.app.FlutterActivity;
import io.flutter.view.FlutterNativeView;
import io.flutter.view.FlutterView;
import io.flutter.embedding.android.FlutterView;
abstract public class BoostFlutterActivity extends FlutterActivity implements IFlutterViewContainer {
public abstract class BoostFlutterActivity extends Activity implements IFlutterViewContainer {
private FlutterContent mFlutterContent;
private BoostFlutterView mFlutterView;
private IOperateSyncer mSyncer;
@Override
protected void onCreate(Bundle savedInstanceState) {
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
mFlutterContent = new FlutterContent(this);
BoostFlutterView.Builder builder = new BoostFlutterView.Builder(this);
mFlutterView = builder.renderMode(FlutterView.RenderMode.texture)
.transparencyMode(FlutterView.TransparencyMode.opaque)
.build();
setContentView(mFlutterContent);
setContentView(mFlutterView);
FlutterBoostPlugin.containerManager().onContainerCreate(this);
onRegisterPlugins(this);
mSyncer = FlutterBoostPlugin.containerManager().generateSyncer(this);
mSyncer.onCreate();
}
@Override
protected void onPostResume() {
super.onPostResume();
FlutterBoostPlugin.containerManager().onContainerAppear(BoostFlutterActivity.this);
mFlutterContent.attachFlutterView(getBoostFlutterView());
protected void onResume() {
super.onResume();
mSyncer.onAppear();
}
@Override
protected void onPause() {
mFlutterContent.detachFlutterView();
FlutterBoostPlugin.containerManager().onContainerDisappear(this);
mSyncer.onDisappear();
super.onPause();
}
@Override
protected void onDestroy() {
FlutterBoostPlugin.containerManager().onContainerDestroy(this);
mFlutterContent.destroy();
mSyncer.onDestroy();
super.onDestroy();
}
public FlutterView createFlutterView(Context context) {
return FlutterBoostPlugin.viewProvider().createFlutterView(this);
}
@Override
public FlutterNativeView createFlutterNativeView() {
return FlutterBoostPlugin.viewProvider().createFlutterNativeView(this);
public void onBackPressed() {
super.onBackPressed();
mSyncer.onBackPressed();
}
@Override
public boolean retainFlutterNativeView() {
return true;
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
mSyncer.onNewIntent(intent);
}
protected View createSplashScreenView() {
FrameLayout frameLayout = new FrameLayout(this);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER;
frameLayout.addView(new ProgressBar(this), params);
return frameLayout;
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mSyncer.onActivityResult(requestCode,resultCode,data);
}
protected View createFlutterInitCoverView() {
View initCover = new View(this);
initCover.setBackgroundColor(Color.WHITE);
return initCover;
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
mSyncer.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
public void onContainerShown() {
mFlutterContent.onContainerShown();
public void onTrimMemory(int level) {
super.onTrimMemory(level);
mSyncer.onTrimMemory(level);
}
@Override
public void onContainerHidden() {
mFlutterContent.onContainerHidden();
public void onLowMemory() {
super.onLowMemory();
mSyncer.onLowMemory();
}
@Override
public void onBackPressed() {
FlutterBoostPlugin.containerManager().onBackPressed(this);
protected void onUserLeaveHint() {
super.onUserLeaveHint();
mSyncer.onUserLeaveHint();
}
@Override
public Activity getActivity() {
public Activity getContextActivity() {
return this;
}
@Override
public BoostFlutterView getBoostFlutterView() {
return (BoostFlutterView) getFlutterView();
return mFlutterView;
}
@Override
public void destroyContainer() {
public void finishContainer() {
finish();
}
@Override
public abstract String getContainerName();
public void onContainerShown() {}
@Override
public abstract Map getContainerParams();
public void onContainerHidden() {}
@Override
public void setBoostResult(HashMap result) {
FlutterBoostPlugin.setBoostResult(this, result);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
FlutterBoostPlugin.onBoostResult(this,requestCode,resultCode,data);
}
class FlutterContent extends FlutterViewStub {
public FlutterContent(Context context) {
super(context);
}
@Override
public View createFlutterInitCoverView() {
return BoostFlutterActivity.this.createFlutterInitCoverView();
}
@Override
public BoostFlutterView getBoostFlutterView() {
return BoostFlutterActivity.this.getBoostFlutterView();
}
@Override
public View createSplashScreenView() {
return BoostFlutterActivity.this.createSplashScreenView();
}
Intent data = new Intent();
data.putExtra(RESULT_KEY,result);
setResult(RESULT_OK,data);
}
}
......@@ -23,138 +23,120 @@
*/
package com.taobao.idlefish.flutterboost.containers;
import android.content.Context;
import android.graphics.Color;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import com.taobao.idlefish.flutterboost.BoostFlutterView;
import com.taobao.idlefish.flutterboost.Debuger;
import com.taobao.idlefish.flutterboost.FlutterBoostPlugin;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import com.taobao.idlefish.flutterboost.interfaces.IOperateSyncer;
import java.util.HashMap;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.embedding.android.FlutterView;
abstract public class BoostFlutterFragment extends Fragment implements IFlutterViewContainer {
FlutterContent mContent;
PluginRegistry mRegistry;
boolean resumed = false;
private BoostFlutterView mFlutterView;
private IOperateSyncer mSyncer;
@Nullable
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
BoostFlutterView.Builder builder = new BoostFlutterView.Builder(getContextActivity());
mFlutterView = builder.renderMode(FlutterView.RenderMode.texture)
.transparencyMode(FlutterView.TransparencyMode.opaque)
.build();
mRegistry = FlutterBoostPlugin.containerManager().onContainerCreate(this);
onRegisterPlugins(mRegistry);
}
mSyncer = FlutterBoostPlugin.containerManager().generateSyncer(this);
mSyncer.onCreate();
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
mContent = new FlutterContent(getActivity());
return mContent;
return mFlutterView;
}
@Override
public void onResume() {
super.onResume();
if (!resumed) {
resumed = true;
FlutterBoostPlugin.containerManager().onContainerAppear(this);
mContent.attachFlutterView(getBoostFlutterView());
Log.e("FlutterBoost", "FlutterMenuFragment resume");
}
mSyncer.onAppear();
}
@Override
public void onPause() {
mSyncer.onDisappear();
super.onPause();
if (resumed) {
resumed = false;
mContent.snapshot();
FlutterBoostPlugin.containerManager().onContainerDisappear(this);
Log.e("FlutterBoost", "FlutterMenuFragment stop");
}
}
@Override
public void onDestroy() {
mSyncer.onDestroy();
super.onDestroy();
if (mContent != null) {
mContent.destroy();
}
FlutterBoostPlugin.containerManager().onContainerDestroy(this);
}
@Override
public void onContainerShown() {
mContent.onContainerShown();
public void onBackPressed() {
mSyncer.onBackPressed();
}
@Override
public void onContainerHidden() {
mContent.onContainerHidden();
public void onNewIntent(Intent intent) {
mSyncer.onNewIntent(intent);
}
@Override
public BoostFlutterView getBoostFlutterView() {
return FlutterBoostPlugin.viewProvider().createFlutterView(this);
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mSyncer.onActivityResult(requestCode,resultCode,data);
}
@Override
public boolean isFinishing() {
return getActivity().isFinishing();
}
protected View createSplashScreenView() {
FrameLayout layout = new FrameLayout(getContext());
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER;
layout.addView(new ProgressBar(getContext()),params);
return layout;
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
mSyncer.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
protected View createFlutterInitCoverView() {
View initCover = new View(getActivity());
initCover.setBackgroundColor(Color.WHITE);
return initCover;
public void onTrimMemory(int level) {
mSyncer.onTrimMemory(level);
}
@Override
public void setBoostResult(HashMap result) {
public void onLowMemory() {
super.onLowMemory();
mSyncer.onLowMemory();
}
class FlutterContent extends FlutterViewStub {
public FlutterContent(Context context) {
super(context);
public void onUserLeaveHint() {
mSyncer.onUserLeaveHint();
}
@Override
public View createFlutterInitCoverView() {
return BoostFlutterFragment.this.createFlutterInitCoverView();
public Activity getContextActivity() {
return getActivity();
}
@Override
public BoostFlutterView getBoostFlutterView() {
return BoostFlutterFragment.this.getBoostFlutterView();
return mFlutterView;
}
@Override
public View createSplashScreenView() {
return BoostFlutterFragment.this.createSplashScreenView();
public void finishContainer() {
getActivity().finish();
}
@Override
public void onContainerShown() {}
@Override
public void onContainerHidden() {}
@Override
public void setBoostResult(HashMap result) {
Intent data = new Intent();
data.putExtra(RESULT_KEY,result);
getActivity().setResult(Activity.RESULT_OK,data);
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Alibaba Group
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.taobao.idlefish.flutterboost.containers;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import com.taobao.idlefish.flutterboost.BoostFlutterView;
import com.taobao.idlefish.flutterboost.Debuger;
abstract public class FlutterViewStub extends FrameLayout {
public static final Handler sHandler = new ProcessHandler(Looper.getMainLooper());
protected Bitmap mBitmap;
protected ImageView mSnapshot;
protected FrameLayout mStub;
protected View mCover;
protected View mSplashScreenView;
public FlutterViewStub(Context context) {
super(context);
mStub = new FrameLayout(context);
mStub.setBackgroundColor(Color.WHITE);
addView(mStub, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mSnapshot = new ImageView(context);
mSnapshot.setScaleType(ImageView.ScaleType.FIT_CENTER);
mSnapshot.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mCover = createFlutterInitCoverView();
addView(mCover, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
final BoostFlutterView flutterView = getBoostFlutterView();
if (!flutterView.firstFrameCalled()) {
mSplashScreenView = createSplashScreenView();
addView(mSplashScreenView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
}
public void onContainerShown() {
Debuger.log("onContainerShown");
sHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (mSplashScreenView != null) {
FlutterViewStub.this.removeView(mSplashScreenView);
mSplashScreenView = null;
}
if (mCover != null) {
FlutterViewStub.this.removeView(mCover);
}
if (mSnapshot.getParent() == FlutterViewStub.this) {
FlutterViewStub.this.removeView(mSnapshot);
mSnapshot.setImageBitmap(null);
if (mBitmap != null && !mBitmap.isRecycled()) {
mBitmap.recycle();
mBitmap = null;
}
}
getBoostFlutterView().scheduleFrame();
getBoostFlutterView().requestFocus();
getBoostFlutterView().invalidate();
}
}, 167);
}
public void onContainerHidden() {
//Debuger.log("onContainerHidden");
}
public void snapshot() {
if (mStub.getChildCount() <= 0) return;
if (mSnapshot.getParent() != null) return;
BoostFlutterView flutterView = (BoostFlutterView) mStub.getChildAt(0);
mBitmap = flutterView.getBitmap();
if (mBitmap != null && !mBitmap.isRecycled()) {
mSnapshot.setImageBitmap(mBitmap);
addView(mSnapshot);
}
}
public void attachFlutterView(final BoostFlutterView flutterView) {
if (flutterView.getParent() != mStub) {
sHandler.removeMessages(ProcessHandler.MSG_DETACH);
Debuger.log("attachFlutterView");
if (flutterView.getParent() != null) {
((ViewGroup) flutterView.getParent()).removeView(flutterView);
}
mStub.addView(flutterView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
}
public void detachFlutterView() {
if (mStub.getChildCount() <= 0) return;
final BoostFlutterView flutterView = (BoostFlutterView) mStub.getChildAt(0);
if (flutterView == null) return;
if (mSnapshot.getParent() == null) {
mBitmap = flutterView.getBitmap();
if (mBitmap != null && !mBitmap.isRecycled()) {
mSnapshot.setImageBitmap(mBitmap);
Debuger.log("snapshot view");
addView(mSnapshot);
}
}
Message msg = new Message();
msg.what = ProcessHandler.MSG_DETACH;
msg.obj = new Runnable() {
@Override
public void run() {
if (flutterView.getParent() != null && flutterView.getParent() == mStub) {
Debuger.log("detachFlutterView");
mStub.removeView(flutterView);
}
}
};
sHandler.sendMessageDelayed(msg,18);
}
public void destroy() {
removeAllViews();
mSnapshot.setImageBitmap(null);
if (mBitmap != null && !mBitmap.isRecycled()) {
mBitmap.recycle();
mBitmap = null;
}
}
protected View createFlutterInitCoverView() {
View initCover = new View(getContext());
initCover.setBackgroundColor(Color.WHITE);
return initCover;
}
protected View createSplashScreenView() {
FrameLayout layout = new FrameLayout(getContext());
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER;
layout.addView(new ProgressBar(getContext()),params);
return layout;
}
abstract protected BoostFlutterView getBoostFlutterView();
public static class ProcessHandler extends Handler {
static final int MSG_DETACH = 175101;
ProcessHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.obj instanceof Runnable) {
Runnable run = (Runnable) msg.obj;
run.run();
}
}
}
}
......@@ -23,106 +23,21 @@
*/
package com.taobao.idlefish.flutterboost.interfaces;
import android.app.Activity;
import java.util.Map;
import io.flutter.plugin.common.PluginRegistry;
public interface IContainerManager {
/**
* call by native side when container create
* @param container
* @return
*/
PluginRegistry onContainerCreate(IFlutterViewContainer container);
IOperateSyncer generateSyncer(IFlutterViewContainer container);
/**
* call by native side when container appear
* @param container
* @return
*/
void onContainerAppear(IFlutterViewContainer container);
/**
* call by native side when container disappear
* @param container
* @return
*/
void onContainerDisappear(IFlutterViewContainer container);
IFlutterViewContainer closeContainer(String uniqueId,Map<String,Object> result);
/**
* call by native side when container destroy
* @param container
* @return
*/
void onContainerDestroy(IFlutterViewContainer container);
/**
* call by native side when back key pressed
* @param container
* @return
*/
void onBackPressed(IFlutterViewContainer container);
/**
* call by flutter side when need destroy container
* @param name
* @param uq
*/
void destroyContainerRecord(String name,String uq);
/**
* call by native side when container handle a result (onActivityResult)
* @param container
* @param result
*/
void onContainerResult(IFlutterViewContainer container,Map result);
/**
* call by flutter side when flutter want set a result for request (setResult)
* @param uniqueId
* @param result
*/
void setContainerResult(String uniqueId,Map result);
/**
* get current interactive container
* @return
*/
IContainerRecord getCurrentTopRecord();
/**
* get last created container
* @return
*/
IContainerRecord getLastRecord();
IContainerRecord getLastGenerateRecord();
/**
* find a container
* @param uniqueId
* @return
*/
IFlutterViewContainer findContainerById(String uniqueId);
/**
* call by flutter side when a container shown or hidden
* @param old
* @param now
*/
void onShownContainerChanged(String old,String now);
void onShownContainerChanged(String oldUniqueId,String nowUniqueId);
/**
* is any container appear now
* @return
*/
boolean hasContainerAppear();
/**
* no use
*/
void reset();
}
......@@ -23,12 +23,10 @@
*/
package com.taobao.idlefish.flutterboost.interfaces;
import java.util.Map;
/**
* a container record, which use map a flutter page
*/
public interface IContainerRecord {
public interface IContainerRecord extends IOperateSyncer{
int STATE_UNKNOW = 0;
int STATE_CREATED = 1;
int STATE_APPEAR = 2;
......@@ -38,9 +36,4 @@ public interface IContainerRecord {
String uniqueId();
IFlutterViewContainer getContainer();
int getState();
void onCreate();
void onAppear();
void onDisappear();
void onDestroy();
void onResult(Map Result);
}
......@@ -21,32 +21,31 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.taobao.idlefish.flutterboost;
package com.taobao.idlefish.flutterboost.interfaces;
import android.content.Context;
import io.flutter.view.FlutterNativeView;
import com.taobao.idlefish.flutterboost.BoostFlutterView;
public class BoostFlutterNativeView extends FlutterNativeView {
import io.flutter.embedding.engine.FlutterEngine;
public BoostFlutterNativeView(Context context) {
super(context);
}
/**
* a flutter view provider
*/
public interface IFlutterEngineProvider {
public void detachFromFlutterView() {
//do nothing...
}
/**
* create flutter engine, we just hold a single instance now
* @param context
* @return
*/
FlutterEngine createEngine(Context context);
public void detach() {
//do nothing...
}
/**
* may return null
* @return
*/
FlutterEngine tryGetEngine();
@Override
public void destroy() {
//do nothing...
}
public void boostDestroy() {
super.destroy();
}
}
......@@ -37,7 +37,7 @@ import io.flutter.plugin.common.PluginRegistry;
public interface IFlutterViewContainer {
String RESULT_KEY = "_flutter_result_";
Activity getActivity();
Activity getContextActivity();
/**
* provide a flutter view
......@@ -48,7 +48,7 @@ public interface IFlutterViewContainer {
/**
* call to destroy the container
*/
void destroyContainer();
void finishContainer();
/**
* container name
......@@ -72,21 +72,9 @@ public interface IFlutterViewContainer {
*/
void onContainerHidden();
/**
* is container finishing
* @return
*/
boolean isFinishing();
/**
* call by flutter side to set result
* @param result
*/
void setBoostResult(HashMap result);
/**
* register flutter plugins
* @param registry
*/
void onRegisterPlugins(PluginRegistry registry);
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Alibaba Group
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.taobao.idlefish.flutterboost.interfaces;
import com.taobao.idlefish.flutterboost.BoostFlutterNativeView;
import com.taobao.idlefish.flutterboost.BoostFlutterView;
/**
* a flutter view provider
*/
public interface IFlutterViewProvider {
/**
* create flutter view, we just hold a single instance now
* @param container
* @return
*/
BoostFlutterView createFlutterView(IFlutterViewContainer container);
/**
* single instance also
* @param container
* @return
*/
BoostFlutterNativeView createFlutterNativeView(IFlutterViewContainer container);
/**
* may return null
* @return
*/
BoostFlutterView tryGetFlutterView();
/**
* release flutter view
*/
void stopFlutterView();
/**
* reset all refrence
*/
void reset();
}
package com.taobao.idlefish.flutterboost.interfaces;
import android.content.Intent;
public interface IOperateSyncer {
void onCreate();
void onAppear();
void onDisappear();
void onDestroy();
void onBackPressed();
void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults);
void onNewIntent(Intent intent);
void onActivityResult(int requestCode, int resultCode, Intent data);
void onUserLeaveHint();
void onTrimMemory(int level);
void onLowMemory();
}
......@@ -29,6 +29,8 @@ import android.content.Context;
import java.util.Map;
import io.flutter.plugin.common.PluginRegistry;
/**
* APIs that platform(Android App) must provide
*/
......@@ -41,10 +43,10 @@ public interface IPlatform {
Application getApplication();
/**
* get main activity, which must always exist at the bottom of task stack.
* register plugins
* @return
*/
Activity getMainActivity();
void onRegisterPlugins(PluginRegistry registry);
/**
* debug or not
......
......@@ -28,11 +28,6 @@ public class FlutterFragment extends BoostFlutterFragment {
super.setArguments(args);
}
@Override
public void onRegisterPlugins(PluginRegistry registry) {
GeneratedPluginRegistrant.registerWith(registry);
}
@Override
public String getContainerName() {
return "flutterFragment";
......@@ -45,11 +40,6 @@ public class FlutterFragment extends BoostFlutterFragment {
return params;
}
@Override
public void destroyContainer() {
}
public static FlutterFragment instance(String tag){
FlutterFragment fragment = new FlutterFragment();
fragment.setTabTag(tag);
......
......@@ -10,11 +10,6 @@ import io.flutter.plugins.GeneratedPluginRegistrant;
public class FlutterPageActivity extends BoostFlutterActivity {
@Override
public void onRegisterPlugins(PluginRegistry registry) {
GeneratedPluginRegistrant.registerWith(registry);
}
/**
* 该方法返回当前Activity在Flutter层对应的name,
* 混合栈将会在flutter层根据这个名字,在注册的Route表中查找对应的Widget
......
package com.taobao.idlefish.flutterboostexample;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import com.taobao.idlefish.flutterboost.Debuger;
import com.taobao.idlefish.flutterboost.FlutterBoostPlugin;
......@@ -13,6 +10,8 @@ import com.taobao.idlefish.flutterboost.interfaces.IPlatform;
import java.util.Map;
import io.flutter.app.FlutterApplication;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MyApplication extends FlutterApplication {
@Override
......@@ -25,17 +24,9 @@ public class MyApplication extends FlutterApplication {
return MyApplication.this;
}
/**
* 获取应用入口的Activity,这个Activity在应用交互期间应该是一直在栈底的
* @return
*/
@Override
public Activity getMainActivity() {
if (MainActivity.sRef != null) {
return MainActivity.sRef.get();
}
return null;
public void onRegisterPlugins(PluginRegistry registry) {
GeneratedPluginRegistrant.registerWith(registry);
}
@Override
......
Future<Map<String,dynamic>> open(String url,{Map<String,dynamic> urlParams,Map<String,dynamic> exts}){
}
void close(String id,{Map<String,dynamic> result,Map<String,dynamic> exts}){
}
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment