Sudoku Solver in Flutter — Understanding Flutter State Management by Example — Part 3
Hey guys!
Welcome to Part 3, where we discuss a solution using inherited widgets — do read Part 1 and Part 2 to understand how we got here.
Design 2 : Using Inherited Widget — I am close…
The design using just stateful widgets clearly has a lot of problems, lets start solving them.
The main problem in previous design is that we are maintaining the shared state everywhere in the widget tree. A better approach would be to maintain it at one place and then make this accessible to the widget tree.
Step 1: Define shared data and operations at one place, and expose it to those who need it. This is what a service class does. So we need a Shared State service class.
Step 2: Make the service class accessible to the widgets in the widget tree. We use InheritedWidget for this.
Inherited widgets — A 5 second description
- It makes data accessible to all widgets in the tree.
- Data should be final (as InheritedWidgets should be immutable), so we can’t maintain board and activeNumber within this as they keep changing, hence we abstract it out to a service, and then make the service final.
Refer this for more details —
Who is responsible for shared state?
Since we are moving all our shared data/operations to a service class — the service class is now responsible for handling them. This service is in turn, maintained by the inherited widget.
Widget Tree
First, lets see how our widget tree changes —
Code
Github: https://github.com/NikhilHeda/SudokuSolver/tree/design2
SudokuSolverService
We first create a service class — SudokuSolverService.
This class defines board and activeNumber and exposes them through getters and setters. We also have operations like solveBoard() and resetBoard() defined in the same class.
SharedStateWidget
We implement a custom InheritedWidget called SharedStateWidget. This has the service class attribute defined as final.
We wrap the SudokuSolverPage widget with the SharedStateWidget, to make the service attribute accessible to all its children (this includes, SudokuCell, KeyPadCell, Solve and Reset buttons) — lifting state up!
Solve and Reset Buttons
A click on solve or reset button changes the board and needs to rebuild the widget, hence, operations to be done in setState().
We get access to solveBoard() and resetBoard() operations from the SudokuSolverService using the SharedStateWidget —
SudokuBoard and KeyPad
SudokuBoard and KeyPad reset to original implementation — Clean code!
We remove the board and activeNumber definitions from all the widgets in the widget tree — now maintained by the service class.
SudokuCell
This continues as a stateful widget. No constructor required as we are not maintaining shared state.
A click on sudoku cell, should change the board value at (row, col) to activeNumber and rebuild widget (hence, inside setState()).
We use the getter (getBoardCell(row, col)) and setter (setBoardCell(row, col)) for board cell value from the service supplied by the SharedStateWidget.
KeyPadCell
Continues to be a stateless widget. No constructor required as we are not maintaining shared state.
A click on keypad cell, should change the active number to that cell’s number using the setActiveNumber() method from the service supplied by the SharedStateWidget.
Problems
So this is a pretty decent code. We can just stop here and accept this as a solution, because —
- We have all shared stuff at one place, hence we can easily maintain/extend it
- Code noise is reduced and each widget makes more sense semantically.
But, we still have the problem of performance — rebuilding the entire widget tree when widget state changes — we still have stateful widgets!
Lets see how to get rid of them, so that our widget tree builds/rebuilds a bit more efficiently.
InheritedWidget is kind of a low-level widget (didn’t know this), there are modules/packages out there, which use this to develop better abstractions for handling data sharing.
Up Next!
Get rid of those stateful widgets using providers— Part 4