Android App Widgets Tutorial
Learn how to give your users fast access to the most important functions of your Android app, right from their home screen, using App Widgets. By Matei Suica.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Android App Widgets Tutorial
35 mins
- Getting started
 - App widget anatomy
 - User interface
 - Resizability and preview
 - Refreshing the widget
 - Widget customisation
 - Create your Widget
 - Customizing the User Interface
 - Thinking vertically
 - Adding buttons
 - Run your Widget
 - Performing actions
 - The RemoteViews class
 - Updating the Widget
 - Widget configuration
 - Managing updates requests
 - Update the widget manually
 - Communicating via Service
 - Making it personal
 - Creating a preferences screen
 - Know your limits
 - Linking preferences to the widget
 - Best practices
 - Where to go from here
 
Update the widget manually
If your app needs to update the data in the Widget more frequently, you already have the solution: you can simply periodically launch the same Intent the Android system does. In the case of the Coffee Log application this happens every time the user selects a coffee in the app.
Open MainActivity and add the following code at the end of refreshTodayLabel:
// Send a broadcast so that the Operating system updates the widget
// 1
val man = AppWidgetManager.getInstance(this)
// 2
val ids = man.getAppWidgetIds(
    ComponentName(this, CoffeeLoggerWidget::class.java))
// 3
val updateIntent = Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
// 4
updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
// 5
sendBroadcast(updateIntent)
Since this code has some new elements, let me walk you through it:
- Get the 
AppWidgetManagerinstance, which is responsible for all the installed Widgets. - Ask for the identifiers of all the instances of your widget (you could add more than one to your homescreen).
 - Create an 
Intentwith theandroid.appwidget.action.APPWIDGET_UPDATEaction asking for an update. - Add the 
idsof the widgets you are sending theIntentto as extras of theIntentfor theAppWidgetManager.EXTRA_APPWIDGET_IDSkey. - Finally, send the broadcast message.
 
Build and run tha app to check that everytime you add some coffee, the widget also updates.
Communicating via Service
Not all the updates needed for Widgets are a consequence of an action from the user. Typical cases are data from a server through periodic polling and push notification events. In cases like these, the request has to come from a different component, which you usually implement as an Android Service.
Choose File\New\Service\Service and change the name to CoffeeQuotesService.

When you click Finish, Android studio generates a Kotlin file for you for the Service.
In CoffeeQuotesService, replace the current implementation of onBind() with:
return null
Change the return type of onBind to be the nullable IBinder?.
Then add this function, which is the one the Android system invokes at every launch of the service Service:
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
  val appWidgetManager = AppWidgetManager.getInstance(this)
  val allWidgetIds = intent?.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS)
  //1
  if (allWidgetIds != null) {
    //2
    for (appWidgetId in allWidgetIds) {
      //3
      CoffeeLoggerWidget.updateAppWidget(this, appWidgetManager, appWidgetId)
    }
  }
  return super.onStartCommand(intent, flags, startId)
}
You’ve seen the first two lines before. The others do the following:
- Check that the array of 
allWidgetIdswas in theIntent. - Loop through the 
allWidgetIdslist. - Update each widget.
 
Now, you need to call this service instead of directly updating the widget. Open CoffeeLoggerWidget and replace the content of onUpdate() with the following in order to start the Service:
val intent = Intent(context.applicationContext, CoffeeQuotesService::class.java)
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds)
context.startService(intent)
This creates an Intent, puts the Widget ids in the intent, and starts the Service. 
In the companion object, add the following function:
private fun getRandomQuote(context: Context): String {
  //1
  val quotes = context.resources.getStringArray(R.array.coffee_texts)
  //2
  val rand = Math.random() * quotes.size
  //3
  return quotes[rand.toInt()].toString()
}
This function generates a random coffee quote:
- It takes a quote array from the strings file
 - It picks a random number
 - Finally, it returns the string at the random position
 
After you have the string, update the widget. In updateAppWidget() add this before the last call:
views.setTextViewText(R.id.coffee_quote, getRandomQuote(context))
That’s it. Every time the widget updates, you get a new quote!
Making it personal
People like to personalize the look and functionality of their Home screens, and Widgets are no exception. You have to take into account that a general purpose Widget won’t bring much value to a user. To make it personal you need to let the users set up preferences and configurations.
Earlier, when covering the configuration of a Widget, you learned that it can have a Configuration screen. This is an Activity that is automatically launched when the user adds a Widget on the home screen. Note that the preferences are set up per Widget because users can add more than one instance of a Widget. It’s better to think about saving this preferences with the id of the Widget.
In this project, the configuration screen could contain a coffee amount limit. If the user logs more coffee than the limit, the Widget will turn into a soft but alarming pink.
Creating a preferences screen
The preference screen for a Widget is an Activity. Choose New\Activity\Empty activity from the File menu and edit the fields to be
- Activity name: 
CoffeeLoggerWidgetConfigureActivity - Layout Name: 
activity_coffee_logger_widget_configure 
Make sure the Launcher Activity checkbox is unchecked and the Source Language is Kotlin.
When you click Finish, Android Studio will generate the code for the new Activity and a template for the layout file, along with adding the registration of the Activity in the AndroidManifest.xml file.
Now create the layout for the configuration screen. Open activity_coffee_logger_widget_configure.xml and add the following:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:orientation="vertical"
  android:padding="16dp">
  <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="8dp"
    android:labelFor="@+id/appwidget_text"
    android:text="@string/coffee_amount_limit" />
  <EditText
    android:id="@id/appwidget_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:inputType="number" />
  <Button
    android:id="@+id/add_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="8dp"
    android:text="@string/save_configuration" />
</LinearLayout>
The layout is nothing complicated: a TextView that represents a label to the EditText, and a Button for the user to save the preferences.
Know your limits
Open CoffeeLoggerWidgetConfigureActivity and add these fields above onCreate() (developers usually put fields at the beginning of the class):
private lateinit var appWidgetText: EditText
private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
private val coffeeLoggerPersistence = CoffeeLoggerPersistence(this)
You will need to use these fields later to save the limit value for each widget.
In onCreate(), add the following code at the end:
//1
appWidgetText = findViewById(R.id.appwidget_text)
//2
val extras = intent.extras
//3
appWidgetId = extras.getInt(
    AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
//4
setResult(Activity.RESULT_CANCELED)
Here’s what the code does:
- Find the 
EditTextin the layout. - Get the extras from the 
Intentthat launched theActivity. - Extract the 
appWidgetIdof the widget. - Make sure that if the user doesn’t press the “Save Configuration” button, the widget is not added.
 
Finally, you need to save the configuration when the user presses the “Save Configuration” button. Below onCreate(), declare the following OnClickListener implementation:
private var onClickListener: View.OnClickListener = View.OnClickListener {
  // 1
  val widgetText = appWidgetText.text.toString()
  // 2
  coffeeLoggerPersistence.saveLimitPref(widgetText.toInt(), appWidgetId)
  // 3
  val resultValue = Intent()
  resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
  // 4
  setResult(RESULT_OK, resultValue)
  // 5
  finish()
}
Here you:
- Get the text input – the coffee limit.
 - Save the limit to local storage (using the Widget id).
 - Create a new 
Intentto return to the caller of theActivityand add the id of the Widget you’re configuring. - Tell the operating system that the configuration is OK. Do this by passing an 
Intentthat contains the widget id. - Close the configuration screen
 
Attach this listener to the button by adding the following line below setContentView() in onCreate():
findViewById<View>(R.id.add_button).setOnClickListener(onClickListener)
This is a chained instruction that finds the Button object and sets its listener. 
