One of the most complicated/buggy part from my Mario Coin Block widget is because I have to handle the input (click) event from users, and update only the respective widget.

Normal way to do this is through the manifest file that the widgetprovider class listen to that particular button action. The problem for this is you cannot specify different copies of your widgets to use different intent. It turns out whenever the PendingIntent fires to widgetprovider, it has no way to know which copy of the widget is firing this intent. Because I need only the clicked copy to animate, this is not possible.

My implementation is letting each copy of the widget have a View class associated to it, in that view class, it is doing something like:

public class CoinBlockView extends BroadcastReceiver {
	public CoinBlockView(Context context, int widgetId)
	{
                // ..skipped
		RemoteViews rviews = new RemoteViews(context.getPackageName(), R.layout.coin_block_widget);
		Intent intent = new Intent(String.format(INTENT_ON_CLICK_FORMAT, widgetId));
                // Prepare the intent, and associate it to the widget (click)
		PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
				PendingIntent.FLAG_UPDATE_CURRENT);

                // And register itself to listen to that
		IntentFilter filter = new IntentFilter(String.format(INTENT_ON_CLICK_FORMAT, widgetId));
		context.registerReceiver(this, filter);
		rviews.setOnClickPendingIntent(R.id.block, pi);
		AppWidgetManager.getInstance(context).updateAppWidget(id, rviews);
		onRedraw(context);
	}
}

I tried to let each instance of View (widget) to have different name of  intent, and itself register to it. Since the name is dynamically generated, I cannot register appwidgetprovider to handle, so the view class itself will handle it. I also created a static Hashtable in the Application to hold the <widgetId, View> pair.

OK, the lesson here is, AppWidget is supposed to be stateless, and I try to attach the state to it. It’s fine normally as Application object normally won’t be destroyed. Problem is, whenever the screen orientation is changed, everything in Android UI will be destroyed and recreate. Funny enough is that, the appWidgetprovider is not notified to update the respective UI.

Of course it won’t be any problem for a normal widget that requires no state, but I have to. The UI is destroyed, so all the Intent attached to the view is gone! That’s the reason why droid and x10 has issue that I never think of (coz my Desire does not change Home screen orientation). My final solution to this is the application itself register to android.intent.action.CONFIGURATION_CHANGED, and destroy everything view class and recreate it again, once the configuration is changed.

Tested on emulators, but still waiting for the updated user report on whether it is working.

Advertisements