In the three preceding articles, I talked about the fly-in application menu and how to implement it on Android. For those who want to catch up on the Making of Prixing series, here are the links:
- The making of Prixing #1: Fly-in app menu
- The making of Prixing #2: Swiping the fly-in app menu
- The making of Prixing #3: Polishing the sliding app menu
This time, I would like to talk about the way we’ve managed notifications in Prixing 3.0. Indeed, Prixing for Android is a rather complex application containing tons of screens. Each of these screens can notify the user about something important: an error occurred while connecting to the server, you earned a new badge, the query you typed is too short, etc. By default, Android provides two great ways to notify the user without blocking: status bar notifications and
Toasts. The first is dedicated to notifications happening outside of the application while the latter is for in-app notifications. Simply put, a
Toast is a non intrusive and non touchable notification that is always displayed on top of your application’s screens.
From my point of view, this class is amazing. I really love it and that’s probably one of the reason why I am getting so frustrated when developing on iOS. Indeed, the platform made by Apple has no equivalent by default. Developers generally use a
AlertDialog equivalent) which is extremely annoying to the user as it completely blocks the user flow as long as one of the “Cancel” or “OK” buttons hasn’t been pressed. As a consequence, iOS developers tend to notify the user via in-layout notifications. It is not consistent throughout iOS apps but the usability gain definitely worth it.
Toast makes unobstrusive notifications amazingly easy as you can create new instances from everywhere as long as you have a reference to a
onReceive method, etc.).
Unfortunately, Toast is far from being perfect and I am not entirely satisfied with it. Toast can be un-accurate in some cases. Indeed,
Toast has one major drawback: it completely breaks contexts. This issue can be reproduced effortless. Let’s say a user is currently in an app firing a
Toast and wants to switch to another application using the dedicated “multitask” button. The Toast will remain on screen even if the brought-to-front application has nothing do to with the previously shown app as described on the following figure:
Sample of a user flow in which Toast fails
As you can easily notice, the problem with
Toasts is they are persistent1. Once a Toast has been fired, it is displayed on top of any screen and remains visible for the duration specified at its creation.
Information is always context-related
When designing and developing an app, you always have to spend a lot of time thinking about contexts. Contexts are insanely important as they define the space/time frame an information can live in. Exposing information in the wrong context will make your UI incomprehensive and will radically degrade your users' experience. Can you imagine Google announcing a new Android version in the middle of the annual world dental congress? I guess and hope your anwser is no. This example clearly demonstrates information and context are close and depend on each other.
As of this logic, Prixing currently features 3 main contexts:
- The application context is where images and network data live in. All of this information is not tied to a particular screen or widget and are shared among all Prixing’s screens.
- The Activity context contains the information belonging to a screen (profile, nearby stores, products list, etc.).
- The application menu context: Introduced by the fly-in app menu, the application menu context describes the entire app navigation. It is completely independent from the two previous contexts.2
In order to bypass the
Toast persistence problem and ensure information is displayed in the correct context, we decided to create a new notification system in Prixing:
Activity-bound notifications. In reference to the
Toast class and to my French culture I decided to name it a
Crouton (if you don’t know what a Crouton is go to this Wikipedia page). This is what it looks like in the current version of Prixing:
Crouton overcomes the main issue of having a
Toast being shown while the menu is open. It sticks to the current screen sliding with it and leaving the menu completely free of any information that would have not been related to it.
Introducing the Crouton API
The Android framework lets you interact with the
Toast class via a very simple API. You usually only need a single line of code to create and show a notification. We wanted
Crouton to be at least as easy to use as the
Toast class so we engineered an insanely simple API:
- Public Constants
int STYLE_ALERT: Show the text notification for a long period of time with a negative style.
int STYLE_CONFIRM: Show the text notification for a short period of time with a positive style.
int STYLE_INFO: Show the text notification for a short period of time with a neutral style.
- Public Methods
static Crouton makeText(PrixingActivity prixingActivity, CharSequence text, int style): Make a Crouton with the given text and style.
static Crouton makeText(PrixingActivity prixingActivity, int resId, int style): Make a Crouton with the given text (extracted from the resource identifier) and style.
void show(): Show the view with the specified style.
Yes, you’re right! No visible constructors, just 2 static factory methods, a public method and 3 constants. The two main differences with
Toast are you always need a
PrixingActivity instead of a
Context and the third parameter isn’t the duration but the style of the notification.
Contrary to the
Crouton automatically computes the duration of the notification depending on the style. Pretty logically, we decided an alert notification had to be visible for a larger amount of time than a confirmation or neutral information. People usually spend more time on understanding an issue than a validation message. As a result, we decided to use 3000 milliseconds instead of 1300 milliseconds.
Crouton handles multiple notifications fired at the same time just like
Toast does. In order to do so, it stacks
Croutons in a queue if another instance is currently being displayed. This makes sure no notification is lost as long as the user remains on the emitting
Implementing in-layout notifications
The Android framework contains several classes inheriting from
Activity. Each of this
Activity-subclasses can be used to manage particular components. For instance,
ListActivity manages a
MapView, etc. All of these subclasses may look helpful for developers but they can also be a pain in the ass when engineering a rather large project. Java not allowing multiple inheritance and not supporting categories/extension methods (yet?) makes it pretty boring and tedious to add features to Activity-subclasses. As a direct consequence, trying to have a consistent layout in your Activity is hard. So here is a tiny advice to you: always favor composition over inheritance in your projects.
To overcome this issue, all Prixing
Activity implements an interface called
PrixingActivity. Roughly speaking,
PrixingActivity manages several
Activity-based behaviors and ensures the Activity root layout is as follows:
As described on the previous figure, a
PrixingActivity always contains a
TextView which is laid out by our custom
TitleBarHost in a way it is above the
TitleBar (custom implementation of the
ActionBar) and slides down when the notification needs to be shown.
To have consistent and smooth animations, we used the same interpolator introduced in the first part of this serie when showing/hiding a
Finally, some of you may wonder why the notification is not lazily created/inflated thanks to a
Crouton currently relies on a single
TextView. Using a
ViewStub is clearly not a performance boost in this case as the final
View hierarchy is small. Keep in mind a
ViewStub remains a
View. As a result, using a
ViewStub would have resulted in inflating 1-min or 2-max
View. The method used in Prixing ensures only a single
TextView is inflated. If you really care about not creating a
TextView that is often never used, you can lazy-create it the first time it is requested.
In-layout notifications may not be perfect but they can solve lots of UI/UX problems. Implementing such a mechanism in Prixing made the application easier to use and more logical. The fly-in app menu clearly outlined some of the major drawbacks of the
Crouton solved. Don’t use
Toast blindly, think about your application’s context and the information you need to display. In other words: leave the engineering mindset aside and start thinking like a real user.
Thanks to @franklinharper for reading drafts of this.
A Toast is actually theoritacally persistent if and only if you don’t call
Toast#cancel(). In practice, nobody keeps a reference on the fired
Toast#cancel()documentation clearly states it shouldn’t be used and, last but not least, the current
Toast#cancel()implementation contains a magnificent
// TODOannotation informing the method is not complete:
// TODO this still needs to cancel the inflight notification if any
Contexts are everything! This is I why I believe an
ActionBarhas to slide with the content when using a fly-in app menu. An
ActionBarshould only be used to describe the current screen/
Activitycontext. As a result it has nothing to do with the application menu. The current version of Prixing, Google+, Spotify, etc. have it right while some others such as Youtube have it wrong in my opinion.