Back

Optimistic Models 2 : Handling Multiple Interactions Without Server Confirmation

Hi there. I hope that you’ve already read the previous post about why you shouldn’t show spinners in your applications, and how you can actually avoid it.

Let’s recap a few things here. Our main goal is to allow the user to feel that every action he/she performs in the application happens immediately, blazingly fast, even if some actions are performed slowly. We’ll be working with non-confirmed operations here a lot, so we cannot use this approach if we’re about to work with highly-sensitive data, such as credit card information, payment information, etc.

Previously we mentioned that in order to make an application look faster, we can predict some operations and show the results to the user without receiving actual confirmation from the server. Despite the fact that there is always a delay when an application communicates with the server, we were able to successfully hide that from the users.

However, this will work well only for simple situations, when the user doesn’t perform actions too quickly, and when we’re actually able to perform them fast enough and not bother about cases, where multiple concurrent operations are performed on the same object. In case there are multiple operations performed, we’ll need something a bit more complex than a simple optimistic model.

Users can be pretty damn fast Let’s consider the following example. The user is working with an object (let’s say a post) and every operation she performs needs to be confirmed by the server.

So here is a list of actions that the user performs:

  • likes the post
  • adds a comment to the post
  • changes post rating from 2 to 5
  • unlikes the post
  • updates the title to “Handling multiple interactions without server confirmation”
  • changes the post rating from 5 to 2
  • changes post rating from 2 to 4
  • likes the post

Let’s say that there’s no such thing as a “Save button” on the screen, and the user expects that every change she makes will be automatically saved at some point. Also, let’s imagine that we cannot wait until the user finishes all his operations (In real life we could probably wait some time before sending a request to the server, then group changes that the user has made, and only then send a request).

Base implementation of this logic can be described like so: Just send the update once the user changes something, and show the results from the server. This implementation often leads to blinking and showing incorrect (from the user perspective, but 100% confirmed on the server side) information to the user.

It probably still can work fine… until operations start to fail.

If we aren’t using an optimistic model, it will be hell; the only thing you can do then is to tell the user “Sorry, something went wrong; please revise all the changes you’ve made in the last four minutes, and check to see if everything looks fine.”  Some application decides to show tons of alerts — for each failed operation.

Optimistic models 2

But even using the optimistic model, it’s hard to correctly handle this situation. If there are a lot of operations, we would have a state based on unapproved states and this chain will break.  :(

Storing confirmed objects and updates

In order to handle those situations, we could use another trick — first, we need some Model in our application which can calculate the state of the object by giving the model a Starting state and list of operations which should be performed on that object.

Next, when an operation is performed on the object, we add this operation to the list of unconfirmed operations, and recalculate the current object state. Once the operation succeeds, we should update the confirmed object and remove the finished operation from the list, and then recalculate the state again. If the operation fails, we just need to remove this operation from the list and recalculate the visible state.

That’s all. Just by adding this one simple rule, we can correctly rollback our failed operations.

Queueing updates

In order to simplify your life, I highly recommend preventing concurrent updates on the object. On one hand it will require having some queue for each object, but on the other hand it will allow you to handle complex cases faster.

Grouping and discarding operations

When we have list of operations that need to be performed on the object, we can simply run them on the server one by one, and update the visual state correspondingly, using the confirmed object + set of operations which are left.

If we know that a list of operations can become too big, we could start to use some optimisations in order to decrease it.

Take a look at the previous example, with the post. What can we change there?

  • If our server accepts object updates and we can send the whole object with changes, we would next: {like}, {comment:, rating:4, title:”title”}.

  • If our server doesn’t accept object updates and works per property basis, we still have some things to optimise.

Retrying operations

We can add one more thing — if the operation fails, we won’t throw it away immediately. Instead, we would retry it again a few times after some delays (Actually, it’s good to have this optimisation even if we’re not using optimistic models).  :)

We can go even further. Instead of blindly repeating the same operation, upon operation failure, we can check whether or not the result of this operation was already overridden by the next operations. If so, we can throw away the current operation and start the next one.

Sum up

We’re often bound to the servers in our applications. The server is the king. The server decides what to do, what the current state is, and how to perform complex business logic. Often the connection to that server isn’t fast and not reliable enough. By moving some parts of simple logic on the client side, we shouldn’t often have to wait for confirmations and can go on even without server confirmation.

P.S

This article was intended to be published a long time ago, and since then a lot of things have happened. One of them is that Parse has revealed their SDK source, and there you can find that some operations are performed in the way similar to that described in this article. Still, I decided to publish this article; maybe not everyone has looked at the Parse sources yet. :)

 


If you have any questions about app development – let us know!


Other useful links:

October 19, 2015
  • Petar

    Nice article. I didn’t get how the backend responds to updates though. Do you always send a list of required changes to be done on a given object, or each change is being send to the server separately ? If the first case is true, then, how is this being managed on the backend ? Is there a fixed set of change-types that can be applies ?

    • Kilew

      It doesn’t have a big difference, actually, it will only affect grouping and discarding part, described in the post. As about backend, we assume, that operations we’re making are pretty simple and predictable (add/update/change), with no complex business logic behind it. Here I assume that you have kind of updateByField API method, or updateFields method. Server just need to apply those changes and returns updated value field or updated object itself. that’s all.

      Have I answered your question? :)

      • Petar

        Let me try to summarise what you are suggesting. So instead of making an API request for every change, you group them in bulk and send batches to the backend to process ? Is there anything else that I have missed. Also, have you implemented successfully this approach somewhere ? Do you know if there are other products who are taking advantage of this ? thanks

        • Kilew

          We only grouping changes, if server supports that kind of API.

          If it doesn’t – you just calling api for every change, and removing those are “duplicating or overriding” previous changes (i.e setValue:1, serValue:2,, setValue:3) -> (setValue:3).

          Partially implemented in the project: http://munch-map.com/

          As mentioned in the post – Parse DSK is using this technique have .

ios development