我试图理解在Widgets State之外控制StatefulWidget状态的最佳实践。
我定义了以下界面。
abstract class StartupView {
Stream<String> get onAppSelected;
set showActivity(bool activity);
set message(String message);
}
我想创建一个实现此接口的StatefulWidget StartupPage
。我希望Widget能够做到以下几点:
按下按钮时,它将通过onAppSelected流发送事件。控制器会监听此操作并执行一些操作(数据库调用,服务请求等)。
控制人员可以致电showActivity
或set message
让视图显示进度并显示消息。
由于Stateful Widget不会将其作为属性公开,因此我不知道访问和修改State属性的最佳方法。
我希望使用它的方式如下:
Widget createStartupPage() {
var page = new StartupPage();
page.onAppSelected.listen((app) {
page.showActivity = true;
//Do some work
page.showActivity = false;
});
}
我已经考虑通过传递我希望它在createState()
中返回的状态来实例化Widget,但这感觉不对。
我们采用这种方法的一些背景:我们目前有一个Dart Web应用程序。对于视图 - 控制器分离,可测试性和对Flutter的前瞻性思考,我们决定为应用程序中的每个视图创建一个接口。这将允许WebComponent或Flutter Widget实现此接口并使所有控制器逻辑保持不变。
答案 0 :(得分:31)
有多种与其他有状态小部件进行交互的方式。
1。 ancestorStateOfType
第一个也是最直接的方法是通过context.ancestorStateOfType
方法。
通常包裹在Stateful
子类的静态方法中,如下所示:
class MyState extends StatefulWidget {
static of(BuildContext context, {bool root = false}) => root
? context.rootAncestorStateOfType(const TypeMatcher<_MyStateState>())
: context.ancestorStateOfType(const TypeMatcher<_MyStateState>());
@override
_MyStateState createState() => _MyStateState();
}
class _MyStateState extends State<MyState> {
@override
Widget build(BuildContext context) {
return Container();
}
}
例如Navigator
的工作方式。
专业人士:
骗局:
State
属性或手动调用setState
State
子类要访问变量时不要使用此方法。当该变量更改时,您的窗口小部件可能不会重新加载。
2。 Listenable,Stream和/或InheritedWidget
有时您可能想要访问某些属性,而不是方法。问题是,只要该值随时间变化,您很可能希望您的小部件进行更新。
在这种情况下,飞镖提供Stream
和Sink
。颤动会在其顶部添加InheritedWidget
和Listenable
,例如ValueNotifier
。它们都做相对相同的事情:在与StreamBuilder
/ context.inheritFromWidgetOfExactType
/ AnimatedBuilder
结合时订阅值更改事件。
当您希望State
公开一些属性时,这是首选解决方案。我不会介绍所有可能性,但这是一个使用InheritedWidget
的小例子:
首先,我们有一个InheritedWidget
公开了count
:
class Count extends InheritedWidget {
static of(BuildContext context) =>
context.inheritFromWidgetOfExactType(Count);
final int count;
Count({Key key, @required Widget child, @required this.count})
: assert(count != null),
super(key: key, child: child);
@override
bool updateShouldNotify(Count oldWidget) {
return this.count != oldWidget.count;
}
}
然后我们的State
实例化了此InheritedWidget
class _MyStateState extends State<MyState> {
int count = 0;
@override
Widget build(BuildContext context) {
return Count(
count: count,
child: Scaffold(
body: CountBody(),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
count++;
});
},
),
),
);
}
}
最后,我们有CountBody
来获取暴露的count
class CountBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Text(Count.of(context).count.toString()),
);
}
}
优点:
ancestorStateOfType
的表现更好await for
或async*
之类的关键字)紧密集成缺点:
3。通知
您可以从窗口小部件发送State
,而不是直接在Notification
上调用方法。并使State
订阅这些通知。
Notification
的示例为:
class MyNotification extends Notification {
final String title;
const MyNotification({this.title});
}
要发送通知,只需在通知实例上调用dispatch(context)
,它就会冒泡。
MyNotification(title: "Foo")..dispatch(context)
任何给定的小部件都可以使用NotificationListener<T>
收听其子级发送的通知:
class _MyStateState extends State<MyState> {
@override
Widget build(BuildContext context) {
return NotificationListener<MyNotification>(
onNotification: onTitlePush,
child: Container(),
);
}
void onTitlePush(MyNotification notification) {
print("New item ${notification.title}");
}
}
示例为Scrollable
,它可以调度ScrollNotification
,包括开始/结束/过度滚动。然后由Scrollbar
用来了解滚动信息,而无需访问ScrollController
优点:
State
上做任何事情。 State
订阅了由其子级触发的事件State
属性缺点:
答案 1 :(得分:14)
您可以使用静态方法公开状态的窗口小部件,一些颤动的示例以这种方式执行,我也开始使用它:
class StartupPage extends StatefulWidget {
static _StartupPageState of(BuildContext context) => context.ancestorStateOfType(const TypeMatcher<_StartupPageState>());
@override
_StartupPageState createState() => new _StartupPageState();
}
class _StartupPageState extends State<StartupPage> {
...
}
然后,您可以通过调用StartupPage.of(context).doSomething();
来访问该州。
这里需要注意的是,你需要在树的某个地方有一个BuildContext。
答案 2 :(得分:5)
我愿意:
class StartupPage extends StatefulWidget {
StartupPageState state;
@override
StartupPageState createState() {
this.state = new StartupPageState();
return this.state;
}
}
class DetectedAnimationState extends State<DetectedAnimation> {
在startupPage.state
之外
答案 3 :(得分:1)
您是否考虑过将状态提升到父窗口小部件?据我所知,这是一种常见的,虽然不如Redux理想的方式来管理React中的状态,而repository显示了如何将该概念应用于Flutter应用程序。
答案 4 :(得分:1)
在尝试解决类似问题时,我发现ancestorStateOfType()
和TypeMatcher
已被弃用。相反,必须使用findAncestorStateOfType()
。但是,根据文档,“调用此方法相对昂贵”。 findAncestorStateOfType()
方法的文档可以在here中找到。
无论如何,要使用findAncestorStateOfType()
,可以实现以下目的(这是使用findAncestorStateOfType()
方法对正确答案的修改):
class StartupPage extends StatefulWidget {
static _StartupPageState of(BuildContext context) => context.findAncestorStateOfType<_StartupPageState>();
@override
_StartupPageState createState() => new _StartupPageState();
}
class _StartupPageState extends State<StartupPage> {
...
}
可以使用与正确答案中所述相同的方式来访问状态(使用StartupPage.of(context).yourFunction()
)。我想用新方法更新帖子。
答案 5 :(得分:0)
还有另一种常用的方法可以访问国家的属性/方法:
class StartupPage extends StatefulWidget {
StartupPage({Key key}) : super(key: key);
@override
StartupPageState createState() => StartupPageState();
}
// Make class public!
class StartupPageState extends State<StartupPage> {
int someStateProperty;
void someStateMethod() {}
}
// Somewhere where inside class where `StartupPage` will be used
final startupPageKey = GlobalKey<StartupPageState>();
// Somewhere where the `StartupPage` will be opened
final startupPage = StartupPage(key: startupPageKey);
Navigator.push(context, MaterialPageRoute(builder: (_) => startupPage);
// Somewhere where you need have access to state
startupPageKey.currentState.someStateProperty = 1;
startupPageKey.currentState.someStateMethod();
答案 6 :(得分:0)
您可以使用 eventify
该库提供了向发射器注册事件通知的机制 或发布者,并在发生事件时得到通知。
您可以执行以下操作:
// Import the library
import 'package:eventify/eventify.dart';
final EventEmitter emitter = new EventEmitter();
var controlNumber = 50;
List<Widget> buttonsGenerator() {
final List<Widget> buttons = new List<Widget>();
for (var i = 0; i < controlNumber; i++) {
widgets.add(new MaterialButton(
// Generate 10 Buttons afterwards
onPressed: () {
controlNumber = 10;
emitter.emit("updateButtonsList", null, "");
},
);
}
}
class AState extends State<ofYourWidget> {
@override
Widget build(BuildContext context) {
List<Widget> buttons_list = buttonsGenerator();
emitter.on('updateButtonsList', null, (event, event_context) {
setState(() {
buttons_list = buttonsGenerator();
});
});
}
...
}
我想不出事件驱动编程无法实现的任何事情。你是无限的!
“不能赋予自由-必须实现自由。” -埃尔伯特·哈伯德(Elbert Hubbard)
答案 7 :(得分:0)
对于可能需要此解决方案的其他人。以下是对@santi 回复的贡献:
class StartupPage extends StatefulWidget {
final StartupPageState _state = StartupPageState();
@override
StartupPageState createState() {
return _state;
}
}
这确保仅创建状态的单个实例。
我决定将 _state
设为私有,这样您就不会将其所有属性暴露给外部类。如果您想使用其中的特定字段,我建议您使用封装来访问/修改它(使用 _state
变量)。
答案 8 :(得分:-1)
我没有像大多数答案所说的那样向所有人公开整个MailApp
,而是建议公开一种与自己的State
交互的方法。这样,与执行工作的State
交互的Widget
不会知道Widget
是无状态的还是有状态的。很干净。
只需在Widget
中创建一个方法,如下所示:
Widget