My latest project at Jana was to create a new widget for mCent which will allow users to see which apps they use the most and launch them. Widgets have some interesting quirks that make them a little different from the standard view- this post will walk you through creating a simple grid styled widget. Much of this post uses example code taken from this sample: http://docs.huihoo.com/android/3.0/resources/samples/StackWidget/.
For my app launcher widget, I initially planned to use a RecyclerView and make calls to the server to get top app stats from the widget code directly. Neither of those things were possible. One of the largest differences between coding for a widget and a regular in app feature is in what views you can use. Widgets have a very limited list of views and layouts available to them. The full list can be found here: https://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout. Before planning out your widget, you should first check and make sure that the UX you want is doable with those items.
Thankfully, GridView, ImageView, and TextViews are all on that list, so what we are aiming for in this post (a grid of app icons and text for a launcher) is definitely possible.
The first step in coding up a widget is to create an xml layout that defines what the basic structure of the widget will be. Here is the one we use in our example:
The next step is to make a class that extends AppWidgetProvider (http://developer.android.com/reference/android/appwidget/AppWidgetProvider.html). Be sure to remember to include this provider as a reciever in your AndroidManifest file like so:
Also note the android:resource points to an xml file named top_apps_widget_info that defines various properties of the widget- I’ll explain in more detail after we are done with the Provider.
In the AppWidgetProvider, you must override methods to get the behavior you want. For our purposes, we will override the onUpdate method and the onRecieve method.
OnUpdate is called when you set an update intent on the widget elsewhere in your code, as well as at a set time that you define in the info xml file mentioned above. It gets the context, an appWidgetManager (http://developer.android.com/reference/android/appwidget/AppWidgetManager.html), and a set of appWidgetIds to update. We will iterate over the widget ids, and for each one use RemoteViews to set the UX up in the way we want. Before updating the UX however, you will want to make sure that the data the UX is using is updated first. In our example, this data is app names and icons that the widget user uses the most. Within the provider, before iterating over the widget ids passed to the onUpdate method, first call the functions that update your underlying data store- you will not be able to pass data directly to the GridView, so you must store it elsewhere, such as a DB or SharedPreferences. This is because the GridView will be drawn using an adapter- a background service that we will write later in this tutorial. For now, call
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.grid_view); at each iteration of the loop to send a message to the service to update the data it holds from the storage we just used. Then, we must attach the adapter to the GridView using an intent. The following code does so:
After attaching the adapter, we must also set up our empty view:
This is what’s shown if there is no data for the adapter to display.
After that, you’ll want to modify your views through remoteViews- if there are certain things you want hidden/visible based on your app state, you can do that here.
If you want the things in the grid_view to be clickable, you need to set up a pending intent as follows:
Additionally, you’ll need to override the onRecieve method for clicks to work. OnReceive will be called when one of your items is clicked on- You can get the intent action and extras from the intent passed into the function, and based on that perform different actions.
Finally, back in the onUpdate function, call
appWidgetManager.updateAppWidget(appWidgetId, rv); to make these changes take hold- now we’ve finished the Provider!
Next up, we’ll create the widget info file- this is a simple xml file that defines various properties of the widget such as starting height and width, update cadence, and more. You can read up on the individual properties here: http://developer.android.com/reference/android/appwidget/AppWidgetProviderInfo.html
After that, we’ll want to set up the grid item’s xml file- basically, what each individual item inside the grid will look like. This is what our grid item xml file looked like:
Once you’ve created the widget info file and the grid item xml file, you’ll need to create the last peice of the puzzle: the service that will populate the grid view. It should extend RemoteViewsService and override one method: onGetViewFactory, returning a custom viewFactory that implements RemoteViewsService.RemoteViewsFactory. In this remote view factory, we handle the data that populates the grid view as well as the creation of each individual grid view item. Remember to add this to the android manifest as well! Our manifest code looks like:
In the onCreate method of the factory, you want to simply set up connections to data sources such as DB’s or shared preferences. It is inadvisable to do anything that will take much time here as taking too long will result in an ‘Application Not Responding’ issue. Similarly, override the onDestroy method, clearing any connections made in the onCreate.
Next define the onDataSetChanged method. This is called by the notifyAppWidgetViewDataChanged function we called in the onUpdate of the provider earlier- it is meant to be the method in which we access our data store setup in the onCreate method of the factory and pull down any new changes to it. Store the list of items you want to save in a way accessible to the factory (like a class var).
Next we need to override getCount(), which should always return the number of items in the grid.
Finally, we will set up our the getViewAt method. This is called when the widget is actually trying to set up the UX for the grid- it is passed a position an called once for each number from 0 to getCount. Within this method, create a new RemoteViews based on the grid view item xml before like so:
RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.top_apps_widget_item);
Then, get the data from your datasource corresponding to the item at this position. Use that data to populate the remote view. In our example, we call
rv.setTextViewText(R.id.widget_item_app_name, rank + ". " + item.getAppName());
to set the app icon and name.
Then, if we set up click behavior before, we specify this individual item’s click behavior as such:
Finally, return the remote view and it should draw correctly.
If you’ve done all the above correctly, your widget should displaying as you expect it now! There are a lot of interconnecting pieces in coding a widget, so be sure to carefully review your code if you find your widget isn’t working correctly. Also, be sure to check out the code sample at http://docs.huihoo.com/android/3.0/resources/samples/StackWidget/ to see a fully working StackView widget- there isn’t much different between a StackView and a GridView widget, and the code sample there helped me immensely when I was first trying to code this widget.
P.S.: If you’re interested in awesome android development or have other programming chops, the tech team is always looking for talented people! See http://jana.com/careers for more details.