narb
narb

Reputation: 988

Widget issue: Exiting the main app resets the static variables in the widget

I have a widget with a button. In my app widget (TestWidget.java) I have a private static boolean variable (buttonClicked) initialized to false.

When I click on the button the boolean buttonClicked is set to true. I have an updatePeriodMillis set to the minimum 30min (1800000ms).

First onUpdate comes: buttonClicked value is true. As expected.

Then I stop my main app. The following onUpdates shows buttonClicked value as false.

launch --> 06-10 10:55:56.365 4186-4186/com.narb.testwidget I/TESTWID: update setButtonClicked false
1st onUpdate after button click --> 06-10 10:56:11.685 4186-4186/com.narb.testwidget I/TESTWID: setButtonClicked true
onUpdate after main app exit --> I/TESTWID: update setButtonClicked false

Why is that?

App widget - TestWidget

public class TestWidget extends AppWidgetProvider {
    private static RemoteViews views;
    private static boolean buttonClicked = false;

    static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
                                int appWidgetId) {
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // Get all ids
        ComponentName thisWidget = new ComponentName(context,
                TestWidget.class);
        int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);

        Log.i("TESTWID", "update setButtonClicked "+buttonClicked);

        views = new RemoteViews(context.getPackageName(), R.layout.test_widget);
        views.setOnClickPendingIntent(R.id.wid_btn_tst, setButton(context));

        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }

    public static void setButtonClicked(boolean b){
        buttonClicked = b;
        Log.i("TESTWID", "setButtonClicked "+buttonClicked);
    }

    public static PendingIntent setButton(Context context) {
        Intent intent = new Intent();
        intent.setAction("TEST");
        return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    }

    public static void pushWidgetUpdate(Context context, RemoteViews remoteViews) {
        ComponentName myWidget = new ComponentName(context, TestWidget.class);
        AppWidgetManager manager = AppWidgetManager.getInstance(context);
        manager.updateAppWidget(myWidget, remoteViews);
    }

}

Button code - TestWidgetReceiver

public class TestWidgetReceiver extends BroadcastReceiver{
    private static boolean isButtonON = false;

    @Override
    public void onReceive(Context context, Intent intent) {
        if(intent.getAction().equals("TEST")){
            updateWidgetButton(context, 2);
        }
    }

    private void updateWidgetButton(Context context, int index) {
        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.test_widget);
        if(index == 2) {
            if(isButtonON) {
                remoteViews.setTextViewText(R.id.wid_btn_tst, "Test Off");
                isButtonON = false;
            }
            else{
                remoteViews.setTextViewText(R.id.wid_btn_tst, "Test On");
                isButtonON = true;
                TestWidget.setButtonClicked(isButtonON);
            }
            //REMEMBER TO ALWAYS REFRESH YOUR BUTTON CLICK LISTENERS!!!
            remoteViews.setOnClickPendingIntent(R.id.wid_btn_tst, TestWidget.setButton(context));
        }

        TestWidget.pushWidgetUpdate(context.getApplicationContext(), remoteViews);
    }

}

Manifest

   <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="Test"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver android:name=".TestWidget">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/test_widget_info" />
        </receiver>

        <receiver
            android:name=".TestWidgetReceiver"
            android:label="widgetBroadcastReceiver" >
            <intent-filter>
                <action android:name="TEST" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/test_widget_info" />
        </receiver>

    </application>

Upvotes: 1

Views: 416

Answers (2)

Anatolii
Anatolii

Reputation: 14660

If you force stop the app or the system decides to stop it due to low memory, all classes are dropped from the memory and all static variables are lost. In this case, you should either persist your data to the DB or SharedPreferences or similar or rethink the approach itself. Is it really necessary to do it the way you're doing here?

Upvotes: 1

CommonsWare
CommonsWare

Reputation: 1006664

In my app widget (TestWidget.java) I have a private static boolean variable (buttonClicked) initialized to false

static variables are a cache, at best.

Then I stop my main app.

I do not know exactly what you mean by that. My guess is that you mean that you remove a task associated with your app from the overview screen.

The following onUpdates shows buttonClicked value as false. Why is that?

If by "stop my main app", you do something like I outlined, you will have terminated your process. At a later point, such as when you click your app widget's button, Android will start a fresh process for you, at which point your static field, at which point your static field will be its default value.

I would have thought that widgets have their own memory copy as they continue to run even if the main app has exited?

No. The views associated with your app widget will exist, in the home screen. That does not include your TestWidget code, which is not part of the home screen.

I'm considering dropping this approach to do an alarmManager with a low refresh time (10 secs).

First, that's not possible on Android 5.1 and above, as it substantially drains the battery. Second, it does not guarantee that your process will stay around.

static variables are a cache, at best. Any app, including those with an app widget, need to store important data somewhere else: SharedPreferences, SQLite database, some other sort of file, a server, etc.

Upvotes: 2

Related Questions