Sudoku Solver in Flutter — Understanding Flutter State Management by Example — Part 4

Nikhil Heda
6 min readApr 7, 2020

Hey guys!

Welcome to Part 4, where we discuss a solution using providers! Do read the previous posts to know how we got here! — Part 1, Part 2, Part 3

Design 3 : Providers — Finally!

The best way to manage simple state is by using — providers. This uses the observer-observable concept for handling shared state.

  1. The observable — something that can be listened too (our shared state)
  2. The observer — entities that are interested in the observable.

Providers package has a bunch of abstractions for encapsulating shared state. We will use the ChangeNotifierProvider.

ChangeNotifierProvider — A 5 second description

  1. All our shared state and operations will be defined in a ChangeNotifier class.
  2. Whenever there is a change to state within the change notifier, we notify listeners using notifyListeners().
  3. In order to obtain/modify state, we use Provider.of() — To facilitate finer widget rebuilds, we use Consumer (which internally uses Provider.of())

Refer this for more details —

Lets enable this feature —

Step 1: Convert our service class to a change notifier, by using ChangeNotifier as a mixin. Now we can listen to changes in state and also update them.

Step 2: Use Consumer widget to subscribe to state changes — delegate build/rebuild of the widget using the state to the consumer using the builder attribute. This will rebuild the widget whenever the state changes.

Step 3: Use Provider.of() to update the state using the SudokuChangeNotifier.

We need not have stateful widgets, as the rebuild is now controlled by the consumer class.

Who is responsible for shared state?

We continue to maintain all shared state in a single service class and enable this class to behave as a notifier by using ChangeNotifier as a mixin. So, this class is now responsible for managing state.

ChangeNotifierProvider widget is responsible for providing this class to all its decendents.

Widget Tree

Code

Github: https://github.com/NikhilHeda/SudokuSolver/tree/design3

pubspec.yaml

Need to modify pubspec file to include the providers package. Add the provider package in dependencies

SudokuChangeNotifier

The one responsible for shared state. We use the same service class as before and enhance it with the ChangeNotifier.

We also add notifyListeners() whenever the board changes, as that needs to force a rebuild of the widgets using its value (SudokuCell).

SudokuChangeNotifier

We wrap our SudokuSolverPage() with the ChangeNotifierProvider widget. This instantiates SudokuChangeNotifier which will be used by the descendants (defined in child) to access the shared state — lifting state up!

In MyApp widget

SudokuSolverPage

This widget becomes stateless, as all the rebuilds will be handled by the consumer widgets.

SudokuSolverPage widget

Solve and Reset Buttons

We use Provider.of() to get access to our notifier, and use the solveBoard() and resetBoard() methods.

Please note we don’t do this inside a setState(), as the solveBoard() and resetBoard() will notify the widgets listening to the board value to rebuild.

We set listen=false, so that the buttons are not listening to the changes — and hence need not be rebuilt.

Solve and Reset buttons

SudokuBoard and KeyPad

No changes to these widgets.

SudokuCell

This changes to a stateless widget — rebuilds handled by consumer.

We use Provider.of(), with listen=false, to set the board value on click of sudoku cell.

To display the board cell value, we are using a Text widget. Whenever we change the board value at that cell — we need to rebuild this widget, that is, the Text widget is one of the listeners to the changes in board.

We wrap that widget in a consumer widget which will the build/rebuild according to the changes in the notifier.

A note on Consumers —

We create a Consumer of SudokuChangeNotifier. We define the builder attribute which accepts a callback with the parameters context, notifier and child — context is the widget context in which the consumer is created, notifier is the instance of our SudokuChangeNotifier, child is the widget tree to be followed after constructing the widget to be built/rebuilt on change — this is what makes this awesome! — enables finer builds (re-build only those that change).

So, the above code works fine, but we have a far less efficient code here than by using setState(). How?

Notice that, we don’t have just 1 consumer, we have (9 X 9 = 81) consumers (1 for each SudokuCell) and all of them will be rebuilt when any one of the board cells change — because we notify ALL the listeners. This was not the case with setState() as that would’ve rebuilt only that particular cell on value change.

So how do we solve this? — use Selectors!

Selectors are nothing but “intelligent” consumers — they are consumers which rebuild only when there is a change in value — A selector defines the change notifier and the type of value to be tested on.

Along with the builder (the second parameter now changes to the test value, and can directly be used, the other 2 remain the same), we also define a selector callback (accepts context and notifier) where we return the value to test on.

Final implementation —

SudokuCell widget

KeyPadCell

This is pretty straightforward —

Use Provider.of() with listen=false (no need to rebuild KeyPadCell on change) to set the activeNumber on click.

KeyPadCell widget

Providers for the win!

Providers are really good for handling shared state efficiently —

  1. Extensible & maintainable: Shared State is at one place — SudokuChangeNotifier
  2. Efficient rebuilds — No more stateful widgets, we delegate the rebuild to consumers (or selectors) to handle rebuild logic — and can rebuild only specific widgets and not the entire widget tree.

There are a lot more abstractions defined for providers (other than ChangeNotifier) its worth looking at the other implementations

In these series of articles, we created a Sudoku Solver application and managed state in different ways —

  1. Using only stateful widgets — not a good idea
  2. Using InheritedWidget — better!
  3. Using Providers — go-to approach to state management!

There are other ways to handle state in flutter — Redux/BLoC/streams. They handle more complicated types of state and not required for this application. But providers can be used along with BLoC/streams too!

That’s pretty much it with this application! Hope it helped understand concepts of state management and application design!

Thanks for reading! 😊

--

--