flutter 从StatefulWidget外部控制状态

icnyk63a  于 2022-11-17  发布在  Flutter
关注(0)|答案(7)|浏览(285)

我试图了解控制StatefulWidget状态之外的状态的最佳实践。
我定义了以下接口。

abstract class StartupView {
  Stream<String> get onAppSelected;

  set showActivity(bool activity);
  set message(String message);
}

我想创建一个StatefulWidget StartupPage来实现这个接口。我希望这个Widget能完成以下任务:
1.当一个按钮被按下时,它将通过onAppSelected流发送一个事件。一个控制器将监听这个事件并执行一些操作(DB调用、服务请求等)。
1.控制器可以调用showActivityset message,使视图显示进度和消息。
因为有状态Widget不将其State公开为属性,所以我不知道访问和修改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实现此接口,并保持所有控制器逻辑不变。

3mpgtkmj

3mpgtkmj1#

与 其他 有 状态 小 部件 交互 的 方式 有 多种 。

    • 1 . 查找 祖先 类型 状态 * *

第 一 个 也 是 最 直接 的 方法 是 通过 context.findAncestorStateOfType
通常 Package 在 Stateful 子类 的 静态 方法 中 , 如下 所 示 :

class MyState extends StatefulWidget {
  static of(BuildContext context, {bool root = false}) => root
      ? context.findRootAncestorStateOfType<_MyStateState>()
      : context.findAncestorStateOfType<_MyStateState>();

  @override
  _MyStateState createState() => _MyStateState();
}

class _MyStateState extends State<MyState> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

中 的 每 一 个
例如 , Navigator 就是 这样 工作 的 。
亲 :

  • 最 简单 的 解决 方案

缺点 :

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

当 你 想 访问 一 个 变量 时 , 不要 使用 这个 方法 , 因为 当 变量 改变 时 , 你 小 部件 可能 不会 重新 加载 。

    • 2 . 可 侦听 、 流 和/或 继承 的 小 部件 * *

有时 候 , 您 可能 希望 访问 一些 属性 , 而 不是 方法 。 问题 是 , 您 最 有 可能 希望 您 的 小 部件 在 该 值 随 时间 变化 时 更新 。
在 这种 情况 下 , dart 提供 了 StreamSink , flutter 在 其 之上 又 增加 了 InheritedWidgetListenable , 如 ValueNotifier , 它们 都 做 了 相对 相同 的 事情 :当 与 StreamBuilder/context.dependOnInheritedWidgetOfExactType/AnimatedBuilder 耦合 时 订阅 值 改变 事件 。
这 是 当 你 想 让 你 的 State 公开 一些 属性 时 的 首选 解决 方案 。 我 不会 涵盖 所有 的 可能 性 , 但 这里 有 一 个 使用 InheritedWidget 的 小 例子 :
首先 , 我们 有 一 个 InheritedWidget , 它 公开 了 一 个 count

class Count extends InheritedWidget {
  static of(BuildContext context) =>
      context.dependOnInheritedWidgetOfExactType<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;
  }
}

格式
然后 我们 有 示例 化 这个 InheritedWidgetState

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()),
    );
  }
}

格式
优点 :

  • 性能 高于 findAncestorStateOfType
  • Stream 替代 方案 仅 适用 于 dart ( 适用 于 web ) , 并且 与 语言 紧密 集成 ( 关键 字 如 await forasync*
  • 值 更改 时 自动 重新 加载 子 项

缺点 :

  • 更多 样板 文件
  • 流 可能 很 复杂
    • 3 . 通知 * *

您 可以 从 widget 发送 一 个 Notification , 并 让 State 订阅 这些 通知 , 而 不是 直接 调用 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(),
    );
  }

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

格式
例如 Scrollable , 它 可以 调度 ScrollNotification , 包括 开始/结束/过度 滚动 。 然后 , Scrollbar 使用 它 来 了解 滚动 信息 , 而 无需 访问 ScrollController
优点 :

  • 很 酷 的 React 式 API 。 我们 不 直接 在 State 上 做 什么 。 它 是 State , 订阅 由 其子 项 触发 的 事件
  • 多 个 小 部件 可以 订阅 同一 通知
  • 防止 子 级 访问 不 需要 的 State 属性

缺点 :

  • 可能 不 适合 您 的 使用 情形
  • 需要 更多 样板 文件
ttcibm8c

ttcibm8c2#

你可以用一个静态方法公开状态的小部件,一些flutter例子就是这样做的,我也开始使用它:

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,该页面位于其树中的某个位置。

gk7wooem

gk7wooem3#

还有另一种访问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();
nzk0hqpo

nzk0hqpo4#

我做:

class StartupPage extends StatefulWidget {
  StartupPageState state;

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

    return this.state;
  }
}

class DetectedAnimationState extends State<DetectedAnimation> {

而外面只是

3mpgtkmj

3mpgtkmj5#

在尝试解决类似问题时,我发现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())。我想用新方法更新帖子。

axkjgtzd

axkjgtzd6#

您可以使用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();
            });
        });
    }
    ...
}

我想不出有什么事情是事件驱动编程所不能实现的。你是无限的!
“自由不能被赐予--它必须被实现。”--埃尔伯特·哈伯德

xvw2m8pv

xvw2m8pv7#

您是否考虑过将状态提升到父部件?据我所知,这是React中管理状态的一种常见方式,尽管不如Redux理想,而此repository演示了如何将此概念应用于Flutter应用。

相关问题