To enum or to sealed class? That is the question we’ll be discussing in this episode of Very Good Engineering 🦄, to understand which way to go when declaring states for our Cubits/Blocs.
💡 Either one of these options could be the right one depending on the following use cases.
Do I want to persist previous data when emitting a new state?
As it happens when filling out a form where data is updated step by step, or when the state has several values that are loaded independently, if your aim is to update new fields of the state or the state itself without losing previously emitted data, using a single class with an enum as the state’s ‘status’ it’s the easiest way to go.
💡 You can also share properties throughout all the states by setting those inside the parent sealed or abstract class.
This can look something like:
Let’s see an example:
As you can see above, because the user is going to fill out their name, surname, and email, and any of them can be null or empty at any time, we need to make sure we have data in each property as per our business logic before allowing the user to create their account.
💡 Using enums to handle status is useful in cases like this where there are several steps for the user to fill up information and the data emitted in previous steps should not be lost in newer emits.
Take a look at the Cubit example for this implementation:
As you can see, having a single state class with an enum for the status helps to keep the information that was added previously.
Let’s see how we consume these types of states in the UI using the BlocListener widget.
As seen above, with this approach, the current status comes from the statusenum property inside the cubit state.
Let’s now check the other way to handle states.
Do I want to emit a fresh state every time?
The other side of the state management aims for clean state updates, isolating the properties of each state that’s emitted. This is useful for when the data being fetched is not going to change, or for instance, we don’t need to keep it in future emits, and it’s a matter of simply:
This can be achieved by leveraging the use of sealed classes (when in Flutter 3.13+) or basic abstract classes (when in older Flutter versions).
Using sealed classes
Let’s see how the states are built:
As you can see, each state holds its own data, and it’s properly isolated from one another.
Let’s now see how to treat this state in the Cubit:
And now let’s consume that Cubit from the UI:
As you can see, sealed classes helps us to properly isolate data inside each state, and whenever we check we are in a certain state we are sure that the data won’t be null at all, as it happens when dealing with enum states.
Using abstract classes
The Cubit class doesn’t change at all:
But the way we consume states as classes differs:
Here you can see that consuming states based on an abstract class is more painful than using sealed classes, but is still the way to go when the Flutter version is not up-to date and you would like to isolate each state.
Bonus - Share properties in some of the states (sealed or abstract classes)
You might be wondering… can I have the same property in more than one state and still continue to use sealed classes? Yes you can!
💡 You can also share properties throughout all the states by setting those inside the parent sealed or abstract class.
Let’s look at an updated version of our state and cubit implementation using sealed classes (Pst! Same thing works for abstract classes as well):
As seen above, ProfileSuccess and ProfileEditing contains a Profile property inside. How can we handle that from inside the Cubit?
This way you can be sure to handle all states in your Cubit methods and also be able to use the values contained.
To conclude this part, here’s also the way to do the same thing but UI side:
Hope this helps to get an idea about which route to take when designing states for your Cubits/Blocs.✨