Flutter 状态管理示例

Posted

技术标签:

【中文标题】Flutter 状态管理示例【英文标题】:Flutter State Management Examples 【发布时间】:2019-06-19 00:54:51 【问题描述】:

在复杂的应用程序中,有时“附加”到小部件的全局变量可以通过某些“外部事件”进行更改,例如 (1) 在另一个线程中运行的计时器,或 (2) socket.io 服务器发出事件(三)其他......

我们称之为全局变量gintCount,应用有3个页面,分别是:

    第 1 页:需要显示 gintCount 最新值的“动态”页面。 第 2 页:另一个“动态”页面,需要显示 gintCount 的最新值,带有文本输入字段。 第 3 页:“静态”页面,当 gintCount 更改时不执行任何操作。

假设用户在页面 1 或页面 2 中执行某项操作,我们应该在何时何地“刷新”页面以显示可能/可能会被 EXTERNAL 事件更改的最新值?

看了Stack Overflow的其他问答,据说Flutter的状态管理有4种方式,分别是:

    使用 setState 使用 ScopedModal 将 Rxdart 与 BLoC 一起使用 使用 Redux

由于我是 Flutter 的新手,我完全迷失在 2 到 4 中,所以我用 no 构建了一个应用程序。 1,即setState。展示我们如何在颤动中管理状态。我希望,在未来,我能够(或其他人)通过使用 no 来提供答案。 2到4。

让我们看一下下面动画gif中正在运行的应用程序:

Screen Shot Gif Link

在gif中可以看到,第1页和第2页都有一个全局计数器,第3页是静态Page。

让我解释一下我是怎么做到的:

完整的源代码可以在以下地址找到:

https://github.com/lhcdims/statemanagement01

共有7个dart文件,分别是:

    gv.dart:存储所有全局变量。 ScreenVariable.dart:获取屏幕的高度/宽度/字体大小等。你可以忽略这个。 BottomBar.dart:底部导航栏。 main.dart:主程序。 Page1.dart:第 1 页小部件。 Page2.dart:页面 2 小部件。 Page3.dart:第 3 页小部件。

我们先来看看gv.dart:

import 'package:flutter/material.dart';
class gv 
  static var gstrCurPage = 'page1'; // gstrCurPage stores the Current Page to be loaded

  static var gintBottomIndex = 0; // Which Tab is selected in the Bottom Navigator Bar

  static var gintCount = 0; // The Global Counter
  static var gintCountLast = 0; // Check whether Global Counter has been changed

  static var gintPage1Counter = 0; // No. of initState called in Page 1
  static var gintPage2Counter = 0; // No. of initState called in Page 2
  static var gintPage3Counter = 0; // No. of initState called in Page 3

  static bool gbolNavigatorBeingPushed = false; // Since Navigator.push will called the initState TWICE, this variable make sure the initState only be called once effectively!

  static var gctlPage2Text = TextEditingController(); // Controller for the text field in Page 2

如何模拟更改全局变量 gv.gintCount 的外部事件?

好的,我在 main.dart 中创建一个线程来运行计时器“funTimerExternal”,并每秒递增 gv.gintCount!

现在,让我们来看看 main.dart:

    // This example tries to demonstrate how to maintain the state of widgets when
    // variables are changed by External Event
    // e.g. by a timer of another thread, or by socket.io
    // This example uses setState and a timer to maintain States of Multiple Pages

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import "package:threading/threading.dart";

import 'gv.dart';
import 'Page1.dart';
import 'Page2.dart';
import 'Page3.dart';
import 'ScreenVariables.dart';


void main()   // Main Program
  var threadExternal = new Thread(funTimerExternal);    // Create a new thread to simulate an External Event that changes a global variable defined in gv.dart
  threadExternal.start();

  SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
      .then((_) 
      sv.Init();        // Init Screen Variables

      runApp(new MyApp());        // Run MainApp
  );



void funTimerExternal() async   // The following function simulates an External Event  e.g. a global variable is changed by socket.io and see how all widgets react with this global variable
  while (true) 
    await Thread.sleep(1000);
    gv.gintCount += 1;
  




class MyApp extends StatefulWidget   // Main App
  @override
  _MyAppState createState() => _MyAppState();

class _MyAppState extends State<MyApp> 
  @override
  initState() 
    super.initState();
    var threadTimerDefault = new Thread(funTimerDefault);      // *** Set funTimerDefault, to listen to change of Vars ***
    threadTimerDefault.start();
  

  void funTimerDefault() async 
    while (true) 
      await Thread.sleep(500);        // Allow this thread to run each XXX milliseconds

      if (gv.gintCount != gv.gintCountLast)         // Check any changes need to setState here, if anything changes, setState according to gv.gstrCurPage
        gv.gintCountLast = gv.gintCount;
        switch (gv.gstrCurPage) 
          case 'page1':
            setState(() );              // Page 1: Refresh Page
            break;
          case 'page2':
            setState(() );              // Page 2: Refresh Page
            break;
          default:              // Page 3: Do Nothing, since Page 3 is static
            break;
        
      
    
  

  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      debugShowCheckedModeBanner: false,        // Disable Show Debug

      home: MainBody(),
    );
  

class MainBody extends StatefulWidget 
  @override
  _MainBodyState createState() => _MainBodyState();

class _MainBodyState extends State<MainBody> 
  @override
  initState() 
    super.initState();
  
  @override
  Widget build(BuildContext context) 
    switch (gv.gstrCurPage)       // Here Return Page According to gv.gstrCurPage
      case 'page1':
        return ClsPage1();
        break;
      case 'page2':
        return ClsPage2();
        break;
      default:
        return ClsPage3();
        break;
    
    return ClsPage1();      // The following code will never be run, to avoid warning only
  

如您所见,我使用另一个计时器“funTimerDefault”来跟踪 gv.gintCount 中的变化,并确定是否应每 XXX 毫秒调用一次 setState。 (XXX目前设置为500)

我知道,这很愚蠢!

如何使用 ScopedModal、Rxdart 和 BLoC 或 Redux 创建类似的示例?

在任何人提供任何答案之前,请记住,全局变量 gintCount 不会被任何用户交互更改,而是不属于任何小部件的外部事件。例如,您可以将此应用视为:

    一个聊天应用程序,'gintCount' 是其他人通过 socket.io 服务器发送给您的消息。或者,

    多人在线游戏,'gintCount' 是另一个玩家在您的屏幕中的位置,由该玩家使用另一部手机控制!

【问题讨论】:

只是给你看this,如果你还没看过, 【参考方案1】:

我已经使用 Redux 重写了示例,我们来看看屏幕截图:

如您所见,第1页有2个计数器,变量存储在gv.dart中

在 gv.dart(存储所有全局变量的 dart 文件)中,我创建了一个“存储”:

import 'package:flutter/material.dart';
import 'package:redux/redux.dart';
import 'dart:convert';

enum Actions  Increment  // The reducer, which takes the previous count and increments it in response to an Increment action.
int counterReducer(int intSomeInteger, dynamic action) 
  if (action == Actions.Increment) 
    // print('Store Incremented: ' + (intSomeInteger + 1).toString());
    return intSomeInteger + 1;
  

  return intSomeInteger;


class gv 
  static Store<int> storeState = new Store<int>(counterReducer, initialState: 0);

  static var gstrCurPage = 'page1'; // gstrCurPage stores the Current Page to be loaded

  static var gintBottomIndex = 0; // Which Tab is selected in the Bottom Navigator Bar

  static var gintGlobal1 = 0;  // Global Counter 1
  static var gintGlobal2 = 0;  // Global Counter 2

  static var gintPage1Counter = 0; // No. of initState called in Page 1
  static var gintPage2Counter = 0; // No. of initState called in Page 2
  static var gintPage3Counter = 0; // No. of initState called in Page 3

  static bool gbolNavigatorBeingPushed = false; // Since Navigator.push will called the initState TWICE, this variable make sure the initState only be called once effectively!

  static var gctlPage2Text = TextEditingController(); // Controller for the text field in Page 2

再次,在 main.dart 中,我创建了另一个线程“funTimerExternal”来模拟“外部事件”,其中一些全局变量被 socket.io 服务器发出事件更改。

在'funTimerExternal'的最后,一些变量改变后,我调用了:

gv.storeState.dispatch(Actions.Increment);

改变 Page1 或 Page2 的状态,当且仅当用户正在浏览 Page 1 或 Page 2。(即当用户正在浏览 Page 3 时什么都不做)

main.dart:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:threading/threading.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';

import 'gv.dart';
import 'Page1.dart';
import 'Page2.dart';
import 'Page3.dart';
import 'ScreenVariables.dart';

void main()   // Main Program
  var threadExternal = new Thread(
      funTimerExternal); // Create a new thread to simulate an External Event that changes a global variable defined in gv.dart
  threadExternal.start();

  SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
      .then((_) 
    sv.Init(); // Init Screen Variables

    runApp(new MyApp()); // Run MainApp
  );


void funTimerExternal() async   // The following function simulates an External Event  e.g. a global variable is changed by socket.io and see how all widgets react with this global variable
  while (true) 
    await Thread.sleep(1000);
    gv.gintGlobal1 += 1;
    gv.gintGlobal2 = (gv.gintGlobal1 / 2).toInt();
    gv.storeState.dispatch(Actions.Increment);
  


class MyApp extends StatefulWidget   // Main App
  @override
  _MyAppState createState() => _MyAppState();


class _MyAppState extends State<MyApp> 
  @override
  initState() 
    super.initState();
  

  @override
  Widget build(BuildContext context) 
    return StoreProvider(
      store: gv.storeState,
      child: MaterialApp(
        debugShowCheckedModeBanner: false, // Disable Show Debug

        home: MainBody(),
      ),
    );
  


class MainBody extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    switch (gv.gstrCurPage) 
      // Here Return Page According to gv.gstrCurPage
      case 'page1':
        gv.gintPage1Counter += 1;
        return StoreConnector<int, int>(
          builder: (BuildContext context, int intTemp) 
            return new ClsPage1(intTemp);
          , converter: (Store<int> sintTemp) 
          return sintTemp.state;
        ,);
        break;
      case 'page2':
        gv.gintPage2Counter += 1;
        return StoreConnector<int, int>(
          builder: (BuildContext context, int intTemp) 
            return new ClsPage2(intTemp);
          , converter: (Store<int> sintTemp) 
          return sintTemp.state;
        ,);
        break;
      default:
        return ClsPage3();
        break;
    
  

与网络上提供的示例不同,“Store”不是在 main.dart 中声明的,而是在另一个 dart 文件 gv.dart 中声明的。即我分离了 UI 和数据!

完整的例子可以在这里找到:

https://github.com/lhcdims/statemanagement02

再次感谢 Miiite 和 shadowsheep 的帮助。

【讨论】:

【参考方案2】:

根据您的需要,您绝对应该更多地研究您谈到的可用架构。 例如,REDUX 完全符合您解决问题的需要。

我只能建议你看看这个 REDUX 演示文稿: https://www.youtube.com/watch?v=zKXz3pUkw9A

即使对于这种模式的新手(我不久前也是如此)来说也是非常容易理解的。 完成后,看看http://fluttersamples.com/

该网站包含十几种不同模式的示例项目。这可能会帮助您入门

【讨论】:

谢谢Miiite,真的很有帮助,我用Redux重写了例子,很快就会公布答案。

以上是关于Flutter 状态管理示例的主要内容,如果未能解决你的问题,请参考以下文章

Flutter中大型模型的状态应该如何管理?

Flutter状态管理终极方案GetX第一篇——路由

Flutter如何状态管理

Flutter StatefulWidget - 状态类继承?

flutter学习-状态State管理

Flutter状态管理终极方案GetX第二篇——状态管理