Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Updating widgets - Introduction
Thomas Künneth
Thomas Künneth

Posted on • Edited on

     

Updating widgets - Introduction

Widgets (appwidgets) have been available on Android practically from the beginning (theAppWidgetProvider class debuted in API level 3). They had theirgreat moments before 2013, but slowly became irrelevant in the following years. When Apple added home screen widgets to iOS 14 (widgets had been around before but were somewhat cumbersome to access), the general reaction was beyond excitement. The good thing about the hype in the neighboring ecosystem is, that Google re-discovered its love for widgets. Android 12 containsa bunch of widget improvements. And we even gota new library that allows us to define the appwidget user interface using a declarative approach. While this series will briefly tackle both topics, too, its main focus is about something seemingly mundane,updating widgets. To understand why this justifies a whole article series, we need to start by looking at how appwdigets are built. The series is based on a small project calledBattery Meter. You can find its source code onGitHub.

Architecture

Technically, appwidgets areBroadcastReceiver subclasses. They receive a bunch of actions, for exampleAppWidgetManager.ACTION_APPWIDGET_UPDATE andAppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED. To simplify the handling of widget-related actions, there's theAppWidgetProvider class that extendsBroadcastReceiver. Its implementaton ofonReceive() calls methods likeonUpdate() andonAppWidgetOptionsChanged() when corresponding actions are received. Therefore, in your apps you usually will want to extendAppWidgetProvider.

<receiverandroid:name=".XMLBatteryMeterWidgetReceiver"android:exported="false"><intent-filter><actionandroid:name="android.appwidget.action.APPWIDGET_UPDATE"/></intent-filter><meta-dataandroid:name="android.appwidget.provider"android:resource="@xml/widget_xmlbatterymeter_info"/></receiver>
Enter fullscreen modeExit fullscreen mode

Appwidgets are registered in the manifest file. They declare an intent filter forandroid.appwidget.action.APPWIDGET_UPDATE and provide meta data (with the nameandroid.appwidget.provider) that links to an xml file.

<appwidget-providerxmlns:android="http://schemas.android.com/apk/res/android"android:minWidth="110dp"android:minHeight="40dp"android:targetCellWidth="2"android:targetCellHeight="1"android:maxResizeWidth="110dp"android:maxResizeHeight="40dp"android:updatePeriodMillis="1800000"android:description="@string/xml_description"android:previewLayout="@layout/widget_batterymeter_initial"android:initialLayout="@layout/widget_batterymeter_initial"android:resizeMode="none"android:widgetCategory="home_screen"></appwidget-provider>
Enter fullscreen modeExit fullscreen mode

The file contains the size of the widget, a description, a preview, layouts for different purposes, and a category. Starting with Android 4.2, widgets could be placed on the lock screen. Unfortunately this feature was removed with Android 5.

We'll focus onandroid:updatePeriodMillis. It defines how often (in milliseconds) the widget wants to be updated.Update meansredraw the contents reflecting the current status. A weather widget may want to get the latest forecast, the battery meter needs to update its gauge depending on the current battery level. Thedocumentation states:

Documentation of AppWidgetProviderInfo#updatePeriodMillis

Now, you may be thinking

Wait a minute, does this really mean that widget update intervals are at least 30 minutes? 🤔

While this is certainly fine for a weather widget, a battery meter may significantly deviate from the actual battery level if the device was in heavy use. So, the answer basically can only beno.

Please recall the documentation said that updatesrequested with updatePeriodMillis will not be delivered more than once every 30 minutes. So, our widget configuration file can't ask for shorter intervals. There are other means, though. To understand them, we need to explore appwidget mechanics a little more.

All widget-related classes reside in theandroid.appwidget package.AppWidgetHost andAppWidgetHostView are used by apps that want to embed widgets in their UI, like the home screen. You can find more information about this inBuild a widget host.AppWidgetManager updates widget state and allows us to get information about installed AppWidget providers and other widget-related state. We'll turn to this class in a minute.AppWidgetProvider is, as I explained a little earlier, a convenience class to aid in implementing widgets. Finally,AppWidgetProviderInfo describes the meta data for an installedAppWidgetProvider. Usually you won't use this class but its alternative representation, the xml file that is referenced in the manifest file.

Let's recap:

  1. AppWidgetProvider andAppWidgetProviderInfo (or its alternate representation define and implement the appwidget
  2. onUpdate() of anAppWidgetProvider instance is called when a widget should update itself
  3. AppWidgetManager updates widget state and allows us to get information about installedAppWidgetProviders and other widget-related state

UsingAppWidgetManager

To appreciate whatAppWidgetManager does, let's look at a minimalonUpdate() implementation first.

overridefunonUpdate(context:Context,appWidgetManager:AppWidgetManager,appWidgetIds:IntArray){appWidgetIds.forEach{appWidgetId->valviews:RemoteViews=RemoteViews(context.packageName,R.layout.appwidget_provider_layout).apply{// update the views}appWidgetManager.updateAppWidget(appWidgetId,views)}}
Enter fullscreen modeExit fullscreen mode

onUpdate() receives a list of widget ids. Each element represents one manifestation (copy) of the widget. Please recall that a given appwidget can be placed on screen several times. While iterating over the widget ids, we create aRemoteViews instance. This object inflates a layout file and gives us access to itsViews.View properties are set by special functions likesetColor() andsetTextViewText(). Once we have crafted our user interface, we update the widget by callingappWidgetManager.updateAppWidget().

Before we move on, please note that, whatever you do inonUpdate() should be finished within 10 seconds. Widgets areBroadcastReceiver instances and Android imposes certain limitations ononReceive(). The documentationsays:

This method is called when the BroadcastReceiver is receiving an Intent broadcast. During this time you can use the other methods on BroadcastReceiver to view/modify the current result values. This method is always called within the main thread of its process, unless you explicitly asked for it to be scheduled on a different thread usingContext.registerReceiver(BroadcastReceiver, IntentFilter, String, android.os.Handler). When it runs on the main thread you should never perform long-running operations in it (there is a timeout of 10 seconds that the system allows before considering the receiver to be blocked and a candidate to be killed).

If you need more time, you can invokegoAsync() in youronUpdate() code (please recall thatonUpdate() is called from theonReceive() implementation ofAppWidgetProvider). Please read thedocumentation to learn more.

Triggering updates

Here's something seemingly obvious: to update a widget more often than every 30 minutes, we just need to make sure thatonUpdate() is called. But how do we do that? If we wanted to directly invoke the methodfrom the outside (for example from one of our apps' activities or services) we would need to pass anAppWidgetManager instance and the list of widget ids. But more importantly, we would need to get anAppWidgetProvider instance. Is this possible? Fortunately, it's not necessary, asAppWidgetManager provides everything we need. Well, almost everything. Take a look.

Using AppWidgetManager

We can obtain theAppWidgetManager instance usingAppWidgetManager.getInstance(). AndgetAppWidgetIds() returns, well, the list of appwidget ids. But all versions ofupdateAppWidget() require aRemoteViews instance. How do we get that? Please recall that we already do this, inside ouronUpdate() implementation. Allow me to remind you:

overridefunonUpdate(context:Context,appWidgetManager:AppWidgetManager,appWidgetIds:IntArray){appWidgetIds.forEach{appWidgetId->valviews:RemoteViews=RemoteViews(context.packageName,R.layout.appwidget_provider_layout).apply{// update the views}appWidgetManager.updateAppWidget(appWidgetId,views)}}
Enter fullscreen modeExit fullscreen mode

But everything that happens insideonUpdate() is an implementation detail that should not be known to the outside world. Consequently, we should not even try to make theRemoteViews instance accessible. Instead, I propose to move the complete code ofonUpdate() to a private top-level function with the same parameters and call this function fromonUpdate().

Here's howXMLBatteryMeterWidgetReceiver looks:

classXMLBatteryMeterWidgetReceiver:AppWidgetProvider(){overridefunonUpdate(context:Context,appWidgetManager:AppWidgetManager,appWidgetIds:IntArray){super.onUpdate(context,appWidgetManager,appWidgetIds)updateXMLBatteryMeterWidget(context=context,appWidgetManager=appWidgetManager,appWidgetIds=appWidgetIds)}}
Enter fullscreen modeExit fullscreen mode

And this is the new private top-level function:

privatefunupdateXMLBatteryMeterWidget(context:Context,appWidgetManager:AppWidgetManager,appWidgetIds:IntArray){appWidgetIds.forEach{appWidgetId->valviews=RemoteViews(context.packageName,R.layout.widget_batterymeter).apply{valpercent=max(context.getBatteryStatusPercent(),0.0F)valresIds=listOf(R.id.percent10,...,R.id.percent100)for(iin0..9){setViewVisibility(resIds[i],if(percent>=10+(i*10))View.VISIBLEelseView.INVISIBLE)}setTextViewText(R.id.percent,"${percent.toInt()} %")valpendingIntent=PendingIntent.getActivity(context,0,Intent(context,MainActivity::class.java),PendingIntent.FLAG_UPDATE_CURRENTorPendingIntent.FLAG_IMMUTABLE)setOnClickPendingIntent(R.id.root,pendingIntent)}appWidgetManager.updateAppWidget(appWidgetId,views)}}
Enter fullscreen modeExit fullscreen mode

Please recall thatView properties cannot changed directly by invoking corresponding setters. Instead you need to use means provided byRemoteViews, for examplesetViewVisibility() andsetTextViewText().Battery Meter divides the battery into ten segments and hides or shows segments based on the current battery level.

To update the widgetfrom the outside, there is certainly still one bit missing.

funContext.updateXMLBatteryMeterWidget(){valcomponent=ComponentName(this,XMLBatteryMeterWidgetReceiver::class.java)with(AppWidgetManager.getInstance(this)){valappWidgetIds=getAppWidgetIds(component)updateXMLBatteryMeterWidget(context=this@updateXMLBatteryMeterWidget,appWidgetManager=this,appWidgetIds=appWidgetIds)}}
Enter fullscreen modeExit fullscreen mode

The outside world doesn't need to know anything about what's happening insideonUpdate(). In an activity, I can use it like this:

overridefunonPause(){super.onPause()updateXMLBatteryMeterWidget()}
Enter fullscreen modeExit fullscreen mode

Looks really handy, right? Every time the activity is paused, the widget gets updated. Well, yes, but...

What's coming next

I somewhat vaguely said that we can update our widgets from activities and services. But what if our widget is not a companion, but basically all the app contains? AWeather widget doesn't necessarily need a main activity. Neither does aBattery Meter. Which app component would trigger widget updates in such scenarios?

Also, what happens to the widget if the user doesn't interact with it but onlylooks at it? Recent Android versions severely limit what apps can do if they have not been in the foreground for some time. Will the widget still be updated?

Let's find out...


Source code

Top comments(2)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
nimbalabs profile image
Nimba Labs
  • Joined

Scheduled periodic work via WorkManager can request the same widget updates.

There is another gotcha, a widget app (without any foreground activity) would be considered a background and in background state, they can't have access to mobile data while data saver is on and all network calls would fail. Widgets using network data source would need some kind of activity to warn users to enable "Allow background data usage" and "Allow data usage while Data saver is on".

CollapseExpand
 
tkuenneth profile image
Thomas Künneth
Developer. Speaker. Listener. Loves writing. GDE Android. Confessing mobile computing addict ;-)
  • Location
    Germany
  • Work
    Senior Android Developer @snappmobile.io
  • Joined

Thanks a lot for reading this article and sharing your thoughts. The topics you mentioned will be tackled in subsequent installments.

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Developer. Speaker. Listener. Loves writing. GDE Android. Confessing mobile computing addict ;-)
  • Location
    Germany
  • Work
    Senior Android Developer @snappmobile.io
  • Joined

More fromThomas Künneth

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp