淺嘗Flutter(一)
承接上文。
頁面路由框架
其實可以講的主要是Flutter的特性導致的一些架構上的變化。
Android的界面跳轉(zhuǎn)主要涉及到系統(tǒng)層面的各個服務。
但是到了Flutter其實就沒有這些概念了,F(xiàn)lutter其實是單Activity架構。
我另一個B端項目POS項目也采用的這種架構,作用很明顯,數(shù)據(jù)通信極其方便。缺點是頁面之間的路由維護會稍微復雜點。而且Flutter的Navigator用起來并不方便,一開始項目對Navigator1.0做了一層封裝,初始化的參數(shù)定義特別死,很不方便,是一個List<String,Map<String,String>>,參數(shù)在傳遞過程中需要來回轉(zhuǎn)換,而且頁面標識也不明顯,非schema內(nèi)部跳轉(zhuǎn)也需要把參數(shù)進行path轉(zhuǎn)換。
比如A--》B攜帶參數(shù){ "t1" : 1, "t2" : 2 },B定義為/test
那么跳轉(zhuǎn)path需要先拼接成/test?t1=1&t2=2,然后到終端頁面還解析成了ArrayList<String,Map<String,Strign>>,這可太低效,且難以理解了。
屬實不好用,我覺的是一個很失敗的封裝,然后重新查閱了Flutter官網(wǎng)資料發(fā)現(xiàn)Flutter有一個Navigator2,采用了Page的概念去維護頁面棧,棧采用Arraylist<Page>,挺清晰的。
所以在其他人開發(fā)業(yè)務的同時我對項目的路由框架下層進行了重構。對外的接口類不動,但是增加了一些可變參數(shù),減少改動。
class XXXNavigator{
static Future navigateTo(BuildContext context, String pageDef,
{bool clearStack = false,
bool replace = false,
String? clearToPageDef,
Map<String, dynamic>? params}) {
unfocus(context);
if (globalDebugCtl) {
print("Jump to $pageDef");
}
return CommonRouterDelegate.getInstance().addPage(context, pageDef, params,
replace: replace,
clearToPageDef: clearToPageDef,
clearStack: clearStack);
}
static Future navigateTo(BuildContext context, String pageDef,
{bool clearStack = false,
bool replace = false,
String? clearToPageDef,
Map<String, dynamic>? params})
//.....省略其他方法
}
CommonRouterDelegate是我實現(xiàn)Navigator的地方。
RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
typedef RouterBuilder = BasePage? Function(
String page, Map<String, dynamic>? params);
class CommonRouterDelegate extends RouterDelegate<CommonRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<CommonRoutePath> {
static CommonRouterDelegate? _instance;
static CommonRouterDelegate getInstance() {
_instance ??= CommonRouterDelegate._private();
return _instance!;
}
CommonRouterDelegate._private();
@override
CommonRoutePath? get currentConfiguration => _currentConfiguration;
@override
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
CommonRoutePath? _currentConfiguration;
var test = false;
List<BasePage> pages = [
const BasePage(child: SplashScreen()),
];
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: List.of(pages),
observers: [routeObserver],
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
BasePage page = pages.removeLast();
notifyListeners();
_popCompleter[route.settings.name]?.complete(result);
if (result is! LoadingPopEntity) {
CommonRouterObserver.getInstance().onPageEnd(page);
}
//通知路由變化
return true;
},
);
}
@override
Future<void> setNewRoutePath(CommonRoutePath configuration) async {
_currentConfiguration = configuration;
notifyListeners();
}
final Map<String, Completer<dynamic>?> _popCompleter = {};
//執(zhí)行指定行為并打開某個頁面
Future addPage(
BuildContext context, String pageDef, Map<String, dynamic>? params,
{bool replace = false,
bool clearStack = false,
String? clearToPageDef}) async {
BasePage? page = getPage(pageDef, params);
if (page != null) {
if (replace) {
pages.removeLast();
}
if (clearStack) {
pages.clear();
}
if (clearToPageDef != null) {
int index =
pages.indexWhere((element) => element.name == clearToPageDef);
if (index > 0) {
pages = pages.sublist(0, index + 1);
}
}
pages.add(page);
notifyListeners();
CommonRouterObserver.getInstance().onPageStart(page);
_popCompleter[pageDef] = Completer<dynamic>();
return await _popCompleter[pageDef]!.future;
}
}
void clear() {
pages.clear();
notifyListeners();
}
void popUtil(BuildContext context, String clearToPageDef) {
int index = pages.indexWhere((element) => element.name == clearToPageDef);
while (index > 0 && index < pages.length) {
BasePage page = pages.removeLast();
// print("<<<<<<<<popUtil>clearToPageDef $page , index = $index");
_popCompleter[page.name!] = Completer<dynamic>();
_popCompleter[page.name]?.complete();
CommonRouterObserver.getInstance().onPageEnd(page);
PageRoute cur = ModalRoute.of(context) as PageRoute;
routeObserver.didPop(cur, cur);
index++;
}
notifyListeners();
}
BasePage<dynamic>? getPage(String pageDef, Map<String, dynamic>? params) {
return baseModuleBuilder.call(pageDef, params) ??
homeModuleBuilder.call(pageDef, params) ??
accountModuleBuilder.call(pageDef, params) ??
payModuleBuilder.call(pageDef, params);
}
}
baseModuleBuilder,homeModuleBuilder,accountModuleBuilder,payModuleBuilder等是我們的頁面工廠,主要建立的是頁面和PageDef的關聯(lián)。
定義的PageDef中維護的是我們每個頁面的字符串路由描述。ModuleBuilder的作用還有對業(yè)務進行分類的作用?;赑ageDef的映射關系我們實現(xiàn)頁面路由也是極其方便。
核心的幾個點主要是,頁面分發(fā)和頁面棧的維護,以及Flutter的await機制的維護,這個還挺好用的,比Android的startActivityForResult好用多了。
從主要方法addPage和Pop。
其中addPage中的幾個參數(shù)尤為重要,分為replace,clearStack,clearToPageDef.
主要是實現(xiàn)了跳新頁面置換現(xiàn)在的頁面(啟動頁面分發(fā)等場景),跳新頁面清棧(非游客模式的token實現(xiàn)場景),清理頁面到指定PageDef并打開新頁面(主要是支付成功之后的下級頁面跳轉(zhuǎn)等場景)。
實際在使用一段時間之后,發(fā)現(xiàn)頁面嵌套await存在一些小問題,我知道是什么問題,前段時間太忙,加上代碼目前穩(wěn)定,所以沒有來得及修復,后面修復的更新一下。