Creating an Android App Widget

Photo by Adrien Olichon on Unsplash
Photo by Adrien Olichon on Unsplash
Android App Widget is an extension of app. App can provide a piece of information or a simple function through app widgets without requiring users to launch the app.

Android App Widget is an extension of app. Users can place app widgets provided by the app on the home screen. App can provide a piece of information or a simple function through app widgets without requiring users to launch the app. For example, the Clock app provides an app widget that displays the current time. The user can see the time on the app widget on the home screen instead of opening the clock app to see the time. This article will introduce how to develop a simple app widget.

The complete code for this chapter can be found in .

Creating an App Widget

It is very easy to create an app widget in the project. As shown in the figure below, in the project, select New -> Widget -> App Widget.

New -> Widget -> App Widget
New -> Widget -> App Widget

Next, enter the class name for the app widget. In the Resizable field, we can choose the resizeable direction. In the Minimum Widget and Minimum Height fields, enter the minimum size for the app widget.

Creates a new App Widget.
Creates a new App Widget.

Then, Android Studio will help us generate all the required code. Of course, you can also manually add these codes one by one. We now have a working app widget. You can install this app in emulator, and display PersonInfoAppWidget on the home screen.

App Widget project structure.
App Widget project structure.

Next, we need to modify PersonInfoAppWidget into an app widget that can display personal information.

Modify person_info_app_widget.xml as follows. As can be seen from the layout, the PersonInfoAppWidget will display the person’s name and job.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    style="@style/Widget.AppWidgetExample.AppWidget.Container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:theme="@style/Theme.AppWidgetExample.AppWidgetContainer">

    <LinearLayout
        style="@style/Widget.AppWidgetExample.AppWidget.InnerView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/name_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textStyle="bold|italic" />

        <TextView
            android:id="@+id/job_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textAlignment="textEnd"
            android:textStyle="bold|italic" />

    </LinearLayout>
</RelativeLayout>

Modify PersonInfoAppWidget.kt as follows. Execute the app again, we can see that the PersonInfoAppWidget will display the person’s name and job.

package com.waynestalk.appwidget

import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews

val wayne = Person(
    name = "Wayne",
    job = "Software programmer",
    website = "https://waynestalk.com/",
    github = "https://github.com/xhhuango",
)

/**
 * Implementation of App Widget functionality.
 */
class PersonInfoAppWidget : AppWidgetProvider() {
    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray,
    ) {
        // There may be multiple widgets active, so update all of them
        for (appWidgetId in appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId)
        }
    }

    override fun onEnabled(context: Context) {
        // Enter relevant functionality for when the first widget is created
    }

    override fun onDisabled(context: Context) {
        // Enter relevant functionality for when the last widget is disabled
    }
}

internal fun updateAppWidget(
    context: Context,
    appWidgetManager: AppWidgetManager,
    appWidgetId: Int,
) {
    // Construct the RemoteViews object
    val view = RemoteViews(context.packageName, R.layout.person_info_app_widget)
    view.setTextViewText(R.id.name_text_view, wayne.name)
    view.setTextViewText(R.id.job_text_view, wayne.job)

    // Instruct the widget manager to update the widget
    appWidgetManager.updateAppWidget(appWidgetId, view)
}

AppWidgetProvider

PersonInfoAppWidget inherits AppWidgetProvider, and AppWidgetProvider inherits BroadcastReceiver. So when there is any change in app widget, AppWidgetProvider will receive broadcasts.

The following are commonly used AppWidgetProvider methods:

  • onUpdate: When the app widget is asked to provide RemoteViews. We must at least implement this method to provide the view of the app widget.
  • onDeleted: When one or more app widgets are deleted, such as from the home screen.
  • onEnabled: When an app widget is added.
  • onDisabled: When the last app widget is removed.
  • onAppWidgetOptionsChanged: When an app widget is resized, or its options are changed.

Each app widget can have multiple instances on the home screen. Hence, each instance has an appWidgetId. Therefore, most of the methods of AppWidgetProvider have appWidgetId parameters, so that AppWidgetProvider can know which instance is changed.

Because AppWidgetProvider is BroadcastReceiver, we must declare it in AndroidManifest.xml. If you use Android Studio to add app widget, then Android Studio will automatically declare it in AndriodManifest.xml for us.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppWidgetExample"
        tools:targetApi="31">
        <receiver
            android:name=".PersonInfoAppWidget"
            android:exported="false">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/person_info_app_widget_info" />
        </receiver>
    </application>
</manifest>

RemoteViews

AppWidget can only provide RemoteViews. The content of RemoteViews can be inflated from a layout. However, RemoteViews only supports certain layouts. All the layouts it supports are listed here. We can see that RemoteViews only supports android layouts, not androidx layouts.

When we want to change the views in the layout, we cannot directly access a view, but must use the method of RemoteViews. For example, in PersonInfoAppWidget, when we want to set the name to TextView, we cannot directly access the TextView, but must use RemoteViews.setTextViewText().

val view = RemoteViews(context.packageName, R.layout.person_info_app_widget)
view.setTextViewText(R.id.name_text_view, wayne.name)
view.setTextViewText(R.id.job_text_view, wayne.job)

AppWidgetProviderInfo XML

When declaring AppWidgetProvider in AndroidManifest.xml, we need to declare AppWidgetProviderInfo in <receiver/> with <meta-data/> .

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <application >
        <receiver
            android:name=".PersonInfoAppWidget"
            android:exported="false">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/person_info_app_widget_info" />
        </receiver>
    </application>
</manifest>

If you use Android Studio to add an app widget, it will automatically generate an AppWidgetProviderInfo for us. In this project, Android Studio generates person_info_widget_info.xml.

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/app_widget_description"
    android:initialKeyguardLayout="@layout/person_info_app_widget"
    android:initialLayout="@layout/person_info_app_widget"
    android:minWidth="110dp"
    android:minHeight="40dp"
    android:previewImage="@drawable/example_appwidget_preview"
    android:previewLayout="@layout/person_info_app_widget"
    android:resizeMode="horizontal|vertical"
    android:targetCellWidth="2"
    android:targetCellHeight="1"
    android:updatePeriodMillis="86400000"
    android:widgetCategory="home_screen" />

Below we briefly explain the various attributes of <appwidget-provider/>, please refer to the official.

AttributesDescription
targetCellWidth
targetCellHeight
minWidth
minHeight
Starting from Android 12, targetCellWidth and targetCellHeight specify the default size of the app widget. If the home screen does not support grid-based layout, minWidth and minHeight will be used instead.
Android 11 and lower, minWidth and minHeight specify the default size of the app widget. When minWidth and minHeight cannot correspond to the size of cells, their values ​​will be rounded up to the nearest cell size.
minResizeWidth
minResizeHeight
Specifies the absolute minimum size of the app widget.
maxResizeWidth
maxResizeHeight
Specifies the recommended maximum size for the app widget.
resizeModeSpecifies the orientation in which the app widget can be resized.
horizontalverticalnonehorizontal|vertical.
initialLayoutThe layout resource of the app widget.
descriptionDisplays the description of this app widget in the widget picker.
previewImage
previewLayout
Starting from Android 12, previewLayout specifies a scalable preview.
Android 11 and lower, previewImage specifies the preview image.
The Widget picker displays this preview.
updatePeriodMillisSpecifies how often the widget framework will call AppWidgetProvider.onUpdate() to update the app widget’s view.
widgetCategorySpecifies that this app widget can be displayed on the home screen ( home_screen) or lock screen ( keyguard).
The attributes of <appwidget-provider/>.

Providing Responsive Layout

When the app widget is resized, we can provide different layouts according to different sizes.

When the PersonInfoAppWidget is resized bigger, we will provide the following layout. In addition to displaying personal names and jobs, the following layout will also display Website and GitHub.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    style="@style/Widget.AppWidgetExample.AppWidget.Container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:theme="@style/Theme.AppWidgetExample.AppWidgetContainer">

    <LinearLayout
        style="@style/Widget.AppWidgetExample.AppWidget.InnerView"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/name_text_view"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textStyle="bold|italic" />

            <TextView
                android:id="@+id/job_text_view"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:textAlignment="textEnd"
                android:textStyle="bold|italic" />

        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Website:"
                android:textStyle="bold|italic" />

            <TextView
                android:id="@+id/website_text_view"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:textAlignment="textEnd"
                android:textStyle="bold|italic" />

        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="GitHub:"
                android:textStyle="bold|italic" />

            <TextView
                android:id="@+id/github_text_view"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:textAlignment="textEnd"
                android:textStyle="bold|italic" />

        </LinearLayout>

    </LinearLayout>

</RelativeLayout>

We can put the view and its corresponding size into a Map, and then set the map to AppWidgetManager.updateAppWidget(). When PersonInfoAppWidget is displayed, it will choose the nearest layout to display according to its size.

internal fun updateAppWidget(
    context: Context,
    appWidgetManager: AppWidgetManager,
    appWidgetId: Int,
) {
    // Construct the RemoteViews object
    val simpleView = RemoteViews(context.packageName, R.layout.person_info_app_widget)
    simpleView.setTextViewText(R.id.name_text_view, wayne.name)
    simpleView.setTextViewText(R.id.job_text_view, wayne.job)

    val detailView = RemoteViews(context.packageName, R.layout.person_info_detail_app_widget)
    detailView.setTextViewText(R.id.name_text_view, wayne.name)
    detailView.setTextViewText(R.id.job_text_view, wayne.job)
    detailView.setTextViewText(R.id.website_text_view, wayne.website)
    detailView.setTextViewText(R.id.github_text_view, wayne.github)

    val viewMapping = mapOf(
        SizeF(60f,  100f) to simpleView,
        SizeF(100f, 200f) to detailView,
    )

    val views = RemoteViews(viewMapping)

    // Instruct the widget manager to update the widget
    appWidgetManager.updateAppWidget(appWidgetId, views)
}

Conclusion

When creating an app widget in a project, many steps are required. However, if you use Android Studio to create an app widget, it is very easy. It will automatically generate all the necessary code for us. We can modify it based on these codes, which saves us a lot of time.

Reference

Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like