Stateful vs Stateless Flutter

Stateless widgets are immutable. Since their properties cannot change, all values are final. On the contrary, stateful widgets maintain state.

For that reason, widgets are rebuilt with the change of the state.

Does that affect flutter application?

Certainly, that rebuilds widgets and affects the performance.

Then what should we do to solve that problem?

When should I use provider Flutter?

We should use provider package to solve that problem.

Why should we use provider package?

The main reason is, provider package will rebuild only the widget that needs the value. The widget that needs the value is known as consumer.

As a result, that consumer widget only rebuilds itself. For example, consider a simple counter. As we press the button to change the state, each pressing changes the state and rebuilds the widget.

For a stateful widget, that rebuild affects the whole tree of widgets.

In Android Studio, we can watch the flutter performance.

Firstly, we see the counter example with the stateful widget.

import 'package:flutter/material.dart';

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: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);  

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {      
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    
    return Scaffold(
      appBar: AppBar(        
        title: Text(widget.title),
      ),
      body: Center(        
        child: Column(          
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), 
    );
  }
}

Let us press the button for five times and side by side watch the flutter performance.

The screenshot looks like the following image.

Widget rebuild with stateful widget
Widget rebuild with stateful widget

Now, let us take a closer look and the screenshot looks like the following image.

Widget rebuild with stateful widget affects every widget
Widget rebuild with stateful widget affects every widget

It clearly shows that after pressing the button for five times, on the far right side of the screen each widget shows the number 6.

That means a stateful way always requires more memory than the stateless way. In fact that is clearly visible in the first image, because the red colored bar represents memory usage.

This is the reason why we should use a combination of Provider package and stateless widgets.

It makes our flutter app more performant.

How do you use Flutter provider package?

We use Flutter provider package like any other packages. Firstly, we add the provider package to the dependencies section in our pubspec.yaml file.

dependencies:
  provider: ^6.0.0

Secondly, we import the provider package just like any other packages.

import 'package:provider/provider.dart';

Finally, we need to notify the listeners that we’re going to change the state.

We can define that method in our model folder, from where our data come.

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }  
}

Next, to run the app we must place providers above the Counter App. Not only that, we need to use other methods so that we can read and watch the change.

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shop_app/models/counter.dart';
void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => Counter()),
      ],
      child: const CounterApp(),
    ),
  );
}

class CounterApp extends StatelessWidget {
  const CounterApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: CounterHomePage(),
    );
  }
}

class CounterHomePage extends StatelessWidget {
  const CounterHomePage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    final subtree = ConstantWidget(
        child: const Text("Hello World")
    );
    final anotherSubtree = _widget();
    return Scaffold(
      appBar: AppBar(
        title: const Text('A Stateless Counter App'),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget> [
            Text('You have pushed the button this many times:'),
            Text(
              '${context.watch<Counter>().count}',
              key: const Key('counterState'),
              style: Theme.of(context).textTheme.headline4,
            ),
            SizedBox(width: 10.0,),
            subtree,
            SizedBox(width: 10.0,),
            anotherSubtree,
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        key: const Key('increment_floatingActionButton'),
        onPressed: () => context.read<Counter>().increment(),
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
  /// Just to test rebuild  
  Widget _widget() => const Text('Hi');

  ConstantWidget({required Text child}) {
    /// Just to test rebuild  
    return Text('Hello');
  }
}

In the above code, a few lines are extremely important.

Firstly, we’ve placed the Multi Provider above our counter app.

Secondly, when the context use read and watch methods, we’ve supplied the type Counter that we’ve defined in the model folder.

'${context.watch<Counter>().count}',
onPressed: () => context.read<Counter>().increment(),

What is the difference between context.read and context.watch?

It’s a key concept while we’re trying to understand the role of Provider package.

To display the count number, we use context.watch.

Why?

We call context.watch to make the count property of Type Counter rebuild when the counter changes.

However, inside the floating action button on press method we return context.read.

It stops the floating action button widget from rebuilding itself.

Now, as a result, we get a different screenshot when we press the button.

Widget rebuild with stateless widget
Widget rebuild with stateless widget

With the help of Provider package, we have kept the requirement of memory usage much lower than the stateful widget.

As we can see, there are no red bars.

Not only that, now we can take a closer look at the flutter performance.

Widget rebuilds affects only one widget
Widget rebuilds affects only two widgets

As we keep on pressing the button, only two widgets rebuild themselves without affecting the others.

Moreover, now the state change centers on only two text widgets.

The parent tree remains unaffected.

What Next?

Books at Leanpub

Books in Apress

My books at Amazon

GitHub repository

Technical blog

Twitter

Comments

16 responses to “Stateful vs Stateless Flutter”

  1. […] The one and only answer is, through Provider package we can avoid widget rebuilds. To get the concept, and if you want an evidence please read my previous blog post on stateful vs stateless widgets. […]

  2. […] The one and only answer is, through Provider package we can avoid widget rebuilds. To get the concept, and if you want an evidence please read my previous blog post on stateful vs stateless widgets. […]

  3. […] Last, but not least, like any application Flutter uses State management. […]

  4. […] We’re going to build a Book Shopping Cart flutter app, and to maintain state we’ve used Provider package for the best result and less widget rebuilds. […]

  5. […] triggers a rebuild on itself. When the widget is a stateful, it calls the setState() method on StateFul […]

  6. […] a widget scrolls, we call it Scrollable. The StatefulWidget is the direct ancestor of a Scrollable class. Therefore when we scroll down or up, as location […]

  7. […] means, our custom Widget OurFirstApp can use many properties and methods of a StatelessWidget that Flutter gives us to use to build our […]

  8. […] a widget scrolls, we call it Scrollable. The StatefulWidget is the direct ancestor of a Scrollable class. Therefore when we scroll down or up, as location […]

  9. […] we can start adding blog items to this Flutter Application. However, in our Home page, AllPages stateful Widget, we must define […]

  10. […] défaut, FutureBuilder est de nature avec stateful. Il maintient son propre […]

  11. […] Most importantly, every component in Flutter is either Stateless or Stateful widget. […]

  12. […] In flutter we call a page or screen as route. As we know, in flutter every page or screen is a widget. It could be either stateful or stateless widgets. […]

  13. […] have managed the state in the stateless widgets by using Provider […]

  14. […] we want another stateful widget that will handle the plugin. However, to use the plugin, firstly, we need to add the dependency in […]

  15. […] questions always haunt Flutter beginners. What is the State of Flutter? And, how to manage the State of […]

Leave a Reply