How to stop rebuilding in Flutter

When our widgets rebuild repetitively, it directly affects flutter performance. Therefore, it’s a million dollar question – how to stop rebuilding in flutter?

There are several tips; and they are flying around the web. To sum them up, we get a few key points that we should follow when we build a flutter app.

Use packages that help us to avoid widget rebuilding. One such package is Provider. Try to make widgets constant.

Provider has different types, such as Consumer, or Selector. As a whole, state management is an intermediate topic in Flutter that requires a lot of attention. Because changing state often requires widget rebuilding.

And, that’s why, in a stateful widget when we call setState() on a State, all descendant widgets rebuild.

As a result localizing the setState() call to the part of the sub tree whose UI actually needs to change, is the only solution.

However, the provider package helps us a lot to avoid unnecessary widget rebuilding.

Let us build a flutter app where three text widgets will display random words. We’ll also have a elevated button; and, by clicking that button we can change one of the words.

Therefore, it looks like the following screenshots.

How to stop widget rebuilding in flutter sample one
How to stop widget rebuilding in flutter sample one

When we run the app, three text widgets rebuild with the home page. After that we click the button that tells us to change word.

What will happen when we click the button?

The last random word will be replaced by a new random word.

Stop widget rebuilding in flutter sample two
Stop widget rebuilding in flutter sample two

If we really want to make our flutter app highly performant, only the text widget displaying the last word should rebuild.

We’ve almost achieved that by using Provider package.

Why “almost”?

Because the second word and the third word comes from the same data model that are provided by the provider package.

Before seeing the code snippets let’s know a few basic things about the packages we’re using here.

The English words package is a package that really helps us to use more than 5000 used English words and in addition, it has some utility functions. Let us use it in tandem with another useful package provider package.

Firstly, we should add the dependency of two packages to our pubspec.yaml.

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.0.0
  flutter_riverpod: ^1.0.0-dev.11


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  english_words: ^4.0.0

Our next stop is the data model from where the second and third words are coming.

We’ve also defined how we can change the third word.

import 'package:flutter/foundation.dart';
import 'package:english_words/english_words.dart';

class WordPairing with ChangeNotifier {
  late var word = WordPair.random();
  late var changedWord = WordPair.random();
  late var wording = WordPair.random();
  void changeWord() {
    changedWord = WordPair.random();
    wording = changedWord;
    notifyListeners();
  }
}

After that, let us see the full code snippet.

import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_artisan/models/wordpair.dart';

class ProviderSampleEight extends StatelessWidget {
  const ProviderSampleEight({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: ProviderSampleEightHome(),
    );
  }
}

class ProviderSampleEightHome extends StatelessWidget {
  const ProviderSampleEightHome({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Wordpairing Test'),
      ),
      body: const SevenBody(),
    );
  }
}

class SevenBody extends StatelessWidget {
  const SevenBody({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('Building home page');
    final wordPair = WordPair.random();

    return ListView(
      children: [
        Container(
          margin: const EdgeInsets.all(10),
          padding: const EdgeInsets.all(10),
          child: FirstWord(wordPair: wordPair),
        ),
        Container(
          margin: const EdgeInsets.all(10),
          padding: const EdgeInsets.all(10),
          child: const SecondWord(),
        ),
        Container(
          margin: const EdgeInsets.all(10),
          padding: const EdgeInsets.all(10),
          child: const ThirdWord(),
        ),
        ElevatedButton(
          onPressed: context.read<WordPairing>().changeWord,
          child: const Text(
            'Change Word',
            style: TextStyle(
              fontSize: 25,
            ),
          ),
        ),
      ],
    );
  }
}

class ThirdWord extends StatelessWidget {
  const ThirdWord({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('Building third word');
    return Text(
      context.watch<WordPairing>().wording.toString(),
      style: const TextStyle(
        fontFamily: 'Allison',
        fontWeight: FontWeight.bold,
        fontSize: 60,
      ),
    );
  }
}

class SecondWord extends StatelessWidget {
  const SecondWord({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('Building second word');
    return Text(
      context.watch<WordPairing>().word.asSnakeCase,
      style: const TextStyle(
        fontFamily: 'Allison',
        fontWeight: FontWeight.bold,
        fontSize: 80,
      ),
    );
  }
}

class FirstWord extends StatelessWidget {
  const FirstWord({
    Key? key,
    required this.wordPair,
  }) : super(key: key);

  final WordPair wordPair;

  @override
  Widget build(BuildContext context) {
    print('Building first word');
    return Text(
      wordPair.asCamelCase,
      style: const TextStyle(
        fontFamily: 'Allison',
        fontWeight: FontWeight.bold,
        fontSize: 60,
      ),
    );
  }
}

As each Text widget has a print statement, we get this output on our terminal when we run the app.

Building home page
Building first word
Building second word
Building third word

That is quite expected. However, when we click the button, then as expected, the second and third text widget rebuild as the data source is same.

Building second word
Building third word

However, we can avoid that with the help of provider selector class.

Let us change the full code snipet.

import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_artisan/models/wordpair.dart';

class ProviderSampleNine extends StatelessWidget {
  const ProviderSampleNine({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: ProviderSampleNineHome(),
    );
  }
}

class ProviderSampleNineHome extends StatelessWidget {
  const ProviderSampleNineHome({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Wordpairing Test'),
      ),
      body: const NineBody(),
    );
  }
}

class NineBody extends StatelessWidget {
  const NineBody({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('Building home page');
    final wordPair = WordPair.random();

    return ListView(
      children: [
        Container(
          margin: const EdgeInsets.all(10),
          padding: const EdgeInsets.all(10),
          child: FirstWord(wordPair: wordPair),
        ),
        Container(
          margin: const EdgeInsets.all(10),
          padding: const EdgeInsets.all(10),
          child: const SecondWord(),
        ),
        Container(
          margin: const EdgeInsets.all(10),
          padding: const EdgeInsets.all(10),
          child: const ThirdWord(),
        ),
        ElevatedButton(
          onPressed: context.read<WordPairing>().changeWord,
          child: const Text(
            'Change Word',
            style: TextStyle(
              fontSize: 25,
            ),
          ),
        ),
      ],
    );
  }
}

class ThirdWord extends StatelessWidget {
  const ThirdWord({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('Building third word');
    return Text(
      '${context.select((WordPairing w1) => w1.wording)}',
      style: const TextStyle(
        fontFamily: 'Allison',
        fontWeight: FontWeight.bold,
        fontSize: 60,
      ),
    );
  }
}

class SecondWord extends StatelessWidget {
  const SecondWord({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('Building second word');
    return Text(
      context.select((WordPairing w2) => w2.word.asSnakeCase),
      style: const TextStyle(
        fontFamily: 'Allison',
        fontWeight: FontWeight.bold,
        fontSize: 80,
      ),
    );
  }
}

class FirstWord extends StatelessWidget {
  const FirstWord({
    Key? key,
    required this.wordPair,
  }) : super(key: key);

  final WordPair wordPair;

  @override
  Widget build(BuildContext context) {
    print('Building first word');
    return Text(
      wordPair.asCamelCase,
      style: const TextStyle(
        fontFamily: 'Allison',
        fontWeight: FontWeight.bold,
        fontSize: 60,
      ),
    );
  }
}

As a result, we can now run the app and this time when we click the button, only the third text widget rebuilds.

Building third word

For more beautiful Flutter UI, you may take a sneak peek at this GitHub repository, though it’s not a hidden treasure. 🙂

What Next?

Books at Leanpub

Books in Apress

My books at Amazon

GitHub repository

Technical blog

Twitter

Comments

5 responses to “How to stop rebuilding in Flutter”

  1. […] importantly, we always want to make our Flutter app faster and performant. Since storing persistent data requires a lot of state management, the Provider package always […]

  2. […] default, Flutter Applications are performant. However, there are some good practices that we can follow to speed up our Flutter […]

  3. […] défaut, les applications Flutter sont performantes. Cependant, il existe quelques bonnes pratiques que nous pouvons suivre pour accélérer nos […]

  4. […] As a result, our Flutter applications open faster and consume less memory. […]

  5. […] défaut, les applications Flutter sont performantes. Cependant, il existe quelques bonnes pratiques que nous pouvons suivre pour accélérer nos […]

Leave a Reply