0

tl;dr question:

How to properly refresh widget e.g. every minute making network request in background, reliably working Android 5-13 and according to policy

I have an app widget which needs to be refreshed more often than forced updatePeriodMillis and also should provide self-refresh button. Self interval refreshing is made with AlarmManager and works pretty good and under button click I have same PendingIntent:

    Intent intent = new Intent(REPORT_WIDGET_RUN_UPDATE);
    intent.setClass(context, ReportAppWidgetProvider.class);
    return PendingIntent.getBroadcast(context, 0, intent, 
        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);

On providers side onReceive gets called in both cases and I should do some network now. I'm using Volley, so its trivial, but with context passed to onReceive I'm always getting timeouts...

Few year ago I've started to use Service for this purpose, but starting Android 8 ForegroundService must be used for such case (background work, not running any Activity). I didn't wanted to show "auto-disappearing" notification to often, so I've switched to JobIntentService. It has some nasty rare bug and it's deprecated now, so even if it works pretty good I've decided to move on to WorkManager as suggesting last post under link, also official doc or some articles (e.g. this).

So now my JobIntentService becomes Worker followed by this article, Context in workers contructor works with Volley, network connection is done, feedback-broadcast is sent to provider and... After whole operation I'm getting VERY unwanted system-called android.appwidget.action.APPWIDGET_UPDATE. It's not mine action and it breaks my flow!

In Android 13 I see some suggestion in system logs why I'm getting this call

I/LauncherAppWidgetHostView: App widget created with id: 33
I/LauncherAppWidgetHostView: App widget with id: 33 loaded

So my widget after such refresh operation gets "recreated"... Behavior is exacly same on Android 10, but without above/any logs (different launcher)

My question is: how to properly refresh widget with network request in background, reliably working Android 5-13 and according to policy?

Maybe should I stick with sometimes-appearing ForegroundService as starting 13 it will be hidden anyway? As far as I remember there is some few-sec-limit for NOT-showing notification? It would be sufficient for me to make network call and then broadcast response to appwidget provider (call made with 3-4 sec. timeout thrown). Any suggestion, preferably working example appreciated

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
snachmsm
  • 17,866
  • 3
  • 32
  • 74

2 Answers2

1

My guess is that you're running into the known WorkManager issue where it "restarts" your widget every time. I originally couldn't figure out what was causing this, but there are several issues reported on Google's Issue Tracker, including this one that I filed. And the workaround top fix this issue is

to create a one-time Worker set to 10 years out, (use setInitialDelay) and set at least one constraint on it

You can see the code in my answer here

user496854
  • 6,461
  • 10
  • 47
  • 84
  • quick late night check: holy s**t, it works. let me back to you after a weekend and few tests, but seems to: thanks! – snachmsm Oct 01 '22 at 21:02
  • well, I've got back after hour, this works! posted my fix as separated answer, all credits goes to you – snachmsm Oct 01 '22 at 21:49
0

fixed with below code, BIG THANKS to user496854 answer and source

public class ReportAppWidgetProvider extends AppWidgetProvider {

    ...

    @Override
    public void onReceive(Context context, final Intent intent) {
        super.onReceive(context, intent);
        Log.i(ReportAppWidgetProvider.this.getClass().getSimpleName(),
                    "onReceive action " + intent.getAction());
        setUpDummyWorkerForPreventingAppWidgetRestarts(context);
        ...

    }

    // https://issuetracker.google.com/issues/241076154
    private void setUpDummyWorkerForPreventingAppWidgetRestarts(Context context) {
        OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(AppWidgetDelayedWidgetWorker.class)
                .setInitialDelay(10 * 365, TimeUnit.DAYS)
                .setConstraints(
                        new Constraints.Builder()
                                .setRequiresCharging(true)
                                .build()
                )
                .build();
        WorkManager.getInstance(context).enqueueUniqueWork(
                AppWidgetDelayedWidgetWorker.TAG,
                ExistingWorkPolicy.REPLACE,
                work);
    }

I've used ExistingWorkPolicy.REPLACE for keeping only one "infinite instance", as my method is called with every broadcast just-to-be-shure (in fact there are two workers and multiple actions for handling by these)

fun fact: above part is in Java, below is in Kotlin. mark of times :)

class AppWidgetDelayedWidgetWorker(
        appContext: Context,
        workerParams: WorkerParameters,
) : CoroutineWorker(appContext, workerParams) {

    companion object {
        const val TAG = "DummyWorker"
    }

    override suspend fun doWork(): Result {
        Log.i("DummyWorker", "doWork")
        return Result.success()
    }
}
snachmsm
  • 17,866
  • 3
  • 32
  • 74