Flutter秘籍四Flutter秘籍(四)八页面导航Flutter应用可能有多个屏幕或页面。页面是一组

使用Navigator.push()导航到新路线,使用Navigator.pop()导航到以前的路线。

路线由Navigator小工具管理。导航器管理一堆路线。可以使用push()方法将路由推入堆栈,使用pop()方法将路由弹出堆栈。堆栈中的顶部元素是当前活动的路由。Navigator是一个有状态小部件,其状态为NavigatorState。要与navigator交互,可以使用Navigator的静态方法或获取一个NavigatorState的实例。通过使用Navigator.of()方法,您可以获得给定构建上下文的最近的封闭NavigatorState实例。您可以显式创建Navigator小部件,但是大多数时候您将使用由WidgetsApp、MaterialApp或CupertinoApp小部件创建的Navigator小部件。

使用抽象Route类的实现来表示路线。例如,PageRoute代表全屏模式路线,PopupRoute代表在当前路线上叠加一个小工具的模式路线。PageRoute和PopupRoute类都是ModalRoute类的子类。对于材质设计应用,创建全屏页面最简单的方法是使用MaterialPageRoute类。MaterialPageRoute使用WidgetBuilder函数构建路线的内容。

在清单8-1中,Navigator.of(context)获取要使用的NavigatorState实例。推送给导航器的新路线是一个MaterialPageRoute实例。新路线有一个按钮,使用NavigatorState.pop()方法将当前路线弹出导航器。其实在使用Scaffoldwidget的时候,应用栏里自动添加了一个后退按钮,所以不需要使用显式的后退按钮。

您想要从不同的地方导航到相同的路线。

使用带有Navigator.pushNamed()方法的命名路线。

当使用Navigator.push()方法将新路线推送到导航器时,使用构建器函数按需构建新路线。当路线可以从不同的地方导航时,这种方法不太适用,因为我们不想重复构建路线的代码。在这种情况下,使用命名路由是更好的选择。命名路由具有唯一的名称。Navigator.pushNamed()方法使用名称来指定要推送到导航器的路线。

在清单8-2中,按下“注册”按钮将指定的路线/sign_up推送到导航器。

classLogInPageextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:Text('LogIn'),),body:Center(child:RaisedButton(child:Text('SignUp'),onPressed:(){Navigator.pushNamed(context,'/sign_up');},),),);}}Listing8-2Usenamedroute在清单8-3中,在routes参数中注册了两条命名路径。

classPageNavigationAppextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'PageNavigation',home:IndexPage(),routes:{'/sign_up':(context)=>SignUpPage(),'/log_in':(context)=>LogInPage(),},);}}Listing8-3Registernamedroutes8.3在路线之间传递数据问题您希望在不同的路由之间传递数据。

使用构造函数参数或RouteSettings对象将数据传递给路由,使用Navigator.pop()方法的result参数从路由传递数据。

构建路径内容时,路径可能需要额外的数据。弹出时,路由也可能返回一些数据。例如,编辑用户详细信息的路由可能需要当前的详细信息作为输入,并返回更新的详细信息作为输出。根据导航路线的方式,有不同的方法在路线之间传递数据。

在清单8-4中,UserDetails类包含用户的名和姓。UserDetailsPage显示用户的详细信息。当按下编辑按钮时,一条新路线被推送到导航器。新路由的内容是一个EditUserDetailsPage小部件,将UserDetails对象作为构造函数参数。新路由的返回值也是一个UserDetails对象,用于更新UserDetailsPage的状态。

UserDetailsresult=awaitNavigator.pushNamed(context,'/edit_user',arguments:_userDetails,);Listing8-6Passdatatonamedroute被命名的路线/edit_user被登记在MaterialApp中。不能使用route参数,因为您不能访问在构建器函数中传递给路线的数据。应使用WidgetsApp、MaterialApp或CupertinoApp的onGenerateRoute参数。onGenerateRoute参数的类型为RouteFactory,是函数类型Route(RouteSettingssettings)的typedef。RouteSettings类包含创建Route对象时可能需要的数据。表8-1显示了RouteSettings类的属性。

表8-1

路由设置的属性

名字

|

类型

描述

||---|---|---||name|String|路线的名称。||arguments|Object|传递给路由的数据。||isInitialRoute|bool|此路线是否是推送到导航器的第一条路线。|

当实现onGenerateRoute函数时,需要根据提供的RouteSettings对象返回路线。在清单8-7中,首先检查name属性,然后返回一个内容为EditUserDetailsPage的MeterialPageRoute。RouteSettings的arguments属性在EditUserDetailsPage构造函数中使用。arguments属性的值是清单8-6中传递的UserDetails对象。

使用onGenerateRoute参数。

当使用WidgetsApp的routes参数注册命名路线时,只有整个路线名称可用于匹配Route对象。如果想用复杂的逻辑将Route对象与路线名称匹配,可以使用onGenerateRoute参数和RouteSettings对象。例如,您可以将所有以/order开头的路线名称匹配到一个Route对象。

在清单8-8中,所有以/order开头的路线名称将使用OrderPage导航到一条路线。

MaterialApp(onGenerateRoute:(RouteSettingssettings){if(settings.name.startsWith('/order')){returnMaterialPageRoute(settings:settings,builder:(context){returnOrderPage();},);}},);Listing8-8Routematching8.5处理未知路线问题您希望处理导航到未知路线的情况。

使用Navigator、WidgetsApp、MaterialApp、CupertinoApp的onUnknownRoute参数。

可能会要求导航员导航到未知的路线。这可能是由于应用中的编程错误或外部路线导航请求造成的。如果onGenerateRoute函数为给定的RouteSettings对象返回null,则onUnknownRoute函数被调用以提供一条回退路线。这个onUnknownRoute函数通常用于错误处理,就像web应用中的404页面一样。onUnknownRoute的类型也是RouteFactory。

在清单8-9中,onUnknownRoute函数返回显示NotFoundPage小部件的路线。

MaterialApp(onUnknownRoute:(RouteSettingssettings){returnMaterialPageRoute(settings:settings,builder:(BuildContextcontext){returnNotFoundPage(settings.name);},);},);Listing8-9UseonUnknownRoute8.6显示材质设计对话框问题您希望显示材质设计对话框。

使用showDialog()功能和Dialog、SimpleDialog和AlertDialog控件。

要使用材质设计对话框,你需要创建对话框部件并显示它们。Dialog类及其子类SimpleDialog和AlertDialog可以用来创建对话框。

SimpleDialog小工具为用户提供了几个选项。选项使用SimpleDialogOption类表示。一个SimpleDialogOption小部件可以有一个子小部件和一个onPressed回调。当创建SimpleDialog时,你可以提供一个孩子列表和一个可选的标题。AlertDialogwidget向用户呈现内容和动作列表。AlertDialog用于确认用户或要求确认。

在清单8-10中,按下按钮会显示一个带有两个选项的简单对话框。

图8-1

材质设计简单对话框

在清单8-11中,按下按钮会显示一个带有两个动作的警告对话框。

图8-2

材质设计警告对话框

您想要显示iOS对话框。

使用showCupertinoDialog()功能和CupertinoAlertDialog和CupertinoPopupSurface控件。

对于iOS应用,你可以使用showCupertinoDialog()功能和CupertinoAlertDialog和CupertinoPopupSurface等小工具来显示对话框。showCupertinoDialog()功能与材质设计的showDialog()功能类似。该函数也使用Navigator.push()方法将对话路径推送到导航器。CupertinoAlertDialog是一个内置的对话框实现,用于确认用户或要求确认。一个CupertinoAlertDialog可能有标题、内容和动作列表。使用CupertinoDialogAction小部件表示动作。表8-2显示了CupertinoDialogAction构造器的参数。

表8-2

CupertinoDialogAction参数

||---|---|---||child|Widget|行动的内容。||onPressed|VoidCallback|操作按了回拨。||isDefaultAction|bool|此操作是否为默认操作。||isdstructuralaction|bool|这个动作是否具有破坏性。破坏性的行为有不同的风格。||textStyle|TextStyle|应用于操作的文本样式。|

在清单8-12中,按下按钮会显示一个iOS风格的警告对话框。

图8-3

iOS警报对话框

如果你想创建一个自定义对话框,你可以使用CupertinoPopupSurface小部件来创建圆角矩形表面。

您想要在iOS应用中呈现一组操作供用户选择。

使用showCupertinoModalPopup()功能和CupertinoActionSheet控件。

如果想在iOS应用中呈现一组动作供用户选择,可以使用showCupertinoModalPopup()函数显示CupertinoActionSheetwidgets。一个CupertinoActionSheet可以有标题、消息、取消按钮和动作列表。动作被表示为CupertinoActionSheetAction小部件。CupertinoActionSheetAction构造器有参数child、onPressed、isDefaultAction、isDestructiveAction,与表8-2中的CupertinoDialogAction构造器含义相同。

在清单8-13中,按下按钮会显示一个带有三个动作和一个取消按钮的动作表。

图8-4

iOS行动表

你想在材质设计应用中显示菜单。

使用showMenu()函数和PopupMenuEntry类的实现。

要使用showMenu()函数,你需要有一个PopupMenuEntry对象的列表。有不同类型的PopupMenuEntry实现:

PopupMenuItem是具有其值的类型的类属。表8-3显示了PopupMenuItem构造器的参数。CheckedPopupMenuItem是PopupMenuItem的子类。CheckedPopupMenuItem有checked属性来指定是否显示复选标记。

表8-3

PopupMenuItem构造函数的参数

||---|---|---||child|Widget|菜单项的内容。||value|T|菜单项的值。||enabled|bool|是否可以选择此菜单项。||height|double|菜单项的高度。默认为48。|

表8-4

showMenu()的参数

清单8-14中的菜单包含一个PopupMenuItem,一个PopupMenuDivider,和一个CheckedPopupMenuItem。

表8-5

弹出菜单按钮的参数

清单8-15展示了如何使用PopupMenuButton来实现与清单8-14中相同的菜单。

图8-5

材质设计菜单

你想要复杂的页面流。

使用嵌套的Navigator实例。

一个Navigator实例管理它自己的路由栈。对于简单的app,一个Navigator实例一般就够了,可以简单使用WidgetsApp、MaterialApp或者CupertinoApp创建的Navigator实例。如果你的应用有复杂的页面流,你可能需要使用嵌套导航器。由于Navigator本身也是一个小部件,Navigator实例可以像普通小部件一样创建。由WidgetsApp、MaterialApp或CupertinoApp创建的Navigator实例成为根导航器。所有导航器都是按层次结构组织的。要获得根导航器,可以在调用Navigator.of()方法时将rootNavigator参数设置为true。表8-6显示了Navigator构造器的参数。

表8-6

导航器参数

在清单8-16中,导航器有两条命名的路线。初始路线设置为on_boarding/topic,所以先显示UserOnBoardingTopicPage。

classUserOnBoardingPageextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:Text('GetStarted'),),body:Navigator(initialRoute:'on_boarding/topic',onGenerateRoute:(RouteSettingssettings){WidgetBuilderbuilder;switch(settings.name){case'on_boarding/topic':builder=(BuildContextcontext){returnUserOnBoardingTopicPage();};break;case'on_boarding/follower':builder=(BuildContextcontext){returnUserOnBoardingFollowPage();};break;}returnMaterialPageRoute(builder:builder,settings:settings,);},),);}}Listing8-16Useron-boardingpage在清单8-17中,按下“下一步”按钮导航到路线名称为on_boarding/follower的下一步。按下“完成”按钮,使用根导航器弹出登机页面。

图8-6

选择主题的步骤

CupertinoTabView有自己的导航器实例。创建CupertinoTabView时,可以提供routes、onGenerateRoute、onUnknownRoute、navigatorObservers参数。这些参数用于配置Navigator实例。当使用CupertinoTabScaffold创建选项卡布局时,每个选项卡视图都有自己的导航状态和历史。

使用嵌套导航器时,确保使用正确的导航器实例很重要。如果要显示和关闭全屏页面或模态对话框,应该使用Navigator.of(context,rootNavigator:true)获得的根导航器。调用Navigator.of(context)只能获得最近的封闭Navigator实例。没有办法获得层次结构中的中间Navigator实例。您需要在窗口小部件树的正确位置使用BuildContext对象。像showDialog()和showMenu()这样的函数总是在内部使用Navigator.of(context)。您只能使用传入的BuildContext对象来控制这些函数使用哪个Navigator实例。

您希望在导航状态改变时得到通知。

使用NavigatorObserver。

有时,您可能希望在导航器状态改变时得到通知。例如,您希望分析使用应用的用户的页面流量,以改善用户体验。当创建Navigator实例时,您可以提供一个NavigatorObserver对象的列表,作为导航器状态变化的观察者。表8-7显示了NavigatorObserver接口的方法。

表8-7

导航观测方法

||---|---||didPop(Routeroute,RoutepreviousRoute)|弹出route并且previousRoute是新激活的路由。||didPush(Routeroute,RoutepreviousRoute)|按下route,而previousRoute是先前激活的路线。||didRemove(Routeroute,RoutepreviousRoute)|route被删除,previousRoute是被删除路线的下一条路线。||didReplace(RoutenewRoute,RouteoldRoute)|将oldRoute替换为newRoute。||didStartUserGesture(Routeroute,RoutepreviousRoute)|用户开始使用手势移动route。路线正下方的路线是previousRoute。||didStopUserGesture()|用户使用手势停止移动路线。|

在清单8-18中,LoggingNavigatorObserver类记录路由推送和弹出时的消息。

表8-8

路由软件的方法

||---|---||didPop()|弹出当前路径时回调。||didPopNext()|当顶层路由弹出后当前路由变为活动时调用。||didPush()|当当前路线被推送时调用。||didPushNext()|推送新路由后,当前路由不再活动时调用。|

要真正得到一个Route对象的通知,您需要使用RouteObserver的subscribe()方法将一个RouteAware对象订阅给一个Route对象。当不再需要订阅时,您应该使用unsubscribe()取消订阅RouteAware对象。

在清单8-19中,_ObservedPageState类实现了RouteAware接口并覆盖了didPush()和didPop()方法来打印出一些消息。ModalRoute.of(context)从构建上下文中获取最近的封闭ModalRoute对象,这是ObservedPage所在的路径。通过使用ModalRoute.of(context),不需要显式传递Route对象。当前_ObservedPageState对象使用传入的RouteObserver对象的subscribe()方法订阅当前路由中的状态变化。当_ObservedPageState对象被释放时,订阅被删除。

将WillPopCallback与ModalRoute对象一起使用。

当一条路线被推送到导航器时,可以使用Scaffold中的后退按钮或Android中的系统后退按钮弹出该路线。有时,您可能想要阻止路线被弹出。例如,如果页面中有未保存的更改,您可能希望首先显示一个警告对话框来要求确认。当使用Navigator.maybePop()方法而不是Navigator.pop()方法时,您有机会决定弹出路线的请求是否应该继续。

你想处理Future物体。

使用then()和catchError()方法处理Future对象的结果。

给定一个Future对象,关于其结果有三种不同的情况:

要注册对Future对象的回调,可以使用then()方法注册一个值回调和一个可选的错误回调,或者使用catchError()方法只注册一个错误回调。建议使用then()方法只注册一个值回调。这是因为如果一个错误回调是使用then()方法的onError参数注册的,这个错误回调不能处理在值回调中抛出的错误。大多数情况下,您希望错误回调处理所有可能的错误。如果一个Future对象的错误没有被它的错误回调函数处理,这个错误将被全局处理程序处理。

在清单9-1中,Future对象可能以值1或一个Error对象结束。值和错误回调都被注册来处理结果。

Future.delayed(Duration(seconds:1),(){if(Random().nextBool()){return1;}else{throwError();}},).then((value){print(value);}).catchError((error){print('error:$error');});Listing9-1Usethen()andcatchError()methodstohandleresultthen()和catchError()方法的返回值也是Future对象。给定一个Future对象A,调用A.then(func)的结果是另一个Future对象B,如果func回调运行成功,FutureB将以调用func函数的返回值完成。否则,FutureB将会以调用func函数时抛出的错误完成。调用B.catchError(errorHandler)返回一个新的Future对象c。错误处理程序可以处理在FutureB中抛出的错误,这些错误可能会在未来的A本身或其值处理程序中抛出。通过使用then()和catchError()方法,Future对象形成了一个处理异步计算的链。

在清单9-2中,多个then()方法被链接在一起按顺序处理结果。

Future.value(1).then((value)=>value+1).then((value)=>value*10).then((value)=>value+2).then((value)=>print(value));Listing9-2Chainedthen()methods如果你想在未来完成时调用函数,你可以使用whenComplete()方法。当这个future完成时,使用whenComplete()添加的函数被调用,不管它完成时是有值还是有错误。whenComplete()方法相当于其他编程语言中的finally块。then().catchError().whenComplete()的链条相当于“尝试-捕捉-最终”。

清单9-3展示了一个使用whenComplete()方法的例子。

Future.delayed(Duration(seconds:5),()=>1).timeout(Duration(seconds:2),onTimeout:()=>10,).then((value)=>print(value));Listing9-4Usetimeout()method9.2使用异步和等待来处理期货问题你想要处理Future对象,就像它们是同步的一样。

使用async和await。

对象代表异步计算。使用Future对象的通常方式是注册回调来处理结果。这种基于回调的风格可能会给习惯同步操作的开发人员造成障碍。使用async和await是Dart中的一个语法糖,可以像普通同步操作一样处理Future对象。

给定一个Future对象,await可以等待其完成并返回其值。await之后的代码可以直接使用返回值,就像它是同步调用的结果一样。使用await时,其封闭功能必须标记为async。这意味着该函数返回一个Future对象。

在清单9-5中,getValue()函数的返回值是一个Future对象。在calculate()函数中,await用于获取getValue()函数的返回值并赋给value变量。由于使用了await,所以calculate()功能被标记为async。

使用Future构造函数Future()、Future.delayed()、Future.sync()、Future.value()和Future.error()创建Future对象。

如果需要创建Future对象,可以使用它的构造函数Future()、Future.delayed()、Future.sync()、Future.value()和Future.error():

清单9-7展示了使用不同Future构造函数的例子。

Future(()=>1).then(print);Future.delayed(Duration(seconds:3),()=>1).then(print);Future.sync(()=>1).then(print);Future.value(1).then(print);Future.error(Error()).catchError(print);Listing9-7CreateFutureobjects9.4使用流问题你想处理一连串的事件。

要从流中接收事件,可以使用listen()方法来设置监听器。listen()方法的返回值是一个代表活动订阅的StreamSubscription对象。根据流上允许的订阅数量,有两种类型的流:

给定一个Stream对象,属性isBroadcast可以用来检查它是否是一个广播流。您可以使用asBroadcastStream()方法从单一订阅流创建广播流。

表9-1显示了listen()方法的参数。您可以为不同的事件提供任意数量的处理程序,并忽略那些不感兴趣的事件。

表9-1

listen()方法的参数

||---|---|---||onData|void(Tevent)|数据事件的处理程序。||onError|Function|错误事件的处理程序。||onDone|void()|done事件的处理程序。||cancelOnError|bool|发出第一个错误事件时是否取消订阅。|

在清单9-8中,提供了三种类型事件的处理程序。

Stream.fromIterable([1,2,3]).listen((value)=>print(value),onError:(error)=>print('error:$error'),onDone:()=>print('done'),cancelOnError:true,);Listing9-8Uselisten()method使用由listen()方法返回的StreamSubscription对象,您可以管理订阅。表9-2展示了StreamSubscription类的方法。

表9-2

流订阅的方法

||---|---||cancel()|取消此订阅。||pause([FutureresumeSignal])|请求流暂停事件发出。如果提供了resumeSignal,流将在未来完成时恢复。||resume()|暂停后继续流。||onData()|替换数据事件处理程序。||onError()|替换错误事件处理程序。||onDone()|替换done事件处理程序。||asFuture([EfutureValue])|返回处理流完成的未来值。|

当您想要处理流的完成时,asFuture()方法很有用。因为一个流可以正常完成,也可以出错,所以使用这个方法会覆盖现有的onDone和onError回调。在发生错误事件的情况下,订阅被取消,返回的Future对象完成时出现错误。在完成事件的情况下,Future对象以给定的futureValue结束。

stream的强大之处在于对流应用各种转换来获得另一个流或值。表9-3显示了返回另一个Stream对象的Stream类中的方法。

表9-3

流转换

清单9-9展示了使用流转换的例子。每个语句下面的代码显示了执行的结果。

Stream.fromIterable([1,2,3]).asyncExpand((intvalue){returnStream.fromIterable([value*5,value*10]);}).listen(print);//->5,10,10,20,15,30Stream.fromIterable([1,2,3]).expand((intvalue){return[value*5,value*10];}).listen(print);//->5,10,10,20,15,30Stream.fromIterable([1,2,3]).asyncMap((intvalue){returnFuture.delayed(Duration(seconds:1),()=>value*10);}).listen(print);//->10,20,30Stream.fromIterable([1,2,3]).map((value)=>value*10).listen(print);//->10,20,30Stream.fromIterable([1,1,2]).distinct().listen(print);//->1,2Stream.fromIterable([1,2,3]).skip(1).listen(print);//->2,3Stream.fromIterable([1,2,3]).skipWhile((value)=>value%2==1).listen(print);//->2,3Stream.fromIterable([1,2,3]).take(1).listen(print);//->1Stream.fromIterable([1,2,3]).takeWhile((value)=>value%2==1).listen(print);//->1Stream.fromIterable([1,2,3]).where((value)=>value%2==1).listen(print);//->1,3Listing9-9Streamtransformations在Stream类中有其他方法返回一个Future对象;见表9-4。这些操作返回单个值,而不是流。

表9-4

单一值的方法

清单9-10显示了使用表9-4中方法的例子。每个语句下面的代码显示了执行的结果。

Stream.fromIterable([1,2,3]).forEach(print);//->1,2,3Stream.fromIterable([1,2,3]).contains(1).then(print);//->trueStream.fromIterable([1,2,3]).any((value)=>value%2==0).then(print);//->trueStream.fromIterable([1,2,3]).every((value)=>value%2==0).then(print);//->falseStream.fromIterable([1,2,3]).fold(0,(v1,v2)=>v1+v2).then(print);//->6Stream.fromIterable([1,2,3]).reduce((v1,v2)=>v1*v2).then(print);//->6Stream.fromIterable([1,2,3]).firstWhere((value)=>value%2==1).then(print);//->1Stream.fromIterable([1,2,3]).lastWhere((value)=>value%2==1).then(print);//->3Stream.fromIterable([1,2,3]).singleWhere((value)=>value%2==1).then(print);//->Unhandledexception:Badstate:ToomanyelementsListing9-10MethodsreturnFutureobjects9.5创建流问题你想要创建Stream对象。

使用不同的Stream构造函数。

有不同的Stream构造函数来创建Stream对象:

清单9-11展示了不同Stream构造函数的例子。

Stream.fromIterable([1,2,3]).listen(print);Stream.fromFuture(Future.value(1)).listen(print);Stream.fromFutures([Future.value(1),Future.error('error'),Future.value(2)]).listen(print);Stream.periodic(Duration(seconds:1),(intcount)=>count*2).take(5).listen(print);Listing9-11UseStreamconstructors另一种创建流的方法是使用StreamController类。一个StreamController对象可以向它控制的流发送不同的事件。默认的StreamController()构造器创建一个单一订阅流,而StreamController.broadcast()构造器创建一个广播流。使用StreamController,您可以以编程方式在流中生成元素。

在清单9-12中,不同的事件被发送到由StreamController对象控制的流中。

表9-5

StreamBuilder的参数

表9-6

异步快照的属性

||---|---|---||connectionState|ConnectionState|异步计算的连接状态。||data|T|异步计算接收的最新数据。||error|Object|异步计算收到的最新错误对象。||hasData|bool|数据属性是否不是null。||hasError|bool|错误属性是否不是null。|

您可以使用connectionState的值来确定连接状态。表9-7显示了ConnectionState枚举的值。

表9-7

ConnectionState的值

||---|---||none|未连接到异步计算。||waiting|连接到异步计算并等待交互。||active|连接到活动的异步计算。||done|连接到终止的异步计算。|

使用StreamBuilderwidget构建UI时,典型的方式是根据连接状态返回不同的widget。例如,如果连接状态正在等待,则可以返回进程指示符。

在清单9-13中,流有五个每秒生成的元素。如果连接状态为none或waiting,则返回一个CircularProgressIndicator小工具。如果状态为active或done,则根据data和error属性的值返回一个Text小工具。

在清单9-14中,我们使用了一种不同的方式来构建UI。使用hasData和hasError属性来检查状态,而不是检查连接状态。

使用dart:convert库中的jsonEncode()和jsonDecode()函数。

JSON是一种流行的web服务数据格式。为了与后端服务交互,您可能需要在两种情况下处理JSON数据:

对于这两种场景,如果您只是偶尔需要处理简单的JSON数据,那么使用dart:convert库中的jsonEncode()和jsonDecode()函数是一个不错的选择。jsonEncode()函数将镖对象转换成字符串,而jsonDecode()函数将字符串转换成镖对象。在清单9-15中,数据对象首先被序列化为JSON字符串,然后JSON字符串再次被反序列化为Dart对象。

vardata={'name':'Test','count':100,'valid':true,'list':[1,2,{'nested':'a','value':123,},],};Stringstr=jsonEncode(data);print(str);Objectobj=jsonDecode(str);print(obj);Listing9-15HandleJSONdatadart:convert库中的JSON编码器只支持有限数量的数据类型,包括数字、字符串、布尔、null、列表和带字符串键的映射。要对其他类型的对象进行编码,您需要使用toEncodable参数来提供一个函数,该函数首先将对象转换为可编码的值。默认的toEncodable函数调用对象上的toJson()方法。向需要序列化为JSON字符串的自定义类添加toJson()方法是一种常见的做法。

在清单9-16中,ToEncode类的toJson()方法返回一个列表,该列表将作为JSON序列化的输入。

classToEncode{ToEncode(this.v1,this.v2);finalStringv1;finalStringv2;ObjecttoJson(){return[v1,v2];}}print(jsonEncode(ToEncode('v1','v2')));Listing9-16UsetoJson()function如果想在序列化的JSON字符串中有缩进,需要直接使用JsonEncoder类。在清单9-17中,两个空格被用作缩进。

StringindentString=JsonEncoder.withIndent('').convert(data);print(indentString);Listing9-17Addindent9.8处理复杂JSON数据问题您希望有一种类型安全的方法来处理JSON数据。

使用json_annotation和json_serializable库。

使用dart:convert库中的jsonEncode()和jsonDecode()函数可以轻松处理简单的JSON数据。当JSON数据具有复杂的结构时,使用这两个函数不是很方便。当反序列化JSON字符串时,结果通常是列表或映射。如果JSON数据有嵌套结构,那么从列表或映射中提取值就不容易了。当序列化对象时,您需要向这些类添加toJson()方法来构建列表或映射。这些任务可以通过使用json_annotation和json_serializable库的代码生成来简化。

json_annotation库提供注释来定制JSON序列化和反序列化行为。json_serializable库提供了生成处理JSON数据的代码的构建过程。要使用这两个库,您需要将它们添加到pubspec.yaml文件中。在清单9-18中,json_serializable库被添加到dependencies,而json_serializable库被添加到dev_dependencies。

dependencies:json_annotation:2.0.0dev_dependencies:build_runner:1.0.0json_serializable:2.0.0Listing9-18Addjson_annotationandjson_serializable在清单9-19中,Person类在json_serialize.dart文件中。注释@JsonSerializable()意味着为Person类生成代码。生成的代码在json_serialize.g.dart文件中。清单9-19中使用的函数_$PersonFromJson()和_$PersonToJson()来自生成的文件。_$PersonFromJson()函数用于Person.fromJson()构造函数,而_$PersonToJson()函数用于toJson()方法。

表9-8

JsonSerializable的属性

缺省值

||---|---|---||anyMap|false|如果为true,则使用Map作为地图类型;否则,使用地图。||checked|false|是否添加额外的检查来验证数据类型。||createFactory|true|是否生成将地图转换为对象的函数。||createToJson|true|是否生成可用作toJson()函数的函数。||disallowUnrecognizedKeys|false|为true时,无法识别的键被视为错误;否则,它们将被忽略。||explicitToJson|false|为true时,生成的toJson()函数对嵌套对象使用toJson。||fieldRename|FieldRename.none|将类字段的名称转换成JSON映射键的策略。||generateToJsonFunction|true|为真时,生成顶层函数;否则,用toJson()函数生成一个mixin类。||includeIfNull|true|是否包含具有空值的字段。||nullable|true|是否优雅地处理空值。||useWrappers|false|是否使用包装类在序列化过程中最大限度地减少Map和List实例的使用。|

generateToJsonFunction属性决定了如何生成toJson()函数。当值为true时,会生成类似清单9-20中_$PersonToJson()的顶级函数。在清单9-21中,User类的generateToJsonFunction属性被设置为false。

@JsonSerializable(generateToJsonFunction:false,)classUserextendsObjectwith_$UserSerializerMixin{User(this.name);finalStringname;}Listing9-21Userclass在清单9-22中,用toJson()方法生成了_$UserSerializerMixin类,而不是函数。清单9-21中的User类只需要使用这个mixin类。

表9-9

JsonKey的属性

||---|---||name|JSON映射键。如果为null,则使用字段名称。||nullable|是否优雅地处理空值。||includeIfNull|如果值为空,是否包括此字段。||ignore|是否忽略该字段。||fromJson|反序列化该字段的函数。||toJson|序列化该字段的函数。||defaultValue|用作默认值的值。||required|JSON映射中是否需要该字段。||disallowNullValue|是否不允许空值。|

清单9-23展示了一个使用JsonKey的例子。

@JsonKey(name:'first_name',required:true,includeIfNull:true,)finalStringfirstName;Listing9-23UseJsonKeyJsonValue注释指定用于序列化的枚举值。在清单9-24中,JsonValue注释被添加到Color的所有枚举值中。

enumColor{@JsonValue('R')Red,@JsonValue('G')Green,@JsonValue('B')Blue}Listing9-24UseJsonValueJsonLiteralannotation从文件中读取JSON数据,并将内容转换成对象。它允许轻松访问静态JSON数据文件的内容。在清单9-25中,JsonLiteral注释被添加到datagetter中。_$dataJsonLiteral是JSON文件中数据的生成变量。

@JsonLiteral('data.json',asConst:true)Mapgetdata=>_$dataJsonLiteral;Listing9-25UseJsonLiteral9.9处理XML数据问题您希望在Flutter应用中处理XML数据。

使用xml库。

XML是一种流行的数据交换格式。你可以使用xml库来处理Flutter应用中的XML数据。您需要先将xml:3.3.1添加到pubspec.yaml文件的dependencies中。与JSON数据类似,XML数据有两种使用场景:

要解析XML文档,您需要使用parse()函数,该函数将一个XML字符串作为输入,并返回解析后的XmlDocument对象。使用XmlDocument对象,可以查询和遍历XML文档树,从中提取数据。

表9-10

XmlParent的属性

表9-11

XmlBuilder的方法

||---|---||element()|用指定的标记名、名称空间、属性和嵌套内容创建一个XmlElement节点。||attribute()|用指定的名称、值、命名空间和类型创建一个XmlAttribute节点。||text()|用指定的文本创建一个XmlText节点。||namespace()|将名称空间prefix绑定到uri。||cdata()|用指定的文本创建一个XmlCDATA节点。||comment()|用指定的文本创建一个XmlComment节点。||processing()|用指定的target和text创建一个XmlProcessing节点。|

构建完成后,可以使用XmlBuilder的build()方法来构建XmlNode作为结果。在清单9-27中,根元素是一个具有id属性的note元素。nest参数的值是一个使用构建器方法构建节点元素内容的函数。

XmlBuilderbuilder=XmlBuilder();builder.processing('xml','version="1.0"');builder.element('note',attributes:{'id':'001',},nest:(){builder.element('from',nest:(){builder.text('John');});builder.element('to',nest:(){builder.text('Jane');});builder.element('message',nest:(){builder..text('Hello!')..comment('messagetosend');});},);XmlNodexmlNode=builder.build();print(xmlNode.toXmlString(pretty:true));Listing9-27UseXmlBuilder清单9-28显示了清单9-27中代码构建的XML文档。

使用html库。

尽管JSON和XML数据格式在Flutter应用中很流行,但您可能仍然需要解析HTML文档来提取数据。这个过程称为屏幕抓取。你可以使用html库来解析HTML文档。要使用这个库,需要将html:.13.4+1添加到pubspec.yaml文件的dependencies中。

parse()函数将HTML字符串解析成Document对象。这些Document对象可以使用W3CDOMAPI进行查询和操作。在清单9-29中,首先解析HTML字符串,然后使用getElementsByTagName()方法获取li元素,最后从li元素中提取id属性和文本。

使用dart:io库中的HttpClient。

HTTP协议是公开web服务的流行选择。表示可以是JSON或XML。通过使用来自dart:io库的HttpClient类,您可以轻松地通过HTTP与后端服务进行交互。

要使用HttpClient类,首先需要选择一个HTTP方法,然后为请求准备HttpClientRequest对象,为响应处理HttpClientResponse对象。HttpClient类有不同的方法对,对应不同的HTTP方法。例如,get()和getUrl()方法都用于发送HTTPGET请求。不同的是,get()方法接受host、port和path参数,而getUrl()方法接受Uri类型的url参数。你可以看到其他对,如post()和postUrl()、put()和putUrl()、patch()和patchUrl()、delete()和deleteUrl()、head()和headUrl()。

使用dart:io库中的WebSocket类。

WebSockets广泛用于web应用中,以提供浏览器和服务器之间的双向通信。他们还可以提供后台数据的实时更新。如果您已经有了一个WebSocket服务器,它可以与浏览器中运行的web应用进行交互,您可能还希望在Flutter应用中提供相同的功能。dart:io库中的WebSocket类可以用来实现WebSocket连接。

在清单9-34中,WebSocket连接到演示echo服务器。通过使用listen()方法订阅WebSocket对象,我们可以处理从服务器发送的数据。两个add()方法调用向服务器发送两条消息。

WebSocket.connect('ws://demos.kaazing.com/echo').then((WebSocketwebSocket){webSocket.listen(print,onError:print);webSocket.add('hello');webSocket.add('world');webSocket.close();}).catchError(print);Listing9-34ConnecttoWebSocket9.13连接插座问题您想要连接到套接字服务器。

使用dart:io库中的Socket类。

在清单9-35中,一个套接字服务器在端口10080上启动。该服务器将接收到的字符串转换成大写字母,并发回结果。

import'dart:io';import'dart:convert';voidmain(){ServerSocket.bind('127.0.0.1',10080).then((serverSocket){serverSocket.listen((socket){socket.addStream(socket.transform(utf8.decoder).map((str)=>str.toUpperCase()).transform(utf8.encoder));});});}Listing9-35Simplesocketserver在清单9-36中,Socket.connect()方法用于连接清单9-35中所示的socket服务器。从服务器接收的数据被打印出来。两个字符串被发送到服务器。

voidmain(){Socket.connect('127.0.0.1',10080).then((socket){socket.transform(utf8.decoder).listen(print);socket.write('hello');socket.write('world');socket.close();});}Listing9-36Connecttosocketserver9.14基于JSON的交互式REST服务问题您希望使用基于JSON的REST服务。

使用HttpClient、json_serialize库和FutureBuilder控件。

移动应用后端通过以JSON为代表的HTTP协议来公开服务是一种流行的选择。通过使用HttpClient、json_serialize库和FutureBuilder小部件,您可以构建UI来使用这些REST服务。这个菜谱提供了一个具体的例子,它结合了清单9-6、9-8和9-11中的内容。

使用grpc库。

$flutterpackagespubglobalactivateprotoc_plugin因为我们使用flutterpackages来运行安装,所以二进制文件放在FlutterSDK的.pub-cache/bin目录下。你需要把这个路径添加到PATH环境变量中。插件要求dart命令可用,所以你还需要将FlutterSDK的bin/cache/dart-sdk/bin目录添加到PATH环境变量中。现在我们可以使用protoc来生成Dart文件,以便与欢迎服务进行交互。在下面的命令中,lib/grpc/generated是生成文件的输出路径。proto_file_path是proto文件的路径。helloworld.proto文件包含迎宾服务的定义。库protobuf和grpc也需要添加到pubspec.yaml文件的dependencies中。

THE END
1.2024学年度第一学期第14周学生营养午餐菜谱物料名称4 含量(g) 星期一 百叶结烧肉 大荤 五花肉 50g 精肉 50g 百叶结 30g 星期一 糖醋小排 大荤 小排 100g 星期一 冬瓜木耳鸡片 小荤 冬瓜 140g 黑木耳(干) 1g 鸡胸肉 15g 星期一 炒青菜 素菜 青菜 120g 星期一 蘑菇蛋汤 汤 蘑菇 10g 鸡蛋 10g 星期二 咖喱土豆鸡块 大荤 鸡边腿 12https://cgzx.qpedu.cn/jngg/483837.htm
2.第14周公布菜单表每周食谱后勤保障第14周公布菜单表 每周菜单 第(14)周2024年12月2日至12月6日http://jkqsyxx.wxedu.net/hqbz/mzsp/content_292408
3.食谱编排一份让厨房更有序的宝贵指南总之,一个高效率、高质量且符合个人需求的地道美味佳肴并不难实现,只要有一张精心规划好的单位 食谱安排表作为指导就可以了。在这样一种系统化策略下,每一次享受美妙用餐时,都会感受到一种前所未有的幸福感,是不是值得我们去尝试呢? 标签:10元做4个家常菜、糖醋排骨怎么做好、春节年夜饭20道菜菜谱图片、100道https://www.78sjdwyn.cn/cai-pu/401535.html
4.第十四周菜单(2024.012.0206)每周菜谱校务公开分享到: 【打印正文】 上一条:第十三周菜单(2024.11.25-29) [ 2024-11-25 ] 下一条:没有了!内容主体:第二实验小学 备案号:苏ICP备06048641号-3 技术支持:苏州工业园区教师发展中心 单位地址:苏州工业园区方洲路666号 邮编:215028 联系电话:0512-65930301https://sip2ps.sipedu.org/xwgk/mzcp/content_933659
5.26道家常菜菜谱大全,勤劳下厨的人先享受美食,每一道菜都很美味【洋葱炒鸡蛋】五块钱的食材,就能吃得非常饱【咖喱鸡腿】就这汁拌饭超级香【青椒炒土豆】大众口味,大家都喜欢吃【毛豆炒肉丝】好吃又便宜,下饭菜【菠萝咕噜肉】酸酸甜甜好味道【土豆炖排骨】超级好吃超级合胃口【双椒炒肉】微微的辣,非常下饭【蒜苔炒鱿鱼】两者搭配非常https://m.163.com/dy/article/JIAKV2AK05568JTI.html
6.家庭一周菜谱安排表家庭一周菜谱安排表范文模板家庭一周菜谱安排表是把人们一周需要吃的食物以餐桌的形式安排好。家庭一周菜谱安排表下载Excel模板免费下载由华军软件园为您提供,源文件为xlsx格式,下载源文件即可自行编辑修改内容样式,更多家庭一周菜谱安排表下载模板免费下载请关注华军软件园。 家庭一周菜谱安排表使用技巧 家庭一周菜谱安排表如何填写? 1.保证https://m.onlinedown.net/soft/10017994.htm
7.食堂菜谱表格模版上传人:1*** IP属地:浙江上传时间:2021-04-27格式:DOCX页数:1大小:14.30KB积分:20版权申诉 全文预览已结束 下载本文档 版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领 文档简介 精品好资料学习推荐星期星期一星期二星期三星期四星期五星期六星期日早餐豆浆、鸡蛋https://www.renrendoc.com/paper/123955670.html
8.卤味美食价目表设计图菜单菜谱广告设计设计图库卤味美食 价目表图片,卤味美食 价目表模板下载,卤味 套餐 卤味套餐 卤味礼盒 卤味菜单,卤味美食 价目表设计素材,昵图网:图片共享和图片交易中心https://www.nipic.com/show/42214289.html
9.复古风米线美食菜谱菜单价格表海报素材下载本作品是张菜单海报设计素材,素材编号为1973334,复古风米线美食菜谱菜单价格表海报格式为PSD,使用Adobe Photoshop创作,欢迎下载更多菜单海报设计模板、菜单海报图片、本菜单海报设计作品上的文字图片均可编辑修改。https://www.ztupic.com/sucai/1973334.html
10.周食谱Excel表格模板周食谱Excel表格模板下载幼儿园一周食谱表execl模板 16414 幼儿园一周食谱Excel模板 15810 学生一周食谱表Excel模板 1285 幼儿园一周健康营养食谱Excel模板 9912 一周营养食谱表Execl模板 844 一周食谱菜单excel模板 495 一周减肥食谱Excel模板 453 实用幼儿园一周食谱表Excel模板 https://www.tukuppt.com/excelmuban/zhoushipu.html
11.亚马逊产品模板表格怎么下载?操作如下:(1)运行Word程序,选择需要打开的Excel文件;(2)如是首次运用Word程序打开Excel XP文件,可能会有“Microsoft Word无法导入指定的格式。这项功能目前尚未安装,是否现在安装?”的提示信息,此时可插入Microsoft Office安装盘进行安装;(3)按照Word程序的提示选择修复整个工作簿还是某个工作表;(4)先将文件中被损坏https://www.lnky.net/zhongyijiudatizhi/36652.html
12.菜单价目表设计菜单价目表模板菜单价目表图片觅知网为您找到1537个原创菜单价目表设计图片,包括菜单价目表图片,菜单价目表素材,菜单价目表海报,菜单价目表背景,菜单价目表模板源文件下载服务,包含PSD、PNG、JPG、AI、CDR等格式素材,更多关于菜单价目表素材、图片、海报、背景、插画、配图、矢量、UI、PS、免抠,https://www.51miz.com/so-sucai/3429266.html
13.续表格式规范(续表)红包 展开详情 麻辣烫在哪里学 彩虹桥 可以在网上查找做麻辣烫的视频,这些视频有做麻辣烫的具体步骤、需要准备的食材、麻辣烫锅底配方等。也可以报麻辣烫方面的厨艺培训班学习,培训班有专业的老师指导。学麻辣烫除了看视频、报培训班之外,还可以看菜谱书籍学习、 展开详情https://edu.iask.sina.com.cn/jy/2JRknwOwGmd.html
14.Springboot美食在线分享平台数据库的逻辑结构设计主要分为两步:第一步将概念设计模型得出的E-R图进行转换成关系模型,然后对转化成的关系模型进行优化。本系统中包含了以上几个E-R模型向关系模型的转换:管理员表、菜谱大类表、收藏信息表、笔记评论表、菜谱信息表、笔记信息表、菜谱小类表和用户信息表。 https://blog.csdn.net/qq_30169035/article/details/134588070
15.食堂菜谱表精品教程系列.pdf关闭预览 想预览更多内容,点击免费在线预览全文 免费在线预览全文 食堂菜谱表 5月1日——5月7 日 周一 早餐 周二 早餐 周三 早餐 周四 早餐 周五 早餐 周六 早餐 周日 早餐 拌粉 地瓜粥 扁肉 白粥 拌面 春卷 豆浆 紫菜汤 花卷 拌面 包子 馒头 豆浆 包子 煎蛋 豆浆 煎蛋 煎蛋 水煮蛋 煎蛋 炒蛋 梅菜笋丝https://max.book118.com/html/2019/1019/8030133073002056.shtm
16.五星小学营养餐食堂财务管理制度17篇(全文)1、食堂每日菜谱公示表 中小学食堂财务管理暂行办法 第10篇 为进一步规范食堂财务管理行为,明晰学校经济关系,维护师生的合法权益,学校各项工作的健康发展,根据《行政事业单位财务规则》和《平顺县中小学食堂管理暂行办法》,结合我校食堂管理的实际,特制订以下实施方案。 https://www.99xueshu.com/w/filelsukl3pn.html
17.中文版InDesignCS6技术大全9.2.11 编辑表头/表尾 555 实战演练:销售单的制作 556 9.3 设置表的格式 561 9.3.1 调整列、行和表的大小 562 9.3.2 将表拆分到几个框架中 563 9.3.3 设置表中文本的格式 563 练习:合并和拆分单元格 565 9.3.4 处理溢流单元格 565 练习:合并和拆分单元格 566 https://www.epubit.com/bookDetails?id=N26644