If you enjoy talks from 360 AnDev, please support the conference via Patreon!

In a lot of applications, users are constantly performing operations that require some form of communication with a server. Whether it’s the loading of content, the sending of a message, or the changing of a setting – how many times have you used an app that made you watch a progress bar? This is a pessimistic approach to crafting – the fear of not knowing whether what is being done is going to be successful.

Introduction

My name’s Joe, and I’m an Android engineer at a company called Buffer. I’ll cover what pessimistic and optimistic UIs are, and how it applies to user interfaces and applications.

Optimistic UI, Pessimistic UI

Optimism: positive thinking

Pessimism: a negative outlook, which leads to taking a safer approach to things.

At Buffer we have been trying to improve the user experience. Buffer is much like Twitter and Facebook, where you can post updates, and read the updates of other users on the queue. To make an update, we use our composer, then enter the text and photos. If you’re not on a fast network, it can take more than 20 or 30 seconds. This can be considered as a pessimistic UI.

Get more development news like this

On the other hand, with optimistic UIs, you can completely remove the stages of waiting, and put the work into a background task that will not block a user. Here, you have confidence with your API.

Blocking UI, or pessimistic UI is still a trend, why is this?

It’s because doing such UI is safe and easier to implement. In development, this is a default operation - when we do a network request, we show a progress dialog.

Issues With Pessimistic Approach

First, the user has to wait, and it removes the ability to multitask. As a result, it makes an app feel slow.

In our app, if a user was free to move around, they could compose an update, reorder things, and manage a social media schedule in the whole time that that.

The Rationale Behind Immediate Feedback

There is a white paper called the Response Time Between Man and Computer Interactions , by Robert Miller, which states that the time between our finger leaving, depressing from a button press, is 100 milliseconds. We expect to see some form of visual feedback that that task is being carried out on the screen. 100 milliseconds is equivalent to a human blink.

Moving on from Pessimistic UI in Our App

Places that can benefit from moving away from Pessimistic UI.

Finishing Activities: on the screen where we compose an update.

Progress Dialogs: when it’s successful, the activity can close, otherwise, we can handle the error.

Background Tasks: here, we can buffer an update and kick off a service.

Using Intent Service

The intent service class is part of the Android framework that allows you to perform asynchronous works off of the main thread. We can pass it in an Intent and that Intent can contain data.

Here, we have an update object, and we pass that in within the Intent so that it can do some work. This allows flags like START_REDELIVER_INTENT. If the service is destroyed by the system, the service will be restarted with the same intent that we created it with.

Job Scheduler class

val jobScheduler = getSystemService(JOB_SCHEDULER_SERVICE); val componentName = ComponentName(this, SomeJobService); val jobInfo = JobInfo.Builder(12, componentName) .setOverrideDeadline(3 * 1000) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .build(); jobScheduler.schedule(jobInfo)

The job scheduler class allows you to schedule different jobs against the Android framework. Using this job info builder class, we can specify criteria to perform operations against like specific timing or network types that we require.

If someone buffers an update for two weeks time, we don’t need to post it instantly to the API. We can schedule it for when the user’s connected to a wifi network or some form of a better connection. Job scheduler allows us to be more intelligent with the jobs that we’re scheduling.

We’ve scheduled our task, we’ve hit off a service and this is happening in the background You can pass data back from your activities, or from within the service, you can update your database or adapter with the content that was created in that class.

Manipulate Local Data During Request

When we buffered an update, we instantly updated the database that we had. When an update’s buffered, we can add it to our database at the point that we hit that request off. This is being optimistic because we’re sure that this is going to be successful and we’ve added this to our content queue.

someService.createUpdate(update) .onError{ return CreateUpdateError(update) } .startWith{ database.insertUpdate(update) } updatesDatabaseObserver.subscribe({ updates -> recyclerView.swap(updates) })

If you’re RxJava, you can start with requests. For example, when you hit off the service to create the update, you can use the start with a prompt that you will insert an update into the database when the service kicks off. You can do intelligent things with the error, and you can handle that error space specifically.

Manipulate Local Data and Sync

We’re working towards having a database that reflects our most up to date content in our application. This allows you to be more flexible and more optimistic in what you’re doing, but it’s more work and requires more steps.

Reflecting State

It’s important to reflect the state. The user needs to know what state the content is in. There are great examples of apps that do this, example, Trello. If you reorder cards and you’re offline, you get a sync icon to let you know that that card hasn’t quite finished updating the server.

This also occurs with Facebook and Instagram. If you like a post, the action has been recorded, and the user is not being blocked, as the state is updated.

Failed Content

If you do encounter errors when you are trying to be optimistic, the user needs to know that something has failed. In the case of simple things, e.g. content cues, we show a failed message on the card. If the user tries to buffer an update and that service fails or something doesn’t quite go as planned, we make sure the card is marked as failed.

Error Dialog

You can show error dialogs, but that goes against the whole point of this talk. Don’t do that unless as an absolute last resort because you throw the user off and you take them away from what they’re in the middle of doing. Instead, we can be more intelligent. We use snackbars because it allows us to show errors for any of this content for our apps.

Notifications

User notifications are a great way of letting the user know that an error has occurred.

Heads-up Notifications

In the case of buffering updates, we use heads-up notifications because these updates are time sensitive. Heads-up notifications allow us to get the attention of the user because it’s time critical and they need to know that that update hasn’t quite gone through.

Notification Badges

Notification badges are only available as of Android O. You have to group your notifications into notification channels. They can be used to let the user know that there has been an error that’s taken place from outside of your app without needing to pester them with popping up notifications and stuff.

Optimistic & Offline

Optimistic UI and offline do tie in together in terms of design. When there’s an error state because of a lack of an internet connection, that may be unnecessary if there’s cached content that the user can be shown.

Conclusion

Make use of what we have in the Android framework, such as services. Do not block the user, and do not use progress bars. If you are syncing, use database and sync adapters. If you’re not syncing, do intelligent stuff with your cache or database and your adapters.

Lastly, use cached data where possible.