- flutter_boost版本(1.17.1, 1.22.4)
在使用flutter_boost進行混合開發時,有時候需要在關閉頁面時向前一頁面回傳數據,原生端處理時一般是通過startActivityForResult啟動頁面,然后在頁面的onActivityResult中接收回傳內容。flutter_boost提供以下回傳方式:
- Flutter->Native
flutter中打開native頁面,并從native回傳數據。常見場景例如flutter中需要選擇文件或圖片上傳時,需要打開原生頁面選擇,然后把文件路徑回傳給flutter。通過如下代碼以startActivityForResult方式啟動native頁面,native頁面關閉時setResult即可
FlutterBoost.singleton.open('url').then((result){...})
// native側setResult
Map map = new HashMap<String, String>();
map.put("a", "a");
Intent intent = getIntent().putExtra(IFlutterViewContainer.RESULT_KEY, (Serializable) map);
setResult(0, intent);
- FlutterA->FlutterB
FlutterBoost.singleton
.open('FlutterB')
.then((Map<dynamic, dynamic> value) {
// 回傳數據處理
});
// FlutterB close并回傳:
final BoostContainerSettings settings = BoostContainer.of(context).settings;
FlutterBoost.singleton
.close(settings.uniqueId, result: <String, dynamic>{'result': 'data from FlutterB'});
目前在FlutterA->FlutterB情況下,回傳數據時,發現android端在物理/系統虛擬返回鍵返回時,沒辦法調用FlutterBoost.singleton.close回傳數據。查看boost源碼,back鍵返回時處理流程大概如下:
BoostFlutterActivity中onBackPressed
->FlutterActivityAndFragmentDelegate中onBackPressed
->mSyncer.onBackPressed
// BoostFlutterActivity.java
private FlutterActivityAndFragmentDelegate delegate;
public void onBackPressed() {
this.delegate.onBackPressed();
}
// FlutterActivityAndFragmentDelegate.java
protected IOperateSyncer mSyncer;
public void onBackPressed() {
this.mSyncer.onBackPressed();
this.ensureAlive();
}
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.v("FlutterActivityAndFragmentDelegate", "Creating FlutterView.");
this.mSyncer = FlutterBoost.instance().containerManager().generateSyncer(this);
...
}
// FlutterBoost.java
static FlutterBoost sInstance = null;
private FlutterViewContainerManager mManager;
public static FlutterBoost instance() {
if (sInstance == null) {
sInstance = new FlutterBoost();
}
return sInstance;
}
public IContainerManager containerManager() {
return sInstance.mManager;
}
// FlutterViewContainerManager.java
public IOperateSyncer generateSyncer(IFlutterViewContainer container) {
Utils.assertCallOnMainThread();
ContainerRecord record = new ContainerRecord(this, container);
if (this.mRecordMap.put(container, record) != null) {
Debuger.exception("container:" + container.getContainerUrl() + " already exists!");
}
this.mRefs.add(new FlutterViewContainerManager.ContainerRef(record.uniqueId(), container));
return record;
}
// ContainerRecord.java
public void onBackPressed() {
Utils.assertCallOnMainThread();
if (this.mState == 0 || this.mState == 4) {
Debuger.exception("state error");
}
HashMap<String, String> map = new HashMap();
map.put("type", "backPressedCallback");
map.put("name", this.mContainer.getContainerUrl());
map.put("uniqueId", this.mUniqueId);
FlutterBoost.instance().channel().sendEvent("lifecycle", map);
}
查看部分源碼可見按下back鍵后,flutter_boost native側會通過channel("flutter_boost")向flutter側發送名為lifecycle的事件,類型為backPressedCallback,flutter_boost的flutter側在FlutterBoost單例初始化時,會初始化ContainerCoordinator,其中會注冊"lifecycle"的監聽。部分代碼如下:
// flutter_boost.dart
FlutterBoost() {
ContainerCoordinator(_boostChannel);
}
// container_coordinator.dart
ContainerCoordinator(BoostChannel channel) {
assert(_instance == null);
_instance = this;
channel.addEventListener("lifecycle",
(String name, Map arguments) => _onChannelEvent(arguments));
channel.addMethodHandler((MethodCall call) => _onMethodCall(call));
}
flutter_boost channel在收到事件時,會執行相關回調方法。back鍵按下后,lifecycle的回調會觸發,可見backPressedCallback類型的處理如下部分代碼所示:
Future<dynamic> _onChannelEvent(dynamic event) {
if (event is Map) {
Map map = event;
final String type = map['type'];
Logger.log('onEvent $type');
switch (type) {
//Handler back key pressed event.
case 'backPressedCallback':
{
final String id = map['uniqueId'];
FlutterBoost.containerManager
?.containerStateOf(id)
?.performBackPressed();
}
break;
...
}
}
return Future<dynamic>(() {});
}
跟進performBackPressed可見,其中backPressedHandler在state初始化時設置為maybePop,maybePop中會有頁面關閉的相關處理
void performBackPressed() {
Logger.log('performBackPressed');
backPressedHandler?.call();
}
@override
void initState() {
super.initState();
backPressedHandler = () => maybePop();
}
@override
Future<bool> maybePop<T extends Object>([T result]) async {
if(routerHistory.isEmpty){
pop(result);
return true;
}
final Route<T> route = routerHistory.last;
final RoutePopDisposition disposition = await route.willPop();
if (mounted) {
switch (disposition) {
case RoutePopDisposition.pop:
pop(result);
return true;
break;
case RoutePopDisposition.doNotPop:
return false;
break;
case RoutePopDisposition.bubble:
pop(result);
return true;
break;
}
}
return false;
}
了解以上流程后,我們大概可以明白為何back鍵返回時,無法回傳內容了,因為maybePop未傳入相關result內容。其實想想可可以理解,因為框架也不知道我們具體要回傳什么。查看上面maybePop的源碼,我們會發現一個關鍵的地方
final RoutePopDisposition disposition = await route.willPop();
按下back時頁面是否pop,完全取決于willPop的返回值。route默認為MaterialPageRoute,其繼承自PageRoute,PageRoute繼承自ModalRoute,在ModalRoute中我們可以看到willPop的具體實現。可見WillPopCallbacks非空且其中callback返回值為false時,返回值為RoutePopDisposition.doNotPop,maybePop中頁面才不會關閉。
@override
Future<RoutePopDisposition> willPop() async {
final _ModalScopeState<T> scope = _scopeKey.currentState;
assert(scope != null);
for (final WillPopCallback callback in List<WillPopCallback>.from(_willPopCallbacks)) {
if (await callback() != true)
return RoutePopDisposition.doNotPop;
}
return await super.willPop();
}
了解了以上流程,我們可以在FlutterB中添加WillPopScope攔截,onWillPop中返回false即可,這樣flutter_boost不會關閉當前頁面,我們在onWillPop中調用Navigator的pop關閉頁面和回傳數據即可。