Flutter State Management Примеры - PullRequest
       40

Flutter State Management Примеры

0 голосов
/ 25 января 2019

В сложном приложении иногда глобальная переменная, «прикрепленная» к виджету, может быть изменена некоторым «ВНЕШНИМ СОБЫТИЕМ», таким как (1) таймер, выполняющийся в другом потоке, или (2) серверный сервер socket.io событие (3) Другое ......

Давайте назовем эту глобальную переменную gintCount, и приложение имеет 3 страницы, а именно:

  1. Страница 1: «Динамическая» страница, на которой необходимо отобразить последнее значение gintCount.
  2. Страница 2: Еще одна динамическая страница, на которой необходимо отобразить последнее значение gintCount с полем ввода текста.
  3. Страница 3: «Статическая» страница, которая ничего не делает при изменении gintCount.

Предположим, что пользователь что-то делает на странице 1 или странице 2, когда и где мы должны «обновить» страницу, чтобы отобразить последнее значение, которое может / может быть изменено событием EXTERNAL?

Я прочитал другие вопросы и ответы в Переполнение стека, и мне сказали, что есть 4 способа управления состоянием флаттера, а именно:

  1. Использование setState
  2. Использование ScopedModal
  3. Использование Rxdart с BLoC
  4. Использование Redux

Поскольку я новичок во Флаттере, я полностью потерялся со 2 до 4, поэтому я создал приложение, используя no. 1, то есть setState. чтобы продемонстрировать, как мы можем управлять состояниями во флаттере. И я надеюсь, что в будущем я смогу (или кто-то еще) дать ответы, используя нет. От 2 до 4.

Давайте посмотрим на работающее приложение в следующем анимационном GIF:

Screen Shot Gif

Снимок экрана Gif Link

Как видно из рисунка, на странице 1 и странице 2 есть глобальный счетчик, а страница 3 - статическая страница.

Позвольте мне объяснить, как я это сделал:

Полный исходный код можно найти по следующему адресу:

https://github.com/lhcdims/statemanagement01

Есть 7 дротиков, а именно:

  1. gv.dart: хранит все глобальные переменные.
  2. ScreenVariable.dart: получить высоту / ширину / размер шрифта и т. Д. Вы можете игнорировать это.
  3. BottomBar.dart: нижняя панель навигации.
  4. main.dart: основная программа.
  5. Page1.dart: виджет Page 1.
  6. Page2.dart: виджет Page 2.
  7. Page3.dart: виджет Page 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 и определять, должен ли setState вызываться каждые XXX миллисекунд. (XXX в настоящее время установлен на 500)

Я знаю, это глупо!

Как создать похожие примеры с помощью ScopedModal, или Rxdart с BLoC, или Redux?

Прежде чем кто-либо даст какие-либо ответы, имейте в виду, что глобальная переменная gintCount не изменяется НИКАКИМ ВЗАИМОДЕЙСТВИЕМ ПОЛЬЗОВАТЕЛЯ, а ВНЕШНЕЕ СОБЫТИЕ НЕ ЧАСТЬ ЛЮБЫХ ВИДЖЕТОВ . Например, вы можете рассматривать это приложение как:

  1. Приложение ЧАТ, которое «gintCount» - это сообщение, отправленное вам кем-то еще через сервер socket.io. Или

  2. Многопользовательская онлайн-игра, в которой gintCount - это позиция другого игрока на ВАШЕ ЭКРАНЕ, которая контролируется этим игроком с помощью другого мобильного телефона!

Ответы [ 2 ]

0 голосов
/ 28 января 2019

Я переписал пример с использованием Redux, давайте посмотрим на экранную шапку:

enter image description here

Как вы можете видеть, есть 2счетчики на странице 1, переменные хранятся в gv.dart

В gv.dart (файл dart, в котором хранятся все глобальные переменные) я создал «Store»:

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' для имитации 'внешнего события', в котором некоторые глобальные переменные изменяются, скажем, посредством события emit сервера socket.io.

В конце 'funTimerExternal', после изменения некоторых переменных я вызвал:

gv.storeState.dispatch (Actions.Increment);

, чтобы изменить состояние Page1 ИЛИ Page2, ЕСЛИ И ТОЛЬКО ЕСЛИ пользовательнавигация по странице 1 или странице 2. (т.е. ничего не делать, когда пользователь перемещается по странице 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;
    }
  }
}

В отличие от примера, представленного в Интернете, «Магазин»не объявлено внутри main.dart, но внутри другого файла дротика gv.dart.т.е. я разделил пользовательский интерфейс и данные!

Полный пример можно найти здесь:

https://github.com/lhcdims/statemanagement02

Еще раз спасибо за помощь Miiite и shadowsheep.

0 голосов
/ 25 января 2019

Для ваших нужд вам определенно стоит взглянуть на доступные архитектуры, о которых вы говорили.Например, REDUX точно соответствует тому, что вам нужно для решения вашей проблемы.

Я могу только посоветовать вам взглянуть на эту презентацию REDUX: https://www.youtube.com/watch?v=zKXz3pUkw9A

Это очень понятно даже дляновички этого образца (которым я был не так давно).Когда вы это сделаете, взгляните на http://fluttersamples.com/

Этот веб-сайт содержит примеры проектов для дюжины различных шаблонов.Это может помочь вам начать

...