Page 5 of 8
Step 4 - Enhancing the user experience with home screen Widgets
Widgets, despite being an extension of an existing app that's already installed in the user's device, are treated as separate applications that live on the home screen and at a glance offer up-to-date information from the 'parent' application.In this case, the Baking App widget should display the ingredient list for the desired recipe.As simplistic as it sounds, in reality it's a tedious process that involves WidgetProviders, RemoteViews, Services and Broadcast receivers and more.
Let's take it step by step, first looking at the official definition of the AppWidgetProviderInfo:
"AppWidgetProviderInfo describes the metadata for an App Widget, such as the App Widget's layout, update frequency, and the AppWidgetProvider class. Define the AppWidgetProviderInfo object in an XML resource using a single <appwidget-provider> element and save it in the project's res/xml/ folder"
The one for Baking App is stored in 'baking_widget_info.xml'.This file contains all the necessary information in setting up the widget's initial appearance according to the following specifications:
The AppWidgetProviderInfo also sets the interval that governs how often the widget gets auto-updated by means of a system-wide timer
The layout file, 'layout/baking_widget_before_recipe.xml'
sets the widget's thumbnail image to that of a recipe book icon 'ic_chrome_reader_mode_black_48dp' and accompanies it with the text "RECIPE INGREDIENTS WILL APPEAR HERE AFTER LAUNCHING BAKING APP".
There are sill two more layouts, 'layout/widget_grid_view.xml' for the GridView with the list of a recipe's ingredients:
<?xml version="1.0" encoding="utf-8"?>
and 'layout/widget_grid_view_item.xml' for the individual TextView items directly mapped to each GridView's cell:
<?xml version="1.0" encoding="utf-8"?>
Each one of these TextView elements will contain the ingredient's title concatenated with its required quantity and measure.
It's important to note that those two widget layouts are based on something called RemoteViews and RemoteViews have some limitations on what kind of layouts they can support. A RemoteViews object and, consequently a widget, support the following layout classes:
As you can see the most popular views are indeed supported, but others like Constraintlayout or Recyclerviewer are not.
The next component to look at in a Widget's hierarchy is the AppWidgetProvider. A widget is in fact a BroadcastReceiver, i.e it listens for events taking place in the Android OS and when it notices that one carries a message destined for it, it captures that message's associated Intent and works on it. The AppWidgetProvider acts as a convenience class that replaces the BroadcastReceiver, since everything you can do with a BroadcastReceiver, you can do with an AppWidgetProvider plus it lets you parse the relevant fields out of the Intent received in its
The AppWidgetProvider also defines the basic methods that allow for programmatic handling of the widget's states of getting updated, enabled, disabled or deleted. We are expected to override one or more related callbacks,
onUpdate(Context, AppWidgetManager, int),
onDeleted(Context, int), onEnabled(Context)
to implement our own AppWidget's functionality.
First, as with all BroadcastReceivers, we have to declare the AppWidgetProvider in our application's AndroidManifest.xml file.
<receiver> element requires the
'android:name attribute', which should point to the specific subtype of AppWidgetProvider used by our app's Widget, namely the BakingWidgetProvider.
<intent-filter> element must include an
<action> element with the
'android:name attribute' which specifies that the AppWidgetProvider accepts the
ACTION_APPWIDGET_UPDATE broadcast. This is the only broadcast we must explicitly declare, as the system-wide AppWidgetManager automatically forwards all other Widget broadcasts to the correct AppWidgetProvider as necessary.
<meta-data> element specifies the AppWidgetProviderInfo resource and requires the following attributes:
android:name - specifies the metadata name
android:resource - specifies the AppWidgetProviderInfo resource location
What this all means is that when our BakingWidgetProvider
receives a broadcasted message with the
action, a custom action that deviates from the default
is in fact notified that there was an update in the parent app and that there's also an associated bundle (the list of ingredients) attached to it. In turn the BakingWidgetProvider extracts that list to get all widget instances associated with our BakingWidgetProvider and refresh their state.
It Is widget(s) in the plural since the user can place as many widget instances as he sees fit on the home screen. So this code notifies all of them of the change so that they can update their views with the new information.
Here is where the RemoteViews come into play:
RemoteViews views = new RemoteViews(
Inflate the 'widget_grid_view' layout as the main container RemoteView and set its RemoteAdapter with:
Intent intent = new Intent(context, GridWidgetService.class);
In essence GridWidgetService provides the RemoteView with its shape so it needs to extend the RemoteViewsService base class which connects the RemoteAdapter adapter to its RemoteView. Since it's a Service it also needs to be registered in our app's manifest:
GridWidgetService's nested inner class GridRemoteViewsFactory must implement the RemoteViewsService.RemoteViewsFactory interface which is a thin wrapper around the RemoteAdapter. In there we can find a typical Adapter's method counterparts, just named differently. As such,
onBindViewHolder is replaced by
getViewAt in the RemoteViewsFactory,
getItemCount is replaced by
dataSetChanged is called once the RemoteViewsFactory is created and every time it gets notified to update its data through
What's left is to set each Remote GridView cell (widget_grid_view_item) to the proper ingredient inside