Rebuilding the default Counter app with Provider package as state management solution

I finally have decided which state management solution I want to use in the near future. Every time I started a private project, I used BLoC and was done. But I have a lot of problems with Bloc which I do not have any more with provider that I will not discuss in this Tutorial. Now I want to write a full series of “Rebuilding app XY with Provider package”. To begin I will do an easy one: The counter app you get if you start a new Flutter project. I will explain the easiest widgets which come with Provider in this tutorial.

First things first: To develop on the counter app you have to start a new Flutter project with the IDE of your choice or with

flutter create counter_provider

I initially delete all code comments so that you will not read about them in the following code basis. They are really good if you still do not understand the Flutter basics. As I was learning about Flutter I read them every time I started a new project. Also, I will rename MyHomePage to CounterPage so that the name will describe the screen. To use provider you have to add the package provider: ^4.0.2 (this post is based on this version) into the dependencies section of your pubspec.yaml.

42 counter

Extracting the state

In the default example, the state is the _counter and there is some business logic in the UI by the _incrementCounter() function. For provider I created a new class CounterModel. The model is really simple. We have a value counter which defaults to 0 at class creation. In this model we only have one the function incrementCounter()which is doing the same as before: It adds +1 to counter. The difference now is in how we handle the state. Before, we called setState to rebuild the complete CounterPage UI tree. In the following code, we call notifyListener() instead. This is a function from super class if we extend from ChangeNotifier. By this, other classes can listen to the model and will be notified if there are state changes. The complete model is coded as followed:

class CounterModel extends ChangeNotifier {
  int counter;

  CounterModel({this.counter = 0});

  void incrementCounter() {
    counter++;
    notifyListeners();
  }
}

Provide the model

We need to create the model and pass it to the Widgets that need the state from this model to build their UI. The deepest page that needs the CounterModel is the CounterPage. So we create the model right above it. The CounterPage is the home widget of MyApp. So we will create the model around this page. Provider offers different Provider classes for this. The easiest class to provide a ChangeNotifier model is the ChangeNotifierProvider. It takes up to 3 arguments. The two, for now, important ones are create and child. In create, we build an instance of the model we want to provide to the underlying widget. The child is the underlying widget that will be built and shown. For Provider you have to pass the class of the model as type argument as well. So the following code shows this process with CounterModel as type argument and the CounterPage as child. We pass this ChangeNotifierProvider into home:

ChangeNotifierProvider<CounterModel>(
          create: (context) => CounterModel(),
          child: CounterPage(title: 'Flutter Demo Home Page'))

 

Using the model

Our CounterPage should not know the counter and any business logic anymore. So we can delete the _counter value and the _incrementCounter() function in the existing page code. Also, the CounterPage is not rebuilding itself anymore so it has not to be a StatefulWidget anymore and we can change it to StatelessWidget. There should be two errors now: One because _counter is unknown now and the other error because there is not _incrementCounter() anymore. To use the instance of the model we have to ask the Provider for the provided class. This we do by:

final counterModel = Provider.of<CounterModel>(context, listen: false);

With this code, we can work with the counterModel and ask for the counter value or call the increment function. But here is one interesting part of the code: listen: false. If we read the counter value on this model and the counter changes we tell the Provider that our UI should not be updated on this change. If we would listen to it the complete CounterPage would rebuild as before. So, for now, we can only use this model to call counterModel.incrementCounter() and deleting the first error. For the second error, we will ask for the counter value in the deepest part of our tree we need it. This is in the Text widget. To only rebuild this part Provider offers a widget called Consumer. If we tell the Consumer that we are interested in the CounterModel as type argument the builder of the Consumer will rebuild this part of the UI on every notifyListeners() call in CounterModel. So we will return the Text widget in the builder function of the Consumer with passing the counter value of the model to the String of the Text widget. This looks like this code:

Consumer<CounterModel>(
  builder: (context, model, child) {
    final count = model.counter;
    return Text(
      '$count',
      style: Theme.of(context).textTheme.display1,
    );
  },
)

 

You can check that really only the Text widget rebuilds by setting breakpoints on the return statements and watching out which breakpoints will be called after incrementCounter is called. Just to be finished here is the full source code:

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ChangeNotifierProvider<CounterModel>(
          create: (context) => CounterModel(),
          child: CounterPage(title: 'Flutter Demo Home Page')),
    );
  }
}

class CounterPage extends StatelessWidget {
  final String title;

  CounterPage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final counterModel = Provider.of<CounterModel>(context, listen: false);

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Consumer<CounterModel>(
              builder: (context, model, child) {
                final count = model.counter;
                return Text(
                  '$count',
                  style: Theme.of(context).textTheme.display1,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => counterModel.incrementCounter(),
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

What’s next?

I think the next part will be the Implementation of a Login procedure just like one of the first bloc tutorials so that you can compare them. Until then you could read one of my other Articles:

Use GridView to prepare a basic board for a puzzle game | Building a Puzzle App in Flutter – Part 1

Why start a Flutter blog

The full source code is on Github: https://github.com/tomkastek/counter_provider

 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.