Application widgets provide your users with easy access to your application’s most frequently used features, while giving your app a presence on the user’s homescreen. By adding a widget to your project, you can provide a better user experience, while encouraging users to remain engaged with your application, as every single time they glance at their homescreen they’ll see your widget, displaying some of your app’s most useful and interesting content.
In this three-part series, we’re building an application widget that has all the functionality you’ll find in pretty much every Android application widget.
In the first post, we created a widget that retrieves and displays data and performs a unique action in response to onClick
events. Then in the second post, we expanded our widget to retrieve and display new data automatically based on a schedule, and in response to user interaction.
We’re picking up right where we left off, so if you don’t have a copy of the widget we created in part one, you can download it from GitHub.
Enhancing Your Widget With a Configuration Activity
Although our widget functions out of the box, some widgets require initial setup—for example, a widget that displays the user’s messages will require their email address and password. You might also want to give your users the ability to customize the widget, such as changing its colour or even modifying its functionality, like how often the widget updates.
If your widget is customisable or requires some setup, then you should include a configuration Activity, which will launch automatically as soon as the user places the widget on their homescreen.
Configuration Activities may also come in handy if you have lots of ideas about the information and features that you want to include in your widget. Rather than cramming all this content into a complex and potentially confusing layout, you could provide a configuration Activity where users pick and choose the content that matters to them.
If you do include a configuration Activity, then don’t get carried away, as there’s a point where choice becomes too much choice. Setting up a widget shouldn’t feel difficult, so it’s recommended that you limit your widget to two or three configuration options. If you’re struggling not to exceed this limit, then consider whether all this choice is really necessary, or whether it’s just adding unnecessary complexity to the setup process.
To create a configuration Activity, you’ll need to follow the following steps.
1. Create the Activity Layout
This is exactly the same as building the layout for a regular Activity, so create a new layout resource file and add all your UI elements as normal.
A configuration Activity is where the user performs initial setup, so once they’ve completed this Activity they’re unlikely to need it again. Since a widget’s layout is already smaller than a regular Activity layout, you shouldn’t waste valuable space by giving users a way to relaunch the configuration Activity directly from the widget layout.
Here I’m creating a simple configuration_activity.xml layout resource file containing a button that, when tapped, will create the application widget.
http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" >
2. Create Your Configuration Class
Your configuration Activity must include the App Widget ID passed by the Intent
that launched the configuration Activity.
If you do include a configuration Activity, then note that the onUpdate()
method won’t be called when the user creates a widget instance, as the ACTION_APPWIDGET_UPDATE
broadcast isn’t sent when a configuration Activity is launched. It’s the configuration Activity’s responsibility to request this first update directly from the AppWidgetManager
. The onUpdate()
method will be called as normal for all subsequent updates.
Create a new Java class named configActivity
and add the following:
import android.app.Activity; import android.appwidget.AppWidgetManager; import android.os.Bundle; import android.widget.Button; import android.content.Intent; import android.view.View; import android.view.View.OnClickListener; public class configActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.configuration_activity); setResult(RESULT_CANCELED); Button setupWidget = (Button) findViewById(R.id.setupWidget); setupWidget.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { handleSetupWidget(); } }); } private void handleSetupWidget() { showAppWidget(); } int appWidgetId; private void showAppWidget() { appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; //Retrieve the App Widget ID from the Intent that launched the Activity// Intent intent = getIntent(); Bundle extras = intent.getExtras(); if (extras != null) { appWidgetId = extras.getInt( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); //If the intent doesn’t have a widget ID, then call finish()// if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { finish(); } //TO DO, Perform the configuration and get an instance of the AppWidgetManager// ... ... ... //Create the return intent// Intent resultValue = new Intent(); //Pass the original appWidgetId// resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); //Set the results from the configuration Activity// setResult(RESULT_OK, resultValue); //Finish the Activity// finish(); } } }
3. Declare the Configuration Activity in Your Project’s Manifest
When you declare your configuration Activity in the Manifest, you need to specify that it accepts the ACTION_APPWIDGET_CONFIGURE
action:
4. Declare the Configuration Activity in Your AppWidgetProviderInfo File
Since the configuration Activity is referenced outside of your package scope, you need to declare it using the fully qualified namespace:
http://schemas.android.com/apk/res/android" android:initialKeyguardLayout="@layout/new_app_widget" android:initialLayout="@layout/new_app_widget" android:minHeight="40dp" android:minWidth="40dp" android:previewImage="@drawable/example_appwidget_preview" android:resizeMode="horizontal|vertical" android:updatePeriodMillis="1800000" android:widgetCategory="home_screen" //Add the following line// android:configure="com.jessicathornsby.widget.configActivity">
Now, whenever you create a new instance of this widget, the configuration Activity will launch, and you’ll need to complete all the options in this Activity before your widget is created. In this instance, that simply means giving the Create Widget button a tap.
Remember, you can download the finished widget, complete with configuration Activity, from GitHub.
Application Widget Best Practices
Throughout this series, we’ve been building an application widget that demonstrates all the core features you’ll find in pretty much every Android application widget. By this point, you know how to create a widget that retrieves and displays data, reacts to onClick
events, updates with new information based on a schedule and in response to user interaction, and has a custom preview image.
These may be the fundamental building blocks of Android application widgets, but creating something that just works is never enough—you also need to ensure your widget is providing a good user experience, by following best practices.
Perform Time-Consuming Operations Off the Main Thread
Widgets have the same runtime restrictions as normal broadcast receivers, so they have the potential to block Android’s all-important main UI thread.
Android has a single thread where all your application code runs by default, and since Android can only process one task at a time, it’s easy to block this important thread. Perform any kind of long-running or intensive operation on the main thread, and your app’s user interface will be unresponsive until the operation completes. In the worst-case scenario, this can result in Android’s Application Not Responding (ANR) error and the application crashes.
If your widget does need to perform any time-consuming or labour-intensive tasks, then you should use a service rather than the main thread. You can create the service as normal and then start it in your AppWidgetProvider
. For example, here we’re using a service to update our widget:
@Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { context.startService(new Intent(context, AppWidgetService.class)); } }
Create a Flexible Layout
Creating a layout that can adapt to a range of screen configurations is one of the most important rules of developing for Android, and it’s a rule that also extends to widgets. Just like regular Activities, your widget’s layout needs to be flexible enough to display and function correctly regardless of the screen configuration, but there are some additional reasons why widget layouts need to be as flexible as possible.
Firstly, if your widget is resizeable, then the user can manually increase and decrease the size of your widget, which is something you don’t have to worry about with traditional Activities.
Secondly, there’s no guarantee that your widget’s minWidth
and minHeight
measurements will perfectly align with a particular device’s homescreen grid. When a widget isn’t a perfect fit, the Android operating system will stretch that widget horizontally and/or vertically to occupy the minimum number of cells required to satisfy the widget’s minWidth
and minHeight
constraints. While this shouldn’t be a significant increase, it’s still worth noting that right from the start your widget may be occupying more space than you’d anticipated.
Creating a flexible widget layout follows the same best practices as designing a flexible Activity layout. There’s already plenty of information available on this topic, but here are some of the most important points to bear in mind when creating your widget layouts:
- Avoid absolute units of measure, such as defining a button in pixels. Due to varying screen densities, 50 pixels doesn’t translate to the same physical size on every device. So a
50px
button is going to appear larger on low-density screens and smaller on high-density screens. You should always specify your layout’s dimensions in density-independent units (dpi) and use flexible elements such aswrap_content
andmatch_parent
. - Provide alternate resources that are optimised for different screen densities. You can also provide layouts that are optimised for different screen sizes, using configuration qualifiers such as
smallestWidth
(sw
). Don’t forget to provide a default version of every resource, so your app has something to fall back on if it ever encounters a screen with characteristics that it doesn’t have a specific resource for. You should design these default resources for normal, medium-density screens.dp - Test your widget across as many screens as possible, using the Android emulator. When creating an AVD, you can specify an exact screen size and resolution, using the Screen controls that appear in the Configure this hardware profile menu. Don’t forget to test how your widget handles being resized across all these different screens!
Don’t Wake the Device
Updating a widget requires battery power, and the Android operating system has no qualms about waking a sleeping device in order to perform an update, which amplifies the impact your widget has on the device’s battery.
If the user realises that your widget is draining their battery, then at the very least they’re going to delete that widget from their homescreen. In the worst-case scenario, they may even delete your app entirely.
You should always aim to update your widget as infrequently as possible while still displaying timely and relevant information. However, some widgets may have a legitimate reason for requiring frequent updates—for example, if the widget includes highly time-sensitive content.
In this scenario, you can reduce the impact these frequent updates have on battery life, by performing updates based on an alarm that won’t wake a sleeping device. If this alarm goes off while the device is asleep, then the update won’t be delivered until the next time the device wakes up.
To update based on an alarm, you need to use the AlarmManager
to set an alarm with an Intent
that your AppWidgetProvider
will receive, and then set the alarm type to either ELAPSED_REALTIME
or RTC
, as these alarm types won’t wake a sleeping device. For example:
@Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { final AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); final Intent i = new Intent(context, WidgetService.class); if (service == null) { service = PendingIntent.getService(context, 0, i, PendingIntent.FLAG_CANCEL_CURRENT); } manager.setRepeating(AlarmManager.ELAPSED_REALTIME, //Set your update interval. 60000 milliseconds (60 seconds) is the minimum interval you can use// SystemClock.elapsedRealtime(), 60000, service); } }
If you do use an alarm, then make sure you open your project’s AppWidgetProviderInfo file (res/xml/new_app_widget_info.xml) and set the updatePeriodMillis
to zero (“0
“). If you forget this step, the updatePeriodMillis
value will override the AlarmManager
and your widget will still wake the device every single time it requires an update.
Conclusion
In this three-part series, we’ve created an Android application widget that demonstrates how to implement all the most common features found in app widgets.
If you’ve been following along since the beginning, then by this point you’ll have built a widget that updates automatically and in response to user input, and that’s capable of reacting to onClick
events. Finally, we looked at some best practices for ensuring your widget provides a good user experience, and discussed how to enhance your widget with a configuration Activity.
Thanks for reading, and while you’re here, check out some of our other great posts on Android app development!
-
Android SDKCreate a Material Design Tabbed Interface in an Android App
-
Android SDKHow to Code a Navigation Drawer for an Android App
-
Android ThingsConnect Android Things to a Smartphone With Nearby Connections 2.0
Powered by WPeMatico