
在本教程中,我们将介绍实现导航服务的过程,该服务允许您在没有BuildContext的情况下进行导航。唯一适用的时间是您将UI代码与业务逻辑分开,类似于此体系结构。通过在服务中进行导航,您可以在进行实际业务逻辑决策的同一位置导航,而不必直接返回上下文可用的UI代码。这就是您希望自己构建一个这样的Dialog Manager的原因,您可以在其中显示并从业务逻辑所需的对话框中获取输入。
这是确保您的视图文件仅显示UI而其余部分在其他地方进行管理的另一步骤。
我们将用以实现这一目标的想法如下。拥有包含GlobalKey
类型的服务NavigatorState
。公开函数以推送命名路由并在需要时传入可选参数。我总是说服务永远不应该在视图文件中使用,这仍然是正确的,但是通过单词use我的意思是调用改变状态或执行业务逻辑的函数。我们在这里所做的就是设置一个州所需的值,以确保我们可以将我们的服务链接到UI。
我已经在provider-get_it架构中设置了一个基本应用程序,该架构在UI中具有导航功能,因此我还可以介绍为什么要将功能移动到模型中。下载代码并在您选择的IDE中打开项目。
启动项目设置
项目中的代码有两个视图,其视图模型与之关联。登录和主页。两者都有一个按钮在屏幕上,登录是一个登录按钮,在Home是一个注销按钮。视图将调用模型上的函数以执行某种业务逻辑,然后返回到UI以执行条件导航。这就是我们想要避免的。我们想要模型中的所有逻辑,包括检查某些事情是否成功并根据结果执行导航。
实施服务
如果你转到主页视图,你会在onTap
函数中看到以下代码GestureDetector
。
onTap: () async {
var success = await model.login(success: true);
if (success) {
Navigator.of(context).pushNamed(routes.HomeRoute);
}
},
这通常是您导航的方式,主要是因为您BuildContext
的模型中没有可用的。从这段代码中可以看出,存在与功能相关的逻辑。这意味着随着应用程序功能的扩展,我们可能会在这里添加更多逻辑。这是我们在一个“假定”只是UI的视图文件中工作时我们绝对想避免做的事情。模型应该处理所有逻辑,视图应该只调用模型上的函数,然后在需要时使用新状态重建自身。
为了遵守该原则,我们将导航功能移动到我们可以从模型类调用的服务中。在lib下创建一个名为services的新文件夹,并在其中创建一个名为navigation_service.dart的新文件
class NavigationService {
final GlobalKey<NavigatorState> navigatorKey =
new GlobalKey<NavigatorState>();
Future<dynamic> navigateTo(String routeName) {
return navigatorKey.currentState.pushNamed(routeName);
}
bool goBack() {
return navigatorKey.currentState.pop();
}
}
打开locator.dart文件并注册导航服务
GetIt locator = GetIt.instance;
void setupLocator() {
locator.registerLazySingleton(() => NavigationService());
}
我们将NavigationService与应用程序链接的方式是将服务中的密钥提供给MaterialApp
。转到main.dart文件并设置您的navigatorKey
。
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
...
navigatorKey: locator<NavigationService>().navigatorKey,
onGenerateRoute: router.generateRoute,
initialRoute: routes.LoginRoute,
);
}
}
使用服务
既然已经设置并实现了服务,我们就可以使用它并将该逻辑移动到viewmodel中。转到LoginView并更新onTap函数GestureDetector
以删除所有登录await功能。
GestureDetector(
onTap: () {
model.login(success: true);
},
child: ...
)
打开LoginViewModel并将route_paths文件作为路由导入。然后我们将检索NavigationService
作为私人最终字段,当成功时我们将调用导航服务导航到HomeRoute。
import 'package:nav_service/constants/route_paths.dart' as routes;
import 'package:nav_service/services/navigation_service.dart';
...
class LoginViewModel extends BaseModel {
final NavigationService _navigationService = locator<NavigationService>();
Future login({bool success = true}) async {
setBusy(true);
await Future.delayed(Duration(seconds: 1));
if (!success) {
setErrorMessage('Error has occured with the login');
} else {
_navigationService.navigateTo(routes.HomeRoute);
setErrorMessage(null);
}
setBusy(false);
}
}
而已。现在,View文件的责任将被带回到显示UI并将用户操作传递给模型,而不是显示UI,将用户操作传递给模型和导航。这HomeView
是完全相同的,LoginView
我们所做的就是更新onTap函数并删除条件反向导航。
GestureDetector(
onTap: () {
model.logout();
},
child: ...
)
然后我们更新通过HomeViewModel
执行后退导航NavigationService
。
import 'package:nav_service/services/navigation_service.dart';
class HomeViewModel extends BaseModel {
final NavigationService _navigationService = locator<NavigationService>();
Future logout({bool success = true}) async {
setBusy(true);
await Future.delayed(Duration(seconds: 1));
if (!success) {
setErrorMessage('Error has occured during sign out');
} else {
_navigationService.goBack();
}
}
}
这样做的好处是导航逻辑扩展您的UI将保持不变,模型将承载所有逻辑/状态管理。这与Dialog Manager教程相结合,应该从您的视图中获取所有“Widget显示”功能,并将其放入您所属的业务逻辑中。
导航参数
拥有导航参数是一项常见任务,因此我们将其添加到导航服务中。如果您想更深入地了解* Flutter中的Navigation,请*查看本教程。
我们首先在navigateTo
函数中添加一个可选的动态参数,NavigationService
并将其传递给我们的pushNamed调用。
Future<dynamic> navigateTo(String routeName, {dynamic arguments}) {
return navigatorKey.currentState.pushNamed(routeName, arguments: arguments);
}
现在在LoginViewModel
我们导航的地方,我们将传入一个String类型的参数,以显示在Button上的HomeView中。
_navigationService.navigateTo(routes.HomeRoute, arguments: '\nFilledStacks');
现在转到router.dart,在这种HomeRoute
情况下,我们将从设置中提取参数并将其传递给HomeView。
...
case routes.HomeRoute:
var userName = settings.arguments as String;
return MaterialPageRoute(
builder: (context) => HomeView(userName: userName));
...
最后。在HomeView
我们可以将userName参数添加到构造函数并将其设置为字段userName。要显示用户名,我们会将其附加到“退出”文本。
class HomeView extends StatelessWidget {
final String userName;
const HomeView({Key key, this.userName}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
...
Text('Logout' + userName,
style: TextStyle(
fontWeight: FontWeight.w800,
color: Colors.white,
fontSize: 30))
);
}
}
这就是你所需要的。现在,您可以继续将所有导航移动到模型中,从视图中移出并将所有业务逻辑保持在一起。
网友评论