Complex list editors without state management in Flutter

Alexey Inkin
Flutter Senior
Published in
4 min readNov 25, 2021

Imagine an editor like this:

Feels like a lot of state management? In this tutorial we will create such an editor without any manual state management. We will use model_editors package. The editor will allow to:

  • Initialize with a list of your models.
  • Get the list of your models after editing.
  • Add books with (+) button.
  • Limit the maximum number of books.
  • Delete books with trash button.
  • Limit the minimum number of books (disallow deleting the last one).
  • Reorder books by dragging handles.

Remember TextEditingController? It encapsulates the handling of text for input fields so you don’t do setState(){} when something is typed. You just set the text in the controller, create a field widget, then get the edited text from the controller. The idea is the same here.

We will follow these steps:

  1. Define models.
  2. Create a controller for one book with its fields.
  3. Create a widget to show and edit one book.
  4. Create a controller for the list of books that will operate on a list of single book controllers.
  5. Get it together to show an editable list.

The complete code for this example is in main.dart in model_editors package on GitHub, at v0.1.2.

1. Define models

2. Create a controller for one book

The book controller holds all the data of the book but not the Book object itself. The data is stored in TextEditingControllers and a controller for checkboxes.

This controller gets initialized with a Book object. And when the data is read, it constructs another Book object from its data.

It subclasses ValueNotifier which is a Flutter’s class that holds some value, defines its getter an setter and notifies some listeners on its change. Our favorite TextEditingController is itself another subclass of ValueNotifier.

CheckboxGroupEditingController is a class from model_editors package that handles checkboxes.

Note that we require a book to have both title and author. If any of these is not set, the controller’s value is considered empty.

3. Create a widget to show and edit one book

This widget works with BookEditingController the same way that a TextField widget works with TextEditingController.

MaterialCheckboxColumn is a widget from model_editors package that handles checkboxes. It reads and sets values of CheckboxGroupEditingController that we created earlier. Its layout is opinionated, so you may want to create your own. If you do, check its source on how to work with the controller.

Note that in BookEditor we do not have to listen to the controller to rebuild. This is because every piece of book data is in its own controller that is listened to in its nested widget: two text fields and one checkbox group.

At this point, if you use the default app template and create BookEditingController and BookEditor, you will see one empty editor on your screen. But we will move on to make a list.

4. Create a controller for the list of books

This is the easy one because AbstractListEditingController of model_editors takes care for everything.

It only needs an upper limit and an item controller factory. Lower limit is optional.

5. Get it together

Now lets make a minimal app to create this list.

MyHomePage is the only stateful widget in this example as it holds the list controller.

The list is shown with ReorderableListViewEditor of model_editors. It does deletion and reordering with zero effort of yours. It only needs the list controller and a factory method for item widgets.

If you do not need reordering, then use a simpler ColumnListEditor.

Next goes ListAddButtonBuilder. It shows the button to add items if the controller allows it.

The controller gets initialized by setting its value property, in this case in initState(). Then, in “Save” button handler value is read. The value is a list that may contain null values. Remember, we require both title and author for a book to be non-null.

There is a convenience property nonNullValues which reads value and filters non-nulls.

Again, the complete code for this tutorial is in main.dart in model_editors package example on GitHub.

Comparison to other state management patterns

Controller vs StatefulWidget

Advantages of a controller:

  • Less rebuilds on changes. The editor widget does not rebuilt itself on changes of most pieces of data. This is because data fields are handled with their own controllers.
  • Never use onChanged again. With traditional approach, widgets report all their changes to their parent, and you get overwhelmed with callbacks. With controller, you only care for children temporary state if you need live validation.
  • Aggregate anything. If you first design a one-entity editor and then realize that you need to reuse it in an editable list, it’s a 10-lines of code addition. If you aggregate entities, you can aggregate their controllers and editor widgets in a few lines of code. You get the value of the entire list or aggregate with one read of value.
  • Consistency. Flutter uses the same approach for TextEditingController. One approach to state management is more intuitive than a mix.

Controller vs BLoC or Cubit

Another approach to state management of editors is to back each form with a BLoC or cubit, then listen to its stream of states to update fields.

You may think of a controller based on ValueNotifier as just a lightweight version of a cubit. It does not create the output stream.

You generally choose cubit when the main use case is to emit states, and you choose ValueNotifier otherwise. You can still read the current state of a cubit synchronously, but its exclusive use defeats the point of cubit.

Conclusion

The controllers in this package are pretty stable and are intended for public use. The widgets on the other hand are mostly ad-hoc and are in development. They lack many properties and customization.

If you wish to help, file an issue with your idea before contributing, and I will see how to incorporate it.

--

--

Alexey Inkin
Flutter Senior

Google Developer Expert in Flutter. PHP, SQL, TS, Java, C++, professionally since 2003. Open for consulting & dev with my team. Telegram channel: @ainkin_com