Floop DynamicWidget — Two in one class: Widget + “State”

A widget that has it’s own “state” in the form of a Map of dynamic values. There are practical consequences about having a mutable Widget that can simplify common app design patterns.

In my previous article I introduced Floop, a library that provides dynamic values to widgets. These kind of values provide a simple solution to many state related problems and in fact they can be used to build apps with a different paradigm, by using no state at all.

To avoid any confusion with the Dart dynamic type, whenever I refer to “dynamic value” I mean dynamic from a UI perspective, which is that the value always displays in the UI as what it is and not as what it was when the widget was built, or more technically, widgets that use a dynamic value automatically rebuild when the value changes.

Library recap

A short recap of the library: A value is dynamic when it is read from an ObservedMap within a Floop widget’s build method. Any StatelessWidget can easily be transformed into a Floop widget by appending with Floop to the widget’s class definition , for example:

class MyWidget extends StatelessWidget with Floop

class MyStatefulWidget extends StatefulWidget with FloopStateful

Include Floop by adding floop in the dependencies of your project’s pubspec. Check the github repository for more details.

How to use

DynamicWidgets are used in the same way as any Floop widget. It has the addition of the dyn getter, which retrieves an instance of an ObservedMap. The special methods are the following:

initDyn() : the equivalent of initState in a stateful widget. Override it to initialize values in dyn .

: the equivalent of in a stateful widget. Override it to initialize values in . forceInit() : Unnecessary in common use cases, but useful to ensure the widget will have a newly created dyn when replacing another widget.

The behavior of the widget is very similar to the conventional StatefulWidget, because the dyn map is persistent when rebuilding as it gets passed on to widgets that are replacing another one (unless the new widget was already initialized).

The following example displays how DynamicWidget instances can be used and a useful design approach that is unique to this widget.

Example

Update: Added a transition to make the example slightly more interesting. The transition is keyed by the widget’s id so that it is shared between all equivalent widgets.

The main ColoredGrid widget displays a grid of ColorBox widgets in two different modes that switch whenever the amount of clicks on one of the colored boxes reaches a multiple of four.

Case 1 (unfeasible with a conventional StatefulWidget): ColoredGrid widget creates a list of 50 ColorBox widgets based on 4 distinct ones that are stored in floop[#widgets] . A click on a ColorBox widget increases dyn[#clicks] , reflecting the change on all elements that use the same widget. Having widgets share the same “state” is a special behavior of DynamicWidget, which can be convenient in many use cases. Achieving this using the conventional StatefulWidget approach requires grasping more sophisticated concepts, because the regular StatefulWidget creates one state per BuildContext.

The four base ColorBox widgets are forcefully initialized with forceInit on creation, reason being that when creating a new random list of ColorBox widgets, the new widgets will copy the dyn member of the widget that they are replacing if they haven’t been initialized yet.

When using stored widgets instead of instantiating them in the build method, keys can be omitted, thus allowing a different design paradigm. In fact, in this example if the ColorBox widgets were instantiated with a key, it would cause a Flutter error, because all children widgets must have distinct keys or no key at all. Keys can still provide the same utility in dynamic widgets as they do in stateful widgets as it’s explained in the next case.

Case 2: In this case DynamicWidget behaves as a regular StatefulWidget would behave. After every click, ColoredGrid rebuilds because floop[#totalClicks] changes, shifting the keys one position to the right. The ColorBox widget list is being created again, but the new widgets are displaying the updated values as expected. This happens because the dyn member of the old widget is copied to the new widget.

The full example can be found here. It’s refactored in the way I like to organize code, using getters and setters for dynamic values. The refactored code makes me wonder whether or not dynamic values require less boilerplate code by avoiding having to create two classes when using StatefulWidget. At the end of the file is the implementation of the ColorBox widget using the traditional StatefulWidget approach.

Thoughts

The DynamicWidget implementation is quite simple, it just disregards the theoretical immutability constraint that widgets are subject to. It serves as an example of why it’s good idea to “disobey the rules” when we have good reasons to do so. I find the practical implications of allowing mutability to be positive from a practical perspective, particularly because a DynamicWidget can be stored as var and can be reused anywhere carrying it’s potentially mutated dyn map. It can therefore be thought of as a widget that carries it’s own “state”, making it trivial to transfer a widget from one place to another without having to deal with any of the more sophisticated concepts that involve transferring states.

DynamicWidget instances can’t have a const constructor, because the dyn member is mutable. The advantage of having a constant widget is that it causes a branch of the Element tree to update completely independently from the rest of the tree, thus an app can have the Element tree divided into several updating branches. If for efficiency purposes it’s desired that a DynamicWidget has it’s own “updating branch”, override operator == to return true in cases where updates are not necessary, achieving the same effect of a constant widget.

I will write a third and last (for now) article about how simple it is to create transitions and animations when using Floop.