Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AppCompat Breaks Launcher Widget ability. "couldn't find any view, using error view"

I'm a launcher developer and for the basis for widgets have always followed this simple guide I found: Hosting Android Widgets. Now this method works 100 % when using the demo app. The problem is as soon as I add an AppCompat theme and extend AppCompatActivity rather than Activity with the latest com.android.support:appcompat-v7:25.1.1, I run into problems. After selecting certain widgets from the ACTION_APPWIDGET_PICK dialog, I get the error as follows:

W/AppWidgetHostView: updateAppWidget couldn't find any view, using error view android.widget.RemoteViews$ActionException: view: android.support.v7.widget.AppCompatImageView can't use method with RemoteViews: setImageResource(int)
at android.widget.RemoteViews.getMethod(RemoteViews.java:775)
at android.widget.RemoteViews.access$300(RemoteViews.java:69)
at android.widget.RemoteViews$ReflectionAction.apply(RemoteViews.java:1266)
at android.widget.RemoteViews.performApply(RemoteViews.java:2587)
at android.widget.RemoteViews.apply(RemoteViews.java:2547)
at android.appwidget.AppWidgetHostView.updateAppWidget(AppWidgetHostView.java:395)
at android.appwidget.AppWidgetHost.createView(AppWidgetHost.java:336)
at com.lgfischer.widgethost.WidgetHostExampleActivity.createWidget(WidgetHostExampleActivity.java:129)
at com.lgfischer.widgethost.WidgetHostExampleActivity.onActivityResult(WidgetHostExampleActivity.java:93)
at android.app.Activity.dispatchActivityResult(Activity.java:6168)
at android.app.ActivityThread.deliverResults(ActivityThread.java:3732)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:3779)
at android.app.ActivityThread.access$1300(ActivityThread.java:162)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1461)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:189)
at android.app.ActivityThread.main(ActivityThread.java:5529)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:950)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:745)

In addition the AppWidgetHostView displays "Couldn't add widget". Based on the error log, one would naturally take a look at WidgetHostExampleActivity.java line #129, but that is simply calling mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);.

It seems that the code will work on appcompat-v7:22.1.1, but nothing above that.

Note:

  1. I have tested and confirmed this error on Android 5.0.2 and Android 7.1.1
  2. This only happens with some widgets. Some that have failed are the Default Calendar app on Android 7.1.1 and Play - My Library play store widget.
  3. In my actual launcher on the play store I have created a custom widget select activity which still has the problem described

I have the full source code for this project with the modifications I mentioned above here: Google Drive

Here is the complete WidgetHostExampleActivity:

public class WidgetHostExampleActivity extends AppCompatActivity{


    final static int APPWIDGET_HOST_ID = 111;
    final static int REQUEST_PICK_APPWIDGET = 222;
    final static int REQUEST_CREATE_APPWIDGET = 333;

    static final String TAG = "WidgetHostExampleActivity";

    AppWidgetManager mAppWidgetManager;
    AppWidgetHost mAppWidgetHost;

    ViewGroup mainlayout;

    /**
     * Called on the creation of the activity.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mainlayout = (ViewGroup) findViewById(R.id.main_layout);

        mAppWidgetManager = AppWidgetManager.getInstance(this);
        mAppWidgetHost = new AppWidgetHost(this, APPWIDGET_HOST_ID);
    }

    /**
     * Launches the menu to select the widget. The selected widget will be on
     * the result of the activity.
     */
    void selectWidget() {
        int appWidgetId = this.mAppWidgetHost.allocateAppWidgetId();
        Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
        pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        addEmptyData(pickIntent);
        startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET);
    }

    /**
     * This avoids a bug in the com.android.settings.AppWidgetPickActivity,
     * which is used to select widgets. This just adds empty extras to the
     * intent, avoiding the bug.
     * 
     * See more: http://code.google.com/p/android/issues/detail?id=4272
     */
    void addEmptyData(Intent pickIntent) {
        ArrayList<AppWidgetProviderInfo> customInfo = new ArrayList<AppWidgetProviderInfo>();
        pickIntent.putParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_INFO, customInfo);
        ArrayList<Bundle> customExtras = new ArrayList<Bundle>();
        pickIntent.putParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_EXTRAS, customExtras);
    }

    /**
     * If the user has selected an widget, the result will be in the 'data' when
     * this function is called.
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK) {
            if (requestCode == REQUEST_PICK_APPWIDGET) {
                configureWidget(data);
            } else if (requestCode == REQUEST_CREATE_APPWIDGET) {
                createWidget(data);
            }
        } else if (resultCode == RESULT_CANCELED && data != null) {
            int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
            if (appWidgetId != -1) {
                mAppWidgetHost.deleteAppWidgetId(appWidgetId);
            }
        }
    }

    /**
     * Checks if the widget needs any configuration. If it needs, launches the
     * configuration activity.
     */
    private void configureWidget(Intent data) {
        Bundle extras = data.getExtras();
        int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
        AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
        if (appWidgetInfo.configure != null) {
            Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
            intent.setComponent(appWidgetInfo.configure);
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
            startActivityForResult(intent, REQUEST_CREATE_APPWIDGET);
        } else {
            createWidget(data);
        }
    }

    /**
     * Creates the widget and adds to our view layout.
     */
    public void createWidget(Intent data) {
        Bundle extras = data.getExtras();
        int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
        AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);

        AppWidgetHostView hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
        hostView.setAppWidget(appWidgetId, appWidgetInfo);
        mainlayout.addView(hostView);

        Log.i(TAG, "The widget size is: " + appWidgetInfo.minWidth + "*" + appWidgetInfo.minHeight);
    }

    /**
     * Registers the AppWidgetHost to listen for updates to any widgets this app
     * has.
     */
    @Override
    protected void onStart() {
        super.onStart();
        mAppWidgetHost.startListening();
    }

    /**
     * Stop listen for updates for our widgets (saving battery).
     */
    @Override
    protected void onStop() {
        super.onStop();
        mAppWidgetHost.stopListening();
    }

    /**
     * Removes the widget displayed by this AppWidgetHostView.
     */
    public void removeWidget(AppWidgetHostView hostView) {
        mAppWidgetHost.deleteAppWidgetId(hostView.getAppWidgetId());
        mainlayout.removeView(hostView);
    }

    /**
     * Handles the menu.
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        Log.i(TAG, "Menu selected: " + item.getTitle() + " / " + item.getItemId() + " / " + R.id.addWidget);
        switch (item.getItemId()) {
        case R.id.addWidget:
            selectWidget();
            return true;
        case R.id.removeWidget:
            removeWidgetMenuSelected();
            return false;
        }
        return super.onOptionsItemSelected(item);
    }

    /**
     * Handle the 'Remove Widget' menu.
     */
    public void removeWidgetMenuSelected() {
        int childCount = mainlayout.getChildCount();
        if (childCount > 1) {
            View view = mainlayout.getChildAt(childCount - 1);
            if (view instanceof AppWidgetHostView) {
                removeWidget((AppWidgetHostView) view);
                Toast.makeText(this, R.string.widget_removed_popup, Toast.LENGTH_SHORT).show();
                return;
            }
        }
        Toast.makeText(this, R.string.no_widgets_popup, Toast.LENGTH_SHORT).show();
    }

    /**
     * Creates the menu with options to add and remove widgets.
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.widget_menu, menu);
        return true;
    }
}

Here is my settings.gradel:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"

    defaultConfig {
        applicationId "com.lgfischer.widgethost"
        minSdkVersion 11
        targetSdkVersion 22

    }

    buildTypes {
        release {
            minifyEnabled false
            shrinkResources false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}


dependencies {
    compile 'com.android.support:appcompat-v7:25.1.1'
}

PS: I hardly ever post questions now-a-days, so when I do, you know it's a real problem.

like image 972
Michael Kern Avatar asked Feb 11 '17 08:02

Michael Kern


1 Answers

change the context of the manager and host to be the application context instead of the activity context.

AppWidgetManager = AppWidgetManager.getInstance(getApplicationContext());
mAppWidgetHost = new AppWidgetHost(getApplicationContext(), APPWIDGET_HOST_ID);

Just fixed that issue for me.

like image 180
dasfima Avatar answered Sep 17 '22 23:09

dasfima