In the last video, a quick review

In the previous section, we looked at how widgets are controlled by multiple variables on a single page. The core component is to register variables using the MultiProvider, and then create and control widgets using the Selector and Consumer components. And a simple analysis of Selector and Consumer components using methods and differences.

So in this video, we’re going to talk about global state management. This section probably won’t have much code, but it will focus on application scenarios.

Previous articles:

Flutter: From ValueListenableBuilder to Provider

Flutter: From ValueListenableBuilder to Provider

Global management

Let’s first talk about several application scenarios in App development. As I am an iOS developer and do not know the accumulation of Android technology, I mainly focus on the implementation of iOS. Then come how these scenes should be handled in Flutter.

Scenario 1: Switch to page B. Enter A name on page B and click OK to display the name on page A.

  • The iOS side:

This is A typical back-pass of values from adjacent pages, B→A, and is recommended to be implemented using callback or delegate. It is also possible to use Notification.

  • The Flutter end:

This can of course be done with callback. But the system to the pop function with the coroutine is not fragrant? Here are the official code comments:

void _accept() {
 Navigator.pop(context, true); // dialog returns true
}
Copy the code

There is no Provider at all, the Flutter system has its own method to solve the battle

Scenario 2: Page A consists of B1 and B2. B1 can operate to change the status of page A, and B2 can also operate to change the status of page A.

  • The iOS side:

This is A page, which consists of several encapsulated independent components. Callback or delegate needs to be exposed in COMPONENTS B1 and B2, and then unified implementation and logical processing is carried out in page A.

  • The Flutter end:

This can of course also be done with callback. However, considering that components B1 and B2 are both in front of page A, when A is constructed, the Provider component is wrapped in the outer layer of A and the corresponding variables are registered, and then used in page B1 and B2, the code implementation is as follows:

class Counter extends ChangeNotifier { int _count = 1; int get count => _count; set count(int value) { _count = value; notifyListeners(); } } class ExampleWithCompose extends StatelessWidget { const ExampleWithCompose({Key key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: PartManager(), ); } } class PartManager extends StatelessWidget { @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (context) => Counter(), child: _contentView(context), ); } Widget _contentView(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Part Manager"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Padding( padding: Const EdgeInsets. Only (bottom: 8.0), child: Text('count ',),), paddING-back (padding-back: const EdgeInsets. Only (bottom: 8.0), child: Text('count ',),), padding-back (padding-back: const EdgeInsets. Child: 8.0), Consumer > < Counter (builder: (context, value, child) { return Text( 'count:${value.count}', ); }, ), ), AddButton(), SubtractButton(), FlatButton( color: Colors.green, child: Text("Next Page"), onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) { return NextPage(); }), ); }, ), ], ), ), ); } } class NextPage extends StatelessWidget { const NextPage({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Next Page"), ), body: Container(), ); } } class AddButton extends StatelessWidget { const AddButton({Key key}) : super(key: key); @override Widget build(BuildContext context) { return FlatButton( color: Colors.blue, child: Text('count++'), onPressed: () { Provider.of<Counter>(context, listen: false).count++; }); } } class SubtractButton extends StatelessWidget { const SubtractButton({Key key}) : super(key: key); @override Widget build(BuildContext context) { return FlatButton( color: Colors.blue, child: Text('count--'), onPressed: () { Provider.of<Counter>(context, listen: false).count--; }); }}Copy the code

This example may seem superfluous, but all the pages are made up of StatelessWidgets, and the only refined control is Consumer

.

As the volume of services increases, for example, B1 components can change the state of B2 pages, and B2 components can change the state of B1 pages, etc., the communication advantage between components on a single page becomes more obvious. Recall the example of MultiProvider in the previous section. This is a special case of this scenario, but without pulling out the components to encapsulate them.

In addition, it should be noted that there is a NextPage button in this example, which will be pushed to the NextPage after being clicked. As you can see from DevTools, although the button is pushed by the PartManager operation, it has no relation to the tree and cannot be called on this pageProvider.of<Counter>(context, listen: false)This kind of method, remember to remember!

Scenario 3: The TabBarController holds four pages A, B, C, and D. Page D performs an operation and expects the state of page A to change.

  • The iOS side:

This is A typical cross-page data transfer. Since page A and page D are not related, it cannot be implemented by using callback or delegate or Notification for sending and receiving.

  • The Flutter end:

This can be achieved by using EventBus, which is similar to the Notification method of iOS. Data is sent and received through the bus to change the data and then change the page. We can also consider wrapping the Provider component before the top-level MaterialApp and registering the corresponding variable component and corresponding variables, so as to achieve the opportunity that any page can obtain and change the variable and achieve the purpose of global management.

class ExampleWithGlobalManager extends StatelessWidget { @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (context) => Counter(), child: MaterialApp( theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: HomePage(), ), ); } } class HomePage extends StatefulWidget { HomePage({Key key}) : super(key: key); @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { @override Widget build(BuildContext context) { return DefaultTabController( length: 4, child: Scaffold( appBar: AppBar( title: Text("Global Manager"), bottom: TabBar( tabs: [Text("A"), Text("B"), Text("C"), Text("D")], ), ), body: TabBarView( children: [ PageA(), PageB(), PageC(), PageD(), ], ), ), ); } } class PageA extends StatelessWidget { const PageA({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Container( child: Center( child: Consumer<Counter>( builder: (context, value, child) => Text( 'count:${value.count}', ), ), ), ); } } class PageB extends StatelessWidget { const PageB({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Container( child: Center( child: Text("B"), ), ); } } class PageC extends StatelessWidget { const PageC({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Container( child: Center( child: Text("C"), ), ); } } class PageD extends StatelessWidget { const PageD({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ AddButton(), SubtractButton(), ], ), ); }} class Counter extends ChangeNotifier {int _count = 1; int get count => _count; set count(int value) { _count = value; notifyListeners(); } } class AddButton extends StatelessWidget { const AddButton({Key key}) : super(key: key); @override Widget build(BuildContext context) { return FlatButton( color: Colors.blue, child: Text('count++'), onPressed: () { Provider.of<Counter>(context, listen: false).count++; }); } } class SubtractButton extends StatelessWidget { const SubtractButton({Key key}) : super(key: key); @override Widget build(BuildContext context) { return FlatButton( color: Colors.blue, child: Text('count--'), onPressed: () { Provider.of<Counter>(context, listen: false).count--; }); }}Copy the code

In practice, count++ and count– buttons are pressed multiple times due to GIF frame rate issues.

ChangeNotifierProvider stands on top of all widgets, basically at the top of the tree, as we can see from DevTools:

Imagine how to change theme color globally, App main language? Many Of the Flutter demos also have solutions. Typically, the MultiProvider is wrapped around the MaterialApp component, registering variables that require global control. Many Flutter demos use this scheme.

conclusion

To this point, from ValueListenableBuilder single page management, to use Provider for single page management, and finally expand to the global management of Provider, in my understanding of the scope has basically been completed.

When I first started Provider, I felt very mysterious or even terrible, because it was not the thinking mode of App development, but more the inheritance of thinking like Flux, Redux and Vuex in the front end.

In traditional App side development, we mainly update and operate cross-page and multi-page data through callback or message sending mechanism. However, in the process of the evolution of App side to big front-end (SwiftUI in iOS, ComposeUI in Android, and Flutter, a cross-platform scheme), The use of declarative and responsive programming in all of these frameworks is an inevitable trend.

Anyway, if you can reduce development complexity and lose less hair while writing code, why not?

This article is participating in the “Nuggets 2021 Spring Recruitment Campaign”, click to see the details of the campaign