In a complex app, sometimes a Global Variable 'attached' to a widget, can be changed by some 'EXTERNAL EVENT' such as (1) A timer that run in another thread, or (2) socket.io server emit event (3) Others ......
Let's call this global variable gintCount and the app has 3 pages, namely:
- Page 1: A 'Dynamic' page that need to display the latest value of gintCount.
- Page 2: Another 'Dynamic' page that need to display the latest value of gintCount, with a Text Input Field.
- Page 3: A 'Static' page that do nothing when gintCount changes.
Suppose the user is doing something in Page 1 or Page 2, when and where should we 'Refresh' the page to display the latest value that may/might be changed by EXTERNAL event?
I read the other Q&A in Stack Overflow and it is said that there are 4 ways for the State Management of Flutter, they are namely:
- Using setState
- Using ScopedModal
- Using Rxdart with BLoC
- Using Redux
Since I'm a newbie in Flutter, I am completely lost in 2 to 4, so I've build an app using no. 1, i.e. setState. to demonstrate how we can manage states in flutter. And I hope, in the future, I am able to (or somebody else) provide answers by using no. 2 to 4.
Let's take a look at the running app in the following animation gif:
Screen Shot Gif Link
As you can see in the gif, there is a Global Counter in Page 1 and Page 2, and Page 3 is a static Page.
Let me explain how I did it:
The complete source code can be found at the following address:
https://github.com/lhcdims/statemanagement01
There are 7 dart files, they are namely:
- gv.dart: Stores all the Global Variables.
- ScreenVariable.dart: Get the height/width/font size of screen etc. You may ignore this.
- BottomBar.dart: The bottom navigation bar.
- main.dart: The main program.
- Page1.dart: Page 1 widget.
- Page2.dart: Page 2 widget.
- Page3.dart: Page 3 widget.
Let's first take a look at 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
}
How did I simulate an External Event that changes the global variable gv.gintCount?
Ok, I create a thread in main.dart that runs the timer 'funTimerExternal', and increment gv.gintCount every second!
Now, let's take a look at 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
}
}
As you can see, I use another timer 'funTimerDefault' to keep track of changes in gv.gintCount, and determine whether setState should be called every XXX milliseconds. (XXX is currently set at 500)
I know, this is stupid!
How can I create similar examples by using ScopedModal, or Rxdart with BLoC, or Redux?
Before anyone provides any answers, please bear in mind that the Global Variable gintCount, is not changed by ANY USER INTERACTION, but an EXTERNAL EVENT that IS NOT PART OF ANY WIDGETS. For example, you can regard this app as:
A CHAT app, that 'gintCount' is a message sent to you by someone else thru socket.io server. Or,
A Multi-Player On-line Game, that 'gintCount' is the position of another player in YOUR SCREEN, which is controlled by that player using another Mobile Phone!