在Flutter中导航到一个新页面,通常我们会使用Navigator.of(context).push()
来处理,或者是调用pushNamed("\xxx"),pop()
等 。
但是这里有几个问题,
- 需要使用到context,这就限制来导航的使用场景,必须要在一个Widget中;
- 第二个问题是,如果在Widget中写导航相关的代码,那么就在UI层混入了逻辑层的代码,路由导航属于业务逻辑,这不利于有一个清晰的架构;
- 所有的调用者都必须要清楚导航逻辑,这使得代码变得强耦合,一旦导航逻辑修改了,所有的调用处都需要修改;
所以,本文将使用get_it库和命名路由named-routing
的方式来解决以上问题。
关于get_it,后面将再出一篇文章详细介绍。
命名路由named-routing
步骤:
- 在MaterialApp中有个onGenerateRoute属性,在这里指定一个方法
generateRoute
,方法接收一个RouteSettings
参数,返回一个Route<dynamic>
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
onGenerateRoute: router.generateRoute,
initialRoute: HomeViewRoute,
home: HomeView(),
);
}
}
- 要导航的一些View
class HomeView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text('Home'),),
);
}
}
class LoginView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text('Login'),),
);
}
}
- 创建一个
routing_constants.dart
来管理所有的路由名称
const String HomeViewRoute = '/';
const String LoginViewRoute = 'login';
- 创建
router.dart
,管理所有的路由逻辑
import 'package:flutter/material.dart';
import 'package:flutter_navigation/routing_constants.dart';
import 'package:flutter_navigation/view/HomeView.dart';
import 'package:flutter_navigation/view/LoginView.dart';
import 'package:flutter_navigation/view/UndefineView.dart';
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case HomeViewRoute:
return MaterialPageRoute(builder: (context) => HomeView());
case LoginViewRoute:
var loginArgument = settings.arguments;
return MaterialPageRoute(builder: (context) => LoginView(argument: loginArgument));
default:
return MaterialPageRoute(
builder: (context) => UndefinedView(name: settings.name));
}
}
- 要导航的时候,直接调用:
Navigator.pushNamed(context, LoginViewRoute);
- 可以设置一个默认的路由页面
UndefinedView
,用于处理异常场景
import 'package:flutter/material.dart';
class UndefinedView extends StatelessWidget {
final String name;
const UndefinedView({Key key, this.name}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('Route for $name is not defined'),
),
);
}
}
也可以像下面这样处理,但是不建议采用,因为下面的方式将导航的业务逻辑混入到了View层中,
return MaterialApp(
title: 'Named Routing',
onGenerateRoute: router.generateRoute,
onUnknownRoute: (settings) => MaterialPageRoute(
builder: (context) => UndefinedView(
name: settings.name,
)),
initialRoute: HomeViewRoute,
);
关于跳转时,参数的传递和返回
调用:
Navigator.pushNamed(context, LoginViewRoute, arguments: 'Data Passed in');
在传递时,在路由管理类里加上传递参数的逻辑
switch (settings.name) {
...
case LoginViewRoute:
var loginArgument = settings.arguments;
return MaterialPageRoute(builder: (context) => LoginView(argument: loginArgument));
}
同时修改LoginView的代码,来接收参数,
class LoginView extends StatelessWidget {
final String argument;
const LoginView({Key key, this.argument}) : super(key: key);
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
Navigator.pop(context, 'fromLogin');
return false;
},
child: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.pop(context, 'fromLogin');
},
),
body: Center(
child: Text('Login $argument'),
),
),
);
}
}
这里使用到了WillPopScope
这个Widget,这个就是用来重载系统的返回键的,其应该用在单个页面上,如果返回了false,那么就不响应系统的返回键,当然也可以加入自己的逻辑,返回一些参数。
到这里已经实现了统一管理导航的逻辑了,接下来实现不使用context来处理导航。
跳转时,不使用context
在Flutter中,GlobalKeys可用于访问StatefulWidget的状态,利用这一点,我们可以在context以外的地方来访问NavigatorState。
- 添加get_it库
get_it: ^3.0.0+1
- 创建一个全局的Service Locator
import 'package:get_it/get_it.dart';
import 'NavigateService.dart';
final GetIt locator = GetIt.instance;
void setupLocator() {
locator.registerSingleton(NavigateService());
}
- 创建一个NavigateService
import 'package:flutter/material.dart';
class NavigateService {
final GlobalKey<NavigatorState> navigatorKey =
GlobalKey(debugLabel: 'navigate_key');
NavigatorState get navigator => navigatorKey.currentState;
get pushNamed => navigator.pushNamed;
get push => navigator.push;
}
- 在runApp 之前初始化
void main() {
setupLocator();
runApp(App());
}
- 在
MaterialApp
的navigatorKey
属性中 绑定NavigateService
中的 GlobalKey
import 'package:flutter/material.dart';
import 'package:flutter_navigation/router.dart' as router;
import 'package:flutter_navigation/routing_constants.dart';
import 'package:flutter_navigation/service_locator.dart';
import 'package:flutter_navigation/view/HomeView.dart';
import 'NavigateService.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
onGenerateRoute: router.generateRoute,
initialRoute: HomeViewRoute,
navigatorKey: locator<NavigateService>().navigatorKey,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomeView(),
);
}
}
完整代码参考:
https://github.com/chenyucheng97/flutter_navigation
参考:
Flutter | 通过 ServiceLocator 实现无 context 导航
Flutter Navigation Cheatsheet - A Guide to Named Routing
Navigate Without BuildContext in Flutter using a Navigation Service