Flutter中的反应式应用程序状态

我已经和Flutter玩了几个星期,可以说,这非常好,这要感谢Flutter和Dart团队。 但是,当我开始在Flutter中破解演示应用程序时,遇到了几个问题:

  1. 如何将应用程序的状态传递到小部件树
  2. 更新应用程序状态后如何重建小部件

因此,让我们从第一个问题“如何传递应用程序的状态”开始。 我将在Flutter的标准“ Counter”演示应用程序的帮助下展示我的解决方案。 创建这样的应用程序很容易:我们只需要在终端中键入“ flutter create myapp”(“ myapp” —是我们的演示应用程序的名称)。

之后,我们应该打开“ main.dart”文件,并使“ MyHomePage”小部件为“无状态”:

 导入'package:flutter / material.dart';  var _counter = 0; 
...
类MyHomePage扩展了StatelessWidget {
MyHomePage({Key key,this.title}):超级(key:key);
最终的字符串标题; @override

小部件build(BuildContext context) {
返回新的脚手架(
appBar:新的AppBar(
标题:新文本(
标题 ),
),
正文:新中心(
子:新列(
mainAxisAlignment:MainAxisAlignment.center,
子代: [
新文字(
“您已经多次按下按钮:”,
),
新文字(
'$ _counter',
样式:Theme.of(context).textTheme.display1,
),
],
),
),
floatActionButton:新的FloatingActionButton(
onPressed:_incrementCounter,
工具提示:“增量”,
子级:new Icon(Icons.add),
),
);

} _incrementCounter(){}
}

我们只是将“ build”方法从“ MyHomePageState”移动到“ MyHomePage”小部件,然后在其中创建空方法“ _incrementCounter”,并在文件顶部创建var“ _counter”。 现在,我们可以重新加载我们的应用程序,并看到屏幕上除了“ +”按钮以外没有任何变化–现在它不起作用。 没关系,因为现在我们的小部件是无状态的。

让我们考虑一下提供应用程序状态的小部件的外观:

  MyApp类扩展StatelessWidget { 
@override
小部件build(BuildContext context){
返回
新提供者(

数据:_counter
儿童
:新的MaterialApp(
标题:“ Flutter演示”,
主题:新的ThemeData(
primarySwatch:Colors.blue,
),
主页:新的MyHomePage(标题:“ Flutter演示首页”),
),

);
}
}

在这里,我们可以看到包装了整个应用程序的新小部件“ Provider”。 它具有两个属性:“数据”(包含我们应用程序的状态)和“子”(后代)。 同样,我们应该有可能从树上的任何小部件中获取此数据,但我们稍后会考虑。 现在让我们为新的小部件写下简单的实现。

首先,让我们通过“ main.dart”创建一个新的dart文件“ Provider.dart”,在该文件中将实现“ Provider”小部件的实现。

现在,我们将“ Provider”创建为“ Stateless”小部件:

 导入'package:flutter / widgets.dart';  提供者的类扩展了StatelessWidget { 
const Provider({this.data,this.child});
最终数据;
最后一个孩子
@override
小部件build(BuildContext context)=> child;
}

是的,非常简单。 现在,将“ Provider”导入“ main.dart”

 导入'package:flutter / material.dart';  导入'package:myapp / Provider.dart'; 

并重建我们的应用程序以检查所有程序是否正常运行。 如果所有“工作”都可行,那就让我们进一步。 现在我们有一个容器来保存应用程序的状态,我们可以回到有关如何从该容器中检索数据的问题。 幸运的是,Flutter已经有了一个解决方案,它是“ InheritedWidget”。 该文档对此进行了很好的描述:

在树上有效传播信息的小部件的基类。

这正是我们所需要的。 让我们创建“继承”窗口小部件。

打开“ Provider.dart”并创建私有的“ _InheritedProvider”小部件:

  _InheritedProvider类扩展了InheritedWidget { 
_InheritedProvider({this.data,this.child})
:super(child:child);
最终数据;
最后一个孩子
@override
bool updateShouldNotify(_InheritedProvider oldWidget){
返回数据!= oldWidget.data;
}
}

“ InheritedWidget”的所有子类都应实现“ updateShouldNotify”方法。 此时,我们只需检查传递的“数据”是否已更改。 例如,当我们将计数器从“ 0”更改为“ 1”时,此方法应返回“ true”。

现在让我们将“继承的”小部件添加到小部件树中:

 提供者的类扩展了StatelessWidget { 
...
@override
小部件构建(BuildContext上下文)
{

返回 new _InheritedProvider(data:data,child: child ;

}
...
}

好的,现在我们有了在窗口小部件树中传播数据的窗口小部件,但是我们应该创建一个公共方法来获取此数据:

 提供者的类扩展了StatelessWidget { 
...
静态(BuildContext上下文){
_InheritedProvider p =
context.inheritFromWidgetOfExactType(_InheritedProvider);
返回p.data;
}

...
}

“ inheritFromWidgetOfExactType”方法获取“ _InheritedProvider”类型实例的最近父控件。

现在,我们拥有解决第一个问题的一切:

  MyApp类扩展StatelessWidget { 
@override
小部件build(BuildContext context){
返回新的提供者(
数据:
0
...
}
...
}
...类MyHomePage扩展了StatelessWidget {
...
小部件build(BuildContext context){

var _counter = Provider.of(context);
...
}

我们已经删除了全局变量“ _counter”,并使用“ Provider.of”在“ MyHomePage”小部件中获得了“ counter”。 如您所见,我们没有将其作为参数传递给“ MyHomePage”,而是使用了“ Provider.of”来获取应用程序的状态,该状态可以应用于树上的任何小部件。 除此之外,“ Provider.of”还会注册当前窗口小部件的上下文,并在更改“ _InheritedProvider”窗口小部件时对其进行重建。

现在是时候检查我们的应用程序是否仍然有效:让我们重新加载它。 为了确保我们的“提供程序”正常运行,我们可以在“ MyApp”小部件中将“数据”从“ 0”更改为“ 1”,然后我们必须重新加载应用程序。 但是,我们的“ +”按钮仍然不起作用。

在这里,我们面临第二个问题“在更改应用程序状态后如何重建窗口小部件”,现在我们应该重新考虑一下。

我们应用的状态只是一个数字,但是要更改此数字的时间并不容易。 如果我们将“计数器”数字包装到“可观察”的对象中,该对象将跟踪更改并将这些更改通知“侦听器”,该怎么办。

幸运的是,Flutter已经有了一个解决方案,它是“ ValueNotifier”。 与往常一样,这是文档中的一个很好的解释:

替换value时,此类将通知其侦听器。

好的,让我们在“ mian.dart”中创建应用程序的状态类:

 导入'package:flutter / material.dart'; 
导入'package:myapp / Provider.dart';
AppState类扩展ValueNotifier {
AppState(值):超级(值);
}
var appState = new AppState(0);

并将其传递给“提供商”

 小部件build(BuildContext context){ 
返回新的提供者(
数据:
appState

由于“数据”包含一个对象,我们应该更改“ Provider.of(context)”的用法,所以让我们这样做:

 小部件build(BuildContext context){ 
var _counter = Provider.of(上下文)
.value ;

重建我们的应用程序,并确保没有错误。

现在我们准备实现“ _incrementCounter”:

  floatActionButton:新的FloatingActionButton( 
onPressed:
()=> _incrementCounter(context)
工具提示:“增量”,
子级:new Icon(Icons.add),
),
);
}
_incrementCounter( 上下文 ){

var appState = Provider.of(context);
appState.value + = 1;

}

让我们重新加载应用程序,然后尝试按“ +”按钮。 一切都没有改变,但是如果我们运行“ Hot reload”,我们将看到文本已经改变。 发生这种情况是因为我们在按下按钮后更改了应用程序的状态。 但是,由于尚未重建小部件,因此我们在屏幕上看到了旧状态。 一旦运行“ Hot reload”小部件,便会重新构建,我们可以在屏幕上看到实际状态。

最后的挑战是在更改应用程序的状态后重建小部件。 但是在此之前,让我们看一下我们已经拥有的东西:

  1. “提供商”-我们应用状态的容器
  2. “ AppState”-跟踪应用程序状态变化并通知“监听器”的类
  3. “ _InheritedProvider” —一种小部件,可以在树中有效传播应用程序的状态,并在更改其自身状态后重建使用者。

首先,让我们回顾一下“ _InheritedProvider”的“ updateShouldNotify”方法:

  @override 
bool updateShouldNotify(_InheritedProvider oldWidget){
返回数据!= oldWidget.data;
}

现在,“数据”等于“ AppState”的实例,这意味着当我们在“ _incrementCounter”方法中更改此实例的“值”时,实际上并不会更改实例本身。 因此,此比较始终返回“ false”。 让我们通过比较“值” -s来解决此问题。 但是为此,我们应该将“值”保存在小部件中,以使我们在重新构建之间不会丢失“值”:

  _InheritedProvider类扩展了InheritedWidget { 
_InheritedProvider({this.data,this.child})

_dataValue = data.value
超级(孩子:孩子);
最终数据;
最后一个孩子

final _dataValue; @override
bool updateShouldNotify(_InheritedProvider oldWidget){
返回
_dataValue!= oldWidget._dataValue ;
}
}

现在它可以正常工作:当我们更改状态的值时,小部件将重建使用者。 但是在重建使用者之前,我们应该在更改应用程序状态后重建小部件本身。

我们的代码中只有一个地方知道“ _InheritedProvider”小部件,这就是“ Provider”小部件。 如果我们要跟踪小部件中的某些状态,则应创建“ statefull”小部件。 好的,让我们将“ Provider”小部件从“ stateless”转换为“ statefull”:

 提供者的类扩展了StatefulWidget { 
const Provider({this.child,this.data});
静态(BuildContext上下文){
_InheritedProvider p =
context.inheritFromWidgetOfExactType(_InheritedProvider);
返回p.data;
}
最终的ValueNotifier数据;
最终的Widget子;
@override
State createState()=>新的_ProviderState();
}
_ProviderState类扩展State {
@override
小部件build(BuildContext context){
返回新的_InheritedProvider(
数据:widget.data,
孩子:widget.child,
);
}
}
_InheritedProvider类扩展了InheritedWidget {
_InheritedProvider({this.data,this.child})

现在让我们“订阅”应用程序的状态更改,并在更改后调用“ setState ”:

  _ProviderState类扩展State  { 

@override
initState(){
super.initState();
widget.data.addListener(didValueChange);
}
didValueChange()=> setState((){});

并且不要忘记在小部件销毁后删除垃圾:

  _ProviderState类扩展State  { 
...
@override
dispose(){
widget.data.removeListener(didValueChange);
super.dispose();
}
...
}

让我们重建应用程序并检查其工作方式。 欢呼,现在,当我们按下“ +”按钮时,我们的应用程序状态将更改,并且小部件将重新构建。

让我们检查一下我们的问题:

  1. 如何将应用程序的状态传递到小部件树—已解决
  2. 应用状态更改后如何重建小部件—已解决

您可以在这里找到源代码-https://gist.github.com/c88f116d7d65d7222ca673b5f9c5bcc3

结论。

本文的总体思路是展示如何在Flutter中实现“ redux”模式而无需外部软件包。 Flutter已经有实现“ redux”模式的软件包,但有时它们不适合您的体系结构,很高兴知道如何用自己的双手从头开始实现某些东西✋。

谢谢,

快乐的编码和愉快的发展!1

Dart的不变性简介

不可否认,不变性是编程(尤其是前端编程)中的热门话题。 图书馆…

medium.com

继承小部件

Flutter小部件树可能很深……

medium.com