January 30, 2012

Hosting Android Widgets - My AppWidgetHost Tutorial

UPDATE: I've moved my whole blog to a new domain. That's why the comments section is closed here. The new URL for this post is http://www.leonardofischer.com/hosting-android-widgets-my-appwidgethost-tutorial/. If you have any question, post it there.

Hi,

No, this isn't another tutorial on how to create Android Widgets. For this, I recommend you the Android SDK or Google. This post is on how to create a simple app that lets the user add and remove widgets, like the Android Home Screen does.

I decided to write this one because I couldn't find anything on the web saying how to do this. I found how to create this example looking at the Android Home Screen Source Code (AHSSC). So, if you already did this, you may find some variable names similar. You can use this as trails to look yourself on the AHSSC  ツ


Initialization

You start by creating two objects. The first is an AppWidgetManager, which will give you the data you need about installed widgets. The second one is an AppWidgetHost, which will keep in memory your widget instances. Latter, your app will handle only the view that will draw the widget.

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


Selecting the Widget

You start by asking to the AppWidgetHost to allocate resources for a widget instance. It will return an ID for that. Then, you need to start an activity to let the user select which widget he wants to add to your app. You need to give this ID to 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, R.id.REQUEST_PICK_APPWIDGET);
}

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);
};

Unfortunately, any kind of software has bugs, and here is one of the Android SDK. The Widget API supports that you merge custom widgets of your application with the installed ones. But if you don't add anything, the Activity that shows the list of widgets to the user crashes with a NullPointerException. The addEmptyData() method above adds some dummy data to avoid this bug. More on this bug here. If you want to add a custom widget, start looking at this point of the AHSSC.


Configuring the Widget

If the user successfully selects a widget from the list (he didn't pressed "back"), it will return an OK to you as an activity result. The data for this result contains the widget ID. Use it to retrieve the AppWidgetProviderInfo to check if the widget requires any configuration (some widgets does need). If it requires, you need to launch the activity to configure the widget. If not, jump to the next step.

@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);
        }
    }
}

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);
    }
}


Creating and Adding it to Your Views

Now is time to create the widget itself. You will use the Widget ID and the AppWidgetProviderInfo to ask to the AppWidgetHost "could you please create a view of this widget for me?". It will return an AppWidgetHostView which is a derived class from View. This one you can handle as any other view from the Framework. But don't forget to set the Widget ID and Widget Info on the view (I don't know why the AppWidgetHost didn't when creating the view).

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);
    layout.addView(hostView);
}


Updating

The widget is now working, but is not being updated by your app. If the widget is a clock, it will be stuck at the time you added it. To register the widget to receive the events it needs, call startListening() on the AppWidgetHost. To avoid wasting battery with unnecessary updates while your app is not visible, call it during the onStart() method of your activity, and call stopListening() during the onStop() method.

@Override
protected void onStart() {
    super.onStart();
    mAppWidgetHost.startListening();
}

@Override
protected void onStop() {
    super.onStop();
    mAppWidgetHost.stopListening();
}


Releasing the Widget

The widget should be working now. But if you want to remove the widget, you need to ask to the AppWidgetHost to release it. If you do not release it, you'll get a memory leak (your app will consume unnecessary memory). Finally, remove it from your LayoutView.

public void removeWidget(AppWidgetHostView hostView) {
    mAppWidgetHost.deleteAppWidgetId(hostView.getAppWidgetId());
    layout.removeView(hostView);
}

Note that the widget ID is also deleted during the onActivityResult() method if the user gave up selecting the widget.


I hope this can help you develop widget based apps. You can download the full source code for this post here or on GitHub. There is also an APK to install on your phone (just make sure you can install it).

UPDATE: I've moved my whole blog to a new domain. That's why the comments section is closed here. The new URL for this post is http://www.leonardofischer.com/hosting-android-widgets-my-appwidgethost-tutorial/. If you have any question, post it there.

January 4, 2012

Blog update: recomendations


UPDATE: I've moved my whole blog to a new domain. That's why the comments section is closed here. The new URL for this post is http://www.leonardofischer.com/blog-update-recomendations/. If you have any question, post it there.

Happy New Year!


Ok, this is just a fast post. I've recently added a new Recomendations page. My intention with it is to share some of the feeds from blogs and news that I found to be valuable and have followed for some time. That page should be in constant update. Well, not so "constant", but from time to time I'll update it with new content.

Also, I've added a list of some blogs from friends. Although these blogs are also in my list of recommendations, I've put then on the right side of the blog (instead of the Recomendations page) to enforce how much I recommend then.


And, again, have a happy new year!

PS.: Thank you, @lfzawacki for reminding me to do this. I pushed this task for weeks...  ツ

UPDATE: I've moved my whole blog to a new domain. That's why the comments section is closed here. The new URL for this post is http://www.leonardofischer.com/blog-update-recomendations/. If you have any question, post it there.