Today, we are writing applications. And each application is connected to the Internet. Let’s just talk about those applications which are connected to the Internet. Often, when users perform some operation in the application, a server response is needed, so while this response is being performed, the user is staring at this:
There’s no problem with that if you really need to have a server response, and you need to have 100% correct data only. But the thing is that there are a lot of times when we can easily avoid showing the spinner to the user, and trick the user, showing him what he wants to see rather than showing spinners during every user interaction, even if those spinners are cute.
Types of user interaction
Let’s look at user interactions from the perspective of user expectations. When doing some operations in the application, the user can’t be sure about the result — before a publication is opened, it’s hard to predict its contents. On the other hand, some operations are highly predictable for the user:
- liking a favorite post
- writing a comment
- following/unfollowing another user
Often all these operations require an internet connection in order to be executed, and in badly designed applications, we often see a spinner after each operation. In very badly designed applications, we see a spinner which locks the whole application screen (and which we don’t have the ability to cancel).
Somehow we need to convince the user that these operations are performing immediately. And here we’ll explain how we do that in our applications.
This approach doesn’t require big architecture solutions from the start and can be implemented at any moment of the application development cycle, without any harm to the original. Still, there are few things which are not required, but highly recommended. These things aren’t new and you can find a lot of information about how to use them correctly and why you need to have them in your application.
View Layer without logic
Taken from MVVM pattern. The main idea is that the View Layer doesn’t know anything about your business logic. All that this View Layer should care about are simple, plain objects, which have no business logic in them, or at least the View Layer shouldn’t know anything about it:
Updateable View Layer
The View layer should reflect any data changes that are performed in the data it renders. Let’s say a person’s name was changed. Your view should behave correctly, and be able to update itself when this happens. In our examples UIViewController will be the object which will respond to the changes in the model and will tell View to update itself with new data.
This is the object that is responsible for network operations in your application. You should have one (I hope you do). Also, it’s even better if your ViewController speaks with the Model layer, and the Model layer itself speaks with Request Manager. Again, this is not required, but this is how it works in our applications.
Here’s how(probably) your application works now. This is waaay too schematic, but you can update it with your own Structural Units.
So here’s what happens:
- Model gets the data from internet
- ViewController listens to the data from Model and updates View
- User Performs some Action on the View
- Spinner starts
- View Controller tells Model to handle action
- Model goes to the internet(to the server, for example)
- Model gets response from the server
- ViewController’s callback got called
- ViewController updates view
- Spinner stops
Let’s add one small thing
In order to trick the user we’ll need to have just one simple thing — the Optimistic Model.
The idea behind that is pretty straightforward. When the user changes something, the optimistic model gets the original object, tries to predict the potential result, then calls ViewController’s callback with this updated object, and then performs the actual operation in the background like the default Model. Once the real result is returned from the server, the optimistic model calls callback with the real object, or in case of an error, the Model performs callback with the original object and then calls error callback.
If we highlight possible flows, they can look like this:
Optimistic Model Implementation
Now, let’s look how the Optimistic Model implementation can look like
With this simple update we were able to perform network operations “immediately” (at least from the user perspective).
What do we have as the result?
- A happy user who doesn’t need to wait for each operation to finish:)
- A happy user, who thinks that your application is blazingly fast(even if your server is really slow).
- In the example, we’re copying the object. This is done to prevent some unexpected changes, which can occur, for example, when there are other references on this object in the application.
- In the example, in server response, we have a fully updated object. In the real world application, this rarely true. Anyway, even if server responds with partial updates, this approach can work correctly.
- The Optimistic Model can be simply used for independent properties or groups of properties of object.
- Depending on your needs, you probably would need to have Local Storage, which will store data that has not been sent to the server yet.
- In complex case, when user tries to perform different operations quickly on the same object, you will probably need to use Temporary/Shadow Storage, which will decide what to show. An example of the implementation of this object will be covered in next post.
Now, press “recommend” button, and measure how long it takes to perform one simple action. Do you see the spinner there? :) So why would you put spinners in your application?