Flutter 页面间数据传递(共享)的几种常用方式
前言
在Android中,我们常遇到的场景就是在页面跳转(Frament,Activity)时候,要将当前的部分数据携带到另外一个页面中,供另外页面使用。这时候我们常用的就是使用Intent, Bundle等携带数据。那么在Flutter的开发过程中,页面之间的数据传递也是必不可少的,又是怎么把一个页面的数据传递(共享)给另外一个页面,或者关闭当前页面并把当前页面的数据带给前一个页面。
本篇文章将会介绍Flutter中,页面面之间的数据传递(共享)的几种常见方式及场景。
在开始数据传递之前我们先创建一个传递数据的类
在Android中传递对象我们需要序列化实现Serializable
或者Parcelable
接口才能被传递,在Flutter中数据传递没有序列化的方法,直接就可以传递对象。定义一个简单的类如下:
///用来传递数据的实体 class TransferDataEntity { String name; String id; int age; TransferDataEntity(this.name, this.id, this.age); }
我们具体看看数据传递的方式
通过构造器(constructor)传递数据
通过构造器传递数据是一种最简单的方式,也是最常用的方式,在第一个页面,我们模拟创建一个我们需要传递数据的对象。当点击跳转的时候,我们把数据传递给DataTransferByConstructorPage页面,并把携带过来的数据展示到页面上。
创建一个传递数据对象
final data = TransferDataEntity("001", "张三丰", 18);
定义一个跳转到DataTransferByConstructorPage页面的方法
_transferDataByConstructor(BuildContext context, TransferDataEntity data) { Navigator.push( context, MaterialPageRoute( builder: (context) => DataTransferByConstructorPage(data: data))); }
在DataTransferByConstructorPage页面接收到数据并展示出来,代码如下
我们只需要做两件事:
1.提供一个final变量
final TransferDataEntity data
2.提供一个构造器接收参数
DataTransferByConstructorPage({this.data});
///通过构造器的方式传递参数 class DataTransferByConstructorPage extends StatelessWidget { final TransferDataEntity data; DataTransferByConstructorPage({this.data}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("构造器方式"), ), body: Column( children: <Widget>[ Container( width: double.infinity, height: 40.0, alignment: Alignment.center, child: Text(data.id), ), Container( width: double.infinity, height: 40.0, alignment: Alignment.center, child: Text(data.name), ), Container( width: double.infinity, height: 40.0, alignment: Alignment.center, child: Text("${data.age}"), ) ], ), ); } }
当一个页面关闭时携带数据到上一个页面(Navigator.pop)
在Android开发中我们需要将数据传递给上一个页面通常使用的传统方式是startActivityForResult()方法。但是在flutter就不用这么麻烦了。只需要使用Navigator.pop方法即可将数据结果带回去。但是我们跳转的时候需要注意两点:
1.我们需要定义一个异步方法用于接收返回来的结果
///跳转的时候我们需要使用异步等待回调结果 dataFromOtherPage 就是返回的结果 _toTransferForResult(BuildContext context, TransferDataEntity data) async { final dataFromOtherPage = await Navigator.push( context, MaterialPageRoute(builder: (context) => TransferRouterPage(data: data)), ) as TransferDataEntity; }
2.在我们要关闭的页面使用Navigator.pop 返回第一个页面
//返回并携带数据 _backToData(BuildContext context){ var transferData = TransferDataEntity("嘻嘻哈哈","007",20); Navigator.pop(context,transferData); }
InheritedWidget方式
官网给出的解释:InheritedWidget是Flutter中非常重要的一个功能型Widget,它可以高效的将数据在Widget树中向下传递、共享,这在一些需要在Widget树中共享数据的场景中非常方便,如Flutter中,正是通过InheritedWidget来共享应用主题(Theme)和Locale(当前语言环境)信息的。InheritedWidget和React中的context功能类似,和逐级传递数据相比,它们能实现组件跨级传递数据。InheritedWidget的在Widget树中数据传递方向是从上到下的,这和Notification的传递方向正好相反。
优点:可以控制每个Widget单独去取数据并使用
如果一个页面只存在同一层级的Weiget这时候使用构造器的方式当然是最简单的,也是最方便的,但是如果一个页面存在多个层级的Weiget这时候构造器的方法就有了局限性,这时候我们使用InheritedWidget 是一个比较好的选择。
使用InheritedWidget
方式如下几步:
继承InheritedWidget提供一个数据源
class IDataProvider extends InheritedWidget{ final TransferDataEntity data; IDataProvider({Widget child,this.data}):super(child:child); @override bool updateShouldNotify(IDataProvider oldWidget) { return data!=oldWidget.data; } static IDataProvider of(BuildContext context){ return context.inheritFromWidgetOfExactType(IDataProvider); } }
定义页面跳转时候携带数据的方法
///跳转到IDataWidget页面并携带数据 _inheritedToPage(BuildContext context, TransferDataEntity data) { Navigator.push( context, MaterialPageRoute( builder: (context) => IDataProvider( child: IDataWidget(), data: data, ))); }
跳转的到的页面并展示数据代码如下
class IDataWidget extends StatelessWidget { @override Widget build(BuildContext context) { final data = IDataProvider.of(context).data; return Scaffold( appBar: AppBar( title: Text("Inherited方式传递数据"), ), body: Column( children: <Widget>[ Container( alignment: Alignment.center, height: 40.0, child: Text(data.name), ), Container( alignment: Alignment.center, height: 40.0, child: Text(data.id), ), Container( alignment: Alignment.center, height: 40.0, child: Text("${data.age}"), ), IDataChildWidget() ], ), ); } } class IDataChildWidget extends StatelessWidget { @override Widget build(BuildContext context) { final data = IDataProvider.of(context).data; return Container( child: Text(data.name), ); } }
我们将上面的IDataProvier
进行改造加入泛型就可以通用了
1.修改后的Provider类如下
class IGenericDataProvider<T> extends InheritedWidget { final T data; IGenericDataProvider({Key key, Widget child, this.data}) : super(key: key, child: child); @override bool updateShouldNotify(IGenericDataProvider oldWidget) { return data != oldWidget.data; } static T of<T>(BuildContext context) { return (context.inheritFromWidgetOfExactType( IGenericDataProvider<T>().runtimeType) as IGenericDataProvider<T>).data; } }
2.使用跳转的时候修改代码如下(主要是添加泛型支持)
_inheritedGenericToPage(BuildContext context, TransferDataEntity data) { Navigator.push( context, MaterialPageRoute( builder: (context) => IGenericDataProvider<TransferDataEntity>( child: IDataWidget(), data: data, ))); }
接收传递的值的方式如下IGenericDataProvider.of<TransferDataEntity>(context) 可以直接取值
class IGenericDataWidget extends StatelessWidget { @override Widget build(BuildContext context) { final data = IGenericDataProvider.of<TransferDataEntity>(context); return Scaffold( appBar: AppBar( title: Text("Inherited泛型方式传递数据"), ), body: Column( children: <Widget>[ Container( alignment: Alignment.center, height: 40.0, child: Text(data.name), ), Container( alignment: Alignment.center, height: 40.0, child: Text(data.id), ), Container( alignment: Alignment.center, height: 40.0, child: Text("${data.age}"), ), ], ), ); } }
全局的提供数据的方式
这种方式我们还是使用InheritedWidget,区别就是我们不是跳转的时候去创建IGenericDataProvider。而是把他放在最顶层注意:这种方式一定要把数据放在顶层
定义顶部数据
class MyApp extends StatelessWidget { // This widget is the root of your application. //传递值的数据 var params = InheritedParams(); @override Widget build(BuildContext context) { return IGenericDataProvider( data: params, child: MaterialApp( title: 'Data Transfer Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Data Transfer Demo'), ), ); } }
接收数据的方式基本和InheritedWidget
相同final data = IGenericDataProvider.of<TransferDataEntity>(context),获取数据
class InheritedParamsPage extends StatefulWidget { @override _InheritedParamsPageState createState() => _InheritedParamsPageState(); } class _InheritedParamsPageState extends State<InheritedParamsPage> { @override Widget build(BuildContext context) { final data = IGenericDataProvider.of<TransferDataEntity>(context); return Scaffold( appBar: AppBar( title: Text("通过全局数据方式"), ), body:Column( children: <Widget>[ Container( alignment: Alignment.center, height: 40.0, child: Text(data.name), ), Container( alignment: Alignment.center, height: 40.0, child: Text(data.id), ), Container( alignment: Alignment.center, height: 40.0, child: Text("${data.age}"), ), ], ), ); } }
通过全局单例模式来使用
这种方式就是创建一个全局单例对象,任何地方都可以操控这个对象,存储和取值都可以通过这个对象
- 创建单例对象
class TransferDataSingleton { static final TransferDataSingleton _instanceTransfer = TransferDataSingleton.__internal(); TransferDataEntity transData; factory TransferDataSingleton() { return _instanceTransfer; } TransferDataSingleton.__internal(); } final transSingletonData = TransferDataSingleton();
- 给单例对象存放数据
_singletonDataTransfer(BuildContext context) { var transferData = TransferDataEntity("二汪", "002", 25); transSingletonData.transData = transferData; Navigator.push(context, MaterialPageRoute(builder: (context) => TransferSingletonPage())); }
- 接收并使用传递的值
class TransferSingletonPage extends StatefulWidget { @override _TransferSingletonPageState createState() => _TransferSingletonPageState(); } class _TransferSingletonPageState extends State<TransferSingletonPage> { @override Widget build(BuildContext context) { //直接引入单例对象使用 var data = transSingletonData.transData; return Scaffold( appBar: AppBar( title: Text("全局单例传递数据"), ), body: Column( children: <Widget>[ Container( alignment: Alignment.center, height: 40.0, child: Text(data.name), ), Container( alignment: Alignment.center, height: 40.0, child: Text(data.id), ), Container( alignment: Alignment.center, height: 40.0, child: Text("${data.age}"), ), ], ), ); } }
全局单例结合Stream的方式传递数据
- 创建一个接受全局的单例对象,并把传递值转成Stream方式
- 在接收数据可以使用StreamBuilder直接接收并处理
class TransferStreamSingleton { static final TransferStreamSingleton _instanceTransfer = TransferStreamSingleton.__internal(); StreamController streamController; void setTransferData(TransferDataEntity transData) { streamController = StreamController<TransferDataEntity>(); streamController.sink.add(transData); } factory TransferStreamSingleton() { return _instanceTransfer; } TransferStreamSingleton.__internal(); } final streamSingletonData = TransferStreamSingleton();
- 传递要携带的数据
_streamDataTransfer(BuildContext context) { var transferData = TransferDataEntity("三喵", "005", 20); streamSingletonData.setTransferData(transferData); Navigator.push(context, MaterialPageRoute(builder: (context) => TransferStreamPage())); }
- 接收要传递的值
class TransferStreamPage extends StatefulWidget { @override _TransferStreamPageState createState() => _TransferStreamPageState(); } class _TransferStreamPageState extends State<TransferStreamPage> { StreamController _streamController = streamSingletonData.streamController; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("全局单例结合Stream"), ), body: StreamBuilder( stream: _streamController.stream, initialData: TransferDataEntity("", "", 0), builder: (context, snapshot) { return Column( children: <Widget>[ Container( alignment: Alignment.center, height: 40.0, child: Text(snapshot.data.name), ), Container( alignment: Alignment.center, height: 40.0, child: Text(snapshot.data.id), ), Container( alignment: Alignment.center, height: 40.0, child: Text("${snapshot.data.age}"), ), ], ); })); } @override void dispose() { _streamController.close(); super.dispose(); } }
总结
以上是我们在在Flutter中常用的几种页面之间传递数据的方式,其中最后一种方式提到了Stream
和StreamBuilder
我有一篇文章专门介绍了Flutter
的Stream
。Flutter Stream简介及使用 详细的介绍了Stream
及部分操作的使用。现在官方推荐的provider实际上就是使用了
InheritedWidget
有时间的话建议详细了下InheritedWidget
及使用方法。以上是对页面之间值传递的一个总结,本文Demo,如有写的不足之处,望指正~