DraganTL
DraganTL

Reputation: 83

Android app widget instances send the same pending intent

I am seeing strange behavior while working with widgets. I am sure that it worked before but, in any case, it doesn't now. Not sure what I changed or if it really did work. The below code creates a simplified version of what I'm trying to do. An app offers one widget. When it is places on the screen, user is prompted for a number. That number is then displayed on the widget. Clicking the widget prints out the number to the log. Multiple instances of this widget can be created with different numbers. Clicking each one should produce a log entry corresponding to the number chosen for each widget.

However, I am seeing some sort of a cached behavior. Let's say I create widget 3 and 5. Clicking 3 will log 3. Clicking 5 will log 3. Deleting 3 and clicking 5 will, again, log 3. How does that work? It looks like the first widget is created and its view is cached?

Manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.developer.app">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:name=".AppApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity
            android:name=".activity.DashboardActivity"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity
            android:name=".activity.DummyWidgetConfigActivity"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
            </intent-filter>
        </activity>

        <receiver android:name=".DummyWidgetProvider">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/dummy_widget_info" />
        </receiver>

        <service
            android:name=".service.DummyWidgetService"
            android:exported="false" />

    </application>

</manifest>

DummyWidgetConfigActivity.java

package com.developer.app.activity;

import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.developer.app.DummyWidgetProvider;
import com.developer.app.R;

/**
 * TODO: Add a class header comment!
 */

public class DummyWidgetConfigActivity extends AppCompatActivity {

    private static final String TAG = "DummyWgtConfigActivity";

    private int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;

    private TextView numberInput = null;
    private Button submitButton = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setResult(RESULT_CANCELED);

        setContentView(R.layout.activity_dummy_config);

        Intent intent = getIntent();
        Bundle extras = intent.getExtras();
        if (extras != null) {
            appWidgetId = extras.getInt(
                    AppWidgetManager.EXTRA_APPWIDGET_ID,
                    AppWidgetManager.INVALID_APPWIDGET_ID);
        }

        if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
            finish();
        }

        numberInput = (TextView) findViewById(R.id.number_input);
        submitButton = (Button) findViewById(R.id.submit);

        submitButton.setOnClickListener(v -> {
            try {
                final int number = Integer.parseInt(numberInput.getText().toString());
                onNumberSelect(number);
            } catch (Exception e) {
                Toast.makeText(this, "Must be a number", Toast.LENGTH_SHORT).show();
            }
        });
    }

    public void onNumberSelect(final int number) {
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
        SharedPreferences.Editor editor = sp.edit();

        editor.putInt("widget_" + appWidgetId, number);
        editor.commit();

        Log.d(TAG, "Creating widget with id " + appWidgetId);

        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
        DummyWidgetProvider.updateAppWidget(this, appWidgetManager, appWidgetId);

        Intent resultValue = new Intent();
        resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        setResult(RESULT_OK, resultValue);
        finish();
    }
}

DummyWidgetProvider.java

package com.developer.app;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.RemoteViews;

import com.developer.app.service.DummyWidgetService;

/**
 * TODO: Add a class header comment!
 */

public class DummyWidgetProvider extends AppWidgetProvider {

    private static final String TAG = "DummyWidgetProvider";

    public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) {
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }

    public static void updateAppWidget(final Context context, final AppWidgetManager appWidgetManager, final int appWidgetId) {
        final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
        final int number = sp.getInt("widget_" + appWidgetId, -1);

        if (number == -1) {
            Log.e(TAG, "Could not find the number associated with widget " + appWidgetId);
            return;
        }

        Log.d(TAG, "Updating widget with id " + appWidgetId);

        Intent intent = DummyWidgetService.createShowNumberIntent(context, number, appWidgetId);
        PendingIntent pendingIntent = PendingIntent.getService(context, 1, intent, 0);

        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_dummy);

        views.setTextViewText(R.id.widget_number, Integer.toString(number));

        views.setOnClickPendingIntent(R.id.widget_number, pendingIntent);

        appWidgetManager.updateAppWidget(appWidgetId, views);
    }
}

DummyWidgetService.java

package com.developer.app.service;

import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;

/**
 * TODO: Add a class header comment!
 */

public class DummyWidgetService extends IntentService {

    private static final String TAG = "DummyWidgetService";
    private static final String DUMMY_WIDGET_SERVICE_WORKER_THREAD = "com.developer.app.THREAD.dummy_widget_service";
    private static final String EXTRA_NUMBER_ID = "com.developer.app.service.DummyWidgetService.extra.NumberId";
    private static final String EXTRA_WIDGET_ID = "com.developer.app.service.DummyWidgetService.extra.WidgetId";
    public final static String EXTRA_ACTION = "com.developer.app.service.DummyWidgetService.extra.ACTION";

    private final static int ACTION_UNKNOWN = -1;
    private final static int ACTION_SHOW_NUMBER = 1;

    public DummyWidgetService() {
        super(DUMMY_WIDGET_SERVICE_WORKER_THREAD);
    }

    public static Intent createShowNumberIntent(final Context context, final int number, final int widgetId) {
        Intent intent = new Intent(context, DummyWidgetService.class);

        intent.putExtra(EXTRA_ACTION, ACTION_SHOW_NUMBER);
        intent.putExtra(EXTRA_NUMBER_ID, number);
        intent.putExtra(EXTRA_WIDGET_ID, widgetId);

        return intent;
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        switch (intent.getIntExtra(EXTRA_ACTION, ACTION_UNKNOWN)) {
            case ACTION_SHOW_NUMBER:
                final int number = intent.getIntExtra(EXTRA_NUMBER_ID, -1);
                final int widgetId = intent.getIntExtra(EXTRA_WIDGET_ID, -1);

                //Toast.makeText(this, "Widget number: " + number, Toast.LENGTH_SHORT).show();
                Log.i(TAG, "Widget number: " + number);

                break;
            default:
                Log.w(TAG, "Unknown action requested!");
        }
    }
}

dummy_widget_info.xml

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:configure="com.developer.app.activity.DummyWidgetConfigActivity"
    android:updatePeriodMillis="86400000"
    android:initialLayout="@layout/widget_dummy"
    android:resizeMode="none"
    android:widgetCategory="home_screen">
</appwidget-provider>

activity_dummy_config.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/number_input"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="number"/>

    <Button
        android:id="@+id/submit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Submit"/>

</LinearLayout>

widget_dummy.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/widget_number"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textAlignment="center"/>

</LinearLayout>

Thank you for any suggestions! I do see that other apps are able to correctly handle multiple instances. As such, I assume that this is purely my mistake.

Upvotes: 3

Views: 1429

Answers (1)

Vadym
Vadym

Reputation: 994

The problem is in that the pending intent is created by the same process (probably Android OS app) with the same internal ID

PendingIntent pendingIntent = PendingIntent.getService(context, 1, intent, 0);

In the above code, 1 is used for every widget instance. As such, that just reuses the intent. That value can be changed to appWidgetId. Also, the flag can be changed from 0 to FLAG_CANCEL_CURRENT or FLAG_UPDATE_CURRENT.

Upvotes: 14

Related Questions