Android widget adventures

George Venios
pxHouse
Published in
4 min readOct 12, 2017

One of the main motivations for building Done was to overcome some of the frustrations of the current top task management apps and craft the best mobile daily planner.

When trying to manage tasks, the tools you’re using should fade in the background. The standard to which I hold task management apps is the experience of using pen and paper: Adding, crossing out and seeing pending tasks should be this simple and intuitive.

While having a good in-app UI is desirable, task management apps benefit immensely from also having a functionally equivalent home screen widget. Therefore, the goal was for Done’s widget to behave the same way as the in-app UI, ensuring the transition between the in-app and widget experiences is seamless. They’re all part of the same app so it is only natural they would look and feel the same.

It was then clear that the items on the widget list need to be expandable. At first this looks impossible. Android offers no obvious way to make items in a ListView expandable, let alone allow doing it over RemoteViews.

Looking more closely to what similar apps have implemented suggests that some solution should be possible. For example, on list item clicks, Any.do’s widget draws the Mark As Complete action in place of the task item, so it seems that we can at the very least swap clicked item layouts. Also, as Todoist’s widget proves, it is possible to emulate more advanced functionality like Contextual Actionbar and multi-selection in our widgets.

Todoist’s widget with a selected item

Indeed, if we look at the problem from a different angle it becomes apparent that if we get our hands a bit dirty these behaviours are well within reach. What we’re really after is allowing clicks on an item to update said item’s view.

In-app this is trivial. When building the item layout in the adapter, all you need to do is attach a click listener, which on click lets the adapter know that it should toggle an item’s selected flag. The adapter then updates its internal selectedIds, and triggers an item update, which in turn causes onBindViewHolder() to be called, allowing us to draw the selected version of the item view. In Done’s case, we only allow a single item to be selected at a time, and selection means expansion. This happens by toggling the visibility of an actions_container view in the item’s layout.

Selectable items in-app

RemoteViews on the other hand have no concept of a click listener. What they do have instead is the ability to fire a PendingIntent when a view is clicked. This works brilliantly when you just need to trigger an action, but what about when you need to update the state of the item instead?

The key to get to the solution is realising that we need to make our RemoteViewsFactory behave a bit more like an adapter: it should be able to hold (selection) state, and the items should be able to update it. Since the link between actions and adapter state is broken, we need to restore it some other way.

One way to update RemoteViewFactory’s selection state from external components is by delegating that logic to another class, and then use it as a collaborator in the factory itself. We’ll call this class WidgetSelectionStateStore. On a basic level, all this class does is keep the selected item id (per widget instance, if you’re feeling generous). Depending on your needs, it can either persist (database, shared preferences etc) or it can be a singleton with static fields. This way we can change a RemoteViewFactory’s state from any component, regardless of its ability to access the factory itself.

We can now wire our RemoteViews, their factory, the WidgetSelectionStateStore and the widget item’s click receiver to emulate the Adapter’s behaviour.

Clicking on an item fires a PendingIntent that launches a receiver, effectively replacing our click listener. Like the listener, the receiver is responsible for informing the WidgetSelectionStateStore that an item needs to be toggled and then requesting a widget update which will trigger the RemoteViewsFactory’s onDataUpdated() method and request new RemoteViews for all list items. Here, in our getViewAt() method we set the “actions container” visibility to either visible or gone based on the selection state returned by our (now updated) WidgetSelectionStateStore.

That was it, our collection widget now supports selectable items!

Selectable items in RemoteView lists

Conclusion

This pattern is not limited to enabling item expansion in list widgets but can be adapted to implement multi selection/checkable items, contextual actions and any other layout update based on item state and user interaction.

For Done it allowed keeping the interface consistent, providing a more intuitive user experience and getting us one more step towards our goal of effortless task planning.

Done is a day-to-day task planner that aims to be the next best thing after post-it notes. Get it here!

--

--