7

I am using AlarmManager to update my widgets. And I want to stop it if there is no widget on homescreen. But I am facing a problem with detecting if there is no widget on home screen.

As whenever I try to get the AppWidgetIds using this way:

AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

int[] appWidgetIDs = appWidgetManager
    .getAppWidgetIds(new ComponentName(context, Widget.class));

I get the a length of appWidgetIDs while actually there is no widget on homescreen. Why?

Therefore, I would like to know if there is a way to detect that a widget id is exists on homescreen.

Thank you upfront.

1 Answers1

23

Congratulations, you've encountered phantom appwidgets. It appears to be documented on the Android issue tracker. They usually occur when the configuration activity for an appwidget is canceled, though it seems to be through improper implementation of the configuration activity; developers neglect to include the appwidget ID as an extra when setting the activity result to RESULT_CANCELED. (even Google's ApiDemos sample application neglects to do this!)

The proper implementation is like this:

public class AppWidgetConfigActivity extends Activity {

    private int appWidgetId;
    private Intent resultValue;

    protected void onCreate(bundle saved) {
        super.onCreate(saved);

        // get the appwidget id from the intent
        Intent intent = getIntent();
        appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);

        // make the result intent and set the result to canceled
        resultValue = new Intent();
        resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        setResult(RESULT_CANCELED, resultValue);

        // if we weren't started properly, finish here
        if (appwidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
            finish();
        }

        /* ... */
    }

    /* ... */

    private void finishConfigure() {
        /* finish configuring appwidget ... */
        setResult(RESULT_OK, resultValue);
    }
}

Thus far I know of no way to detect the presence of a phantom appwidget without doing your own bookkeeping. I suggest storing a SharedPreferences value indicating that the configuration activity was not canceled and then querying this value in your other code. You can also use this information to "delete" a phantom widget if you come across one. In your appwidget configuration activity:

private void finishConfigure() {
    /* finish configuring appwidget ... */
    setResult(RESULT_OK, resultValue);

    String key = String.format("appwidget%d_configured", appwidgetId);
    SharedPreferences prefs = getSharedPreferences("widget_prefs", 0);
    prefs.edit().putBoolean(key, true).commit;
}

Then you can check that you have at least one non-phantom appwidget like so:

AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
AppWidgetHost appWidgetHost = new AppWidgetHost(context, 1); // for removing phantoms
SharedPreferences prefs = getSharedPreferences("widget_prefs", 0);
boolean hasWidget = false;

int[] appWidgetIDs = appWidgetManager.getAppWidgetIds(new ComponentName(context, Widget.class));
for (int i = 0; i < appWidgetIDs.length; i++) {
    int id = appWidgetIDs[i];
    String key = String.format("appwidget%d_configured", id);
    if (prefs.getBoolean(key, false)) {
        hasWidget = true;
    } else {
        // delete the phantom appwidget
        appWidgetHost.deleteAppWidgetId(id);
    }
}

if (hasWidget) {
    // proceed
} else {
    // turn off alarms
}
Karakuri
  • 38,365
  • 12
  • 84
  • 104
  • This answer deserves a lot more kudos! I'm convinced that lots of people will be having this issue (even if they don't know it) because the sample "hello world" appwidget code that you get from Android Studio doesn't do it properly, and that's where a lot of people will start. With your advice to include the appwidget ID as an extra when setting the activity result to `RESULT_CANCELED`, I don't seem to be creating any phantom widgets anymore. Thanks! – drmrbrewer Feb 03 '15 at 08:17
  • As for the code to delete phantom widgets *should* they appear for whatever reason, where would you recommend putting it? Not in `onUpdate()` because that seems to be called when a config activity is first opened, which is before the widget has been configured -- don't want to kill the widget before it is born! – drmrbrewer Feb 03 '15 at 08:20
  • Very helpful and well written answer. Thanks. – dazed Jan 20 '16 at 11:32
  • where do you call finishConfigure? – yalematta Feb 14 '18 at 22:10