从StatefulWidget外部控制状态

Mat*_*ith 33 flutter

我试图理解在Widgets State之外控制StatefulWidget状态的最佳实践.

我定义了以下接口.

abstract class StartupView {
  Stream<String> get onAppSelected;

  set showActivity(bool activity);
  set message(String message);
}
Run Code Online (Sandbox Code Playgroud)

我想创建一个StartupPage实现此接口的StatefulWidget .我希望Widget能够执行以下操作:

  1. 按下按钮时,它将通过onAppSelected流发送事件.控制器甚至会监听它并执行一些操作(db调用,服务请求等).

  2. 控制器可以调用showActivityset message使视图显示消息进度.

因为有状态窗口小部件不会将其状态作为属性公开,所以我不知道访问和修改状态属性的最佳方法.

我希望使用它的方式如下:

Widget createStartupPage() {
    var page = new StartupPage();
    page.onAppSelected.listen((app) {
      page.showActivity = true;
      //Do some work
      page.showActivity = false;
    });
  }
Run Code Online (Sandbox Code Playgroud)

我已经考虑通过传递我希望它返回的状态来实例化Widget createState()但是感觉不对.

关于我们为什么采用这种方法的一些背景:我们目前有一个Dart Web应用程序.对于视图 - 控制器分离,可测试性和对Flutter的前瞻性思考,我们决定为应用程序中的每个视图创建一个接口.这将允许WebComponent或Flutter Widget实现此接口并使所有控制器逻辑保持不变.

Rém*_*let 51

有多种方法可以与其他有状态小部件进行交互.

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();
  }
}
Run Code Online (Sandbox Code Playgroud)

这是如何Navigator工作的例子.

优点:

  • 最简单的解决方案

缺点:

  • 试图访问State属性或手动调用setState
  • 需要公开State子类

要访问变量时,请勿使用此方法.当变量发生变化时,您的窗口小部件可能无法重新加载

2.可监听,流和/或InheritedWidget

有时候您可能想要访问某些属性而不是方法.问题是,当该值随时间变化时,您很可能希望您的小部件更新.

在这种情况下,飞镖报价StreamSink.而扑在它的顶部增加InheritedWidgetListenableValueNotifier.它们都做了相对相同的事情:与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;
  }
}
Run Code Online (Sandbox Code Playgroud)

然后我们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++;
            });
          },
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

最后,我们得到了CountBody这个暴露的count

class CountBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(Count.of(context).count.toString()),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

优点:

  • 比表现更高效 ancestorStateOfType
  • 流替换只是dart(适用于web)并且强烈集成在语言中(关键字如await foror async*)
  • 当值改变时,孩子们自动重新加载

缺点:

  • 更多样板
  • 流可能很复杂

3.通知

State您可以Notification从窗口小部件发送a,而不是直接调用方法.并State订阅这些通知.

一个例子Notification是:

class MyNotification extends Notification {
  final String title;

  const MyNotification({this.title});
}
Run Code Online (Sandbox Code Playgroud)

要发送通知,只需调用dispatch(context)您的通知实例,它就会冒出来.

MyNotification(title: "Foo")..dispatch(context)
Run Code Online (Sandbox Code Playgroud)

任何给定的窗口小部件都可以使用以下命令监听其子项发送的通知NotificationListener<T>:

class _MyStateState extends State<MyState> {
  @override
  Widget build(BuildContext context) {
    return NotificationListener<MyNotification>(
      onNotification: onTitlePush,
      child: Container(),
    );
  }

  bool onTitlePush(MyNotification notification) {
    print("New item ${notification.title}");
    // true meaning processed, no following notification bubbling.
    return true;
  }
}
Run Code Online (Sandbox Code Playgroud)

一个例子是Scrollable,可以发送ScrollNotification包括开始/结束/过度滚动.然后用来Scrollbar知道滚动信息而无法访问ScrollController

优点:

  • 酷反应API.我们不直接做什么State.这State是订阅由其子女触发的事件
  • 多个窗口小部件可以订阅同一个通知
  • 防止儿童访问不需要的State属性

缺点:

  • 可能不适合您的用例
  • 需要更多样板

  • 这是一个很棒的答案! (8认同)
  • 不推荐使用祖先状态类型 (3认同)

xqw*_*zts 21

您可以使用静态方法公开状态的窗口小部件,一些颤动的示例以这种方式执行,我也开始使用它:

class StartupPage extends StatefulWidget {
  static _StartupPageState of(BuildContext context) => context.ancestorStateOfType(const TypeMatcher<_StartupPageState>());

  @override
  _StartupPageState createState() => new _StartupPageState();
}

class _StartupPageState extends State<StartupPage> {
  ...
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以通过呼叫访问该状态StartupPage.of(context).doSomething();.

这里需要注意的是,您需要在其树中的某个位置具有BuildContext.

  • 问题在于,`createState()`是flutter调用的生命周期方法,因此您不应该自己调用它并存储返回的状态。 (2认同)
  • 我看到这种方法的问题是它需要我们的通用控制器成为 Widget 树的一部分,这将违背我们在 Web 上重用它们的设计目标。似乎 Flutter 正在强制任何需要更改小部件状态的东西都成为小部件本身。 (2认同)
  • 您好,“TypeMatcher”已弃用。可以更新一下最新的更新吗? (2认同)

Bam*_*oUA 21

还有另一种常用方法可以访问 State 的属性/方法:

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();
Run Code Online (Sandbox Code Playgroud)

  • 与接受的答案不同,当屏幕上有同一小部件​​的多个实例时,这也适用。每个人都有自己的钥匙! (2认同)

San*_*bat 8

我愿意:

class StartupPage extends StatefulWidget {
  StartupPageState state;

  @override
  StartupPageState createState() {
    this.state = new StartupPageState();

    return this.state;
  }
}

class DetectedAnimationState extends State<DetectedAnimation> {
Run Code Online (Sandbox Code Playgroud)

而外面只是 startupPage.state

  • 我喜欢这个,因为它非常简单,而且很容易理解。 (2认同)
  • StatefulWidget 应该是不可变的,因此声明非最终属性状态是不正确的(并且会导致警告)。 (2认同)