Flutter-页面间数据传递共享的几种常用方式

20次阅读

共计 9256 个字符,预计需要花费 24 分钟才能阅读完成。

前言

    在 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 中常用的几种页面之间传递数据的方式,其中最后一种方式提到了 StreamStreamBuilder我有一篇文章专门介绍了 FlutterStream。Flutter Stream 简介及使用 详细的介绍了 Stream 及部分操作的使用。

    现在官方推荐的 provider 实际上就是使用了 InheritedWidget 有时间的话建议详细了下 InheritedWidget 及使用方法。

    以上是对页面之间值传递的一个总结,本文 Demo,如有写的不足之处,望指正~

正文完
 0