Loading status with minimal effort [part 3]

Paweł S
4 min readJul 7, 2022

--

One of the critical elements of great UX is showing progress indicators. It informs users of the ongoing processes. They communicate the state of the app and indicate available actions.

This is the last part of the series. It will build on the previous ones, so if you haven't checked it out, here are parts 1 and 2.

Loading indicator should be visible from starting the long-running operation till its completion either successful or failure. There are multiple possible solutions for that, but we will focus on how we can continue what we have started in this series.

Continuing our work

The simplest solution may be to set the loading state, start the long-running operation, and after the completion, change the state to normal.

setState(LoadingState)
final loadMoviesResult = await loadPopularMoviesUsecase();
setState(NormalState);
if(result is ErrorResult){ ... }

It is a simple solution that should work pretty well. As you remember, we changed single-time data loading to observing the database last time. For some state management solutions (like BLoC) it may be a slight problem.

Imagine this scenario:

Our user opens the app, and the loading process starts. We set the LoadingState which displays the progress indicator in the UI. Everything is working as expected. Perfect. Then the Stream returns the first results from the database, so we set <???>State.

Well, there it is, our problem. We wrote the listening method always to set the NormalState, as it is the default one showing data. When we had set it, we would lose the indication of loading which is still in progress. This is not what we want. There is a solution to that. We need to set the boolean flag isLoading when we put the LoadingState for the first time, and then in the listening method, we would check if it is true. It is pretty simple and intuitive and should be the go-to solution.

There is another solution to that problem. It may simplify the implementation of screens that observe the same data (list of items and single item in detail screen), so we would need to duplicate parts of code to those places, and as we know duplication of code is a big no-no.

Introducing StreamData

The solution would be to add the loading status into the data stream itself. In previous parts we introduced Response and Result as our wrapper class for data. This time we will do something similar.

Introducing StreamData

We only have loading and ready data, as we handle errors using Results. Let’s update our code to use our new classes. Before we do that we should create a typedef for this stream. The full-size version would be a bit snippy in the long run.

typedef DataStream<T> = Stream<StreamData<T>>;

Updating the repository

Now we need to update the repository. When we change Stream<List<ContentData>> -> DataStream<List<ContentData> the analyzer will raise an error as we are not returning Stream<StreamData<List<ContentData>>>. How to combat this, and how to differentiate if we are loading or not?

We want it to be reactive, so we need to have control of the stream. For that, we can use this LoadingStatus class. (BehaviourSubject is a part of rxdart package)

LoadingStatus uses a StreamController to push boolean value (to inform if we are loading or not) events to the stream. We are changing the state of loading using setter isLoading. The exposed listen method that returns Stream<bool> is what we are interested in. It will help us push the ReadyData or LoadingData in the final DataStream.

Now, we can tackle the error that the analyzer is shouting about. We need to connect both streams to achieve the result type of DataStream. In code the change will look like this:

Ok, this is great. We combine the stream of data with the stream of loading state. When either of them emits, we will update the StreamData that our UI is listening to.

Writing every time the combining method might be annoying. To simplify that we can use this extension method:

Now we can finish the changes required in the repository. Currently, we are connecting both of the streams, but we do not update the loading status when actually loading the data.

As we can see, adding a better mechanism for showing users the loading status was not that demanding. After introducing the required classes (StreamData and LoadingStatus), we only have to write a few lines of code to add support for it.

Listening to the DataStream in UI

Listening code in the UI will be different for everyone, but exemplary code will look like this:

That is the end of the series. I hope you liked it (👏 appreciated) and got something useful from it. If you have some ideas on how to improve this solution, write a comment and share it with others.

Code with these changes can be seen on GitHub (branch: loading_status)

--

--

Paweł S

Passionate Android/Flutter developer that strives for perfection, which can never be achieved.