remote views

API ref


setRemoteAdapter(widgetid, viewid, serviceintent)
AbsListView:setRemoteViewsAdapter(...)

My previous notes on widgets


RemoveViewsService
RemoteViewsFactory (thin wrapper on adapter)

Acts like an adapter that supplies views for a given data set. An implemented adapter usually holds a data set and then supply the necessary views to a list.

in case of remoteviews factory this seem to return RemoteViews instead of plain views.

RemoveViewsService

This takes an intent and returns a remoteviewsfactory for that intent.

In short it seem to return, then, the data for a list to display and also hwo to display (because of the adapter emulated by the remoteviews factory)

you are expected to inherit from this service and return an implementation ofthe remoteviewsfactory.

This seem a bit like long running services.


//Setting up a component to call
Intent intent = new Intent(context, WeatherWidgetService.class);

//load up the widget id
//service can retrieve it
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);

//why this? intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
//Load up remote views //what other constructors are there? RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout); //setup the service to provide the remote view rv.setRemoteAdapter(appWidgetIds[i], R.id.weather_list, intent);

Convert this Intent into a String holding a URI representation of it. The returned URI string has been properly URI encoded, so it can be used with Uri.parse(String). The URI contains the Intent's data as the base URI, with an additional fragment describing the action, categories, type, flags, package, component, and extras.

returns a string

when do I use intent toUri() method

Search for: when do I use intent toUri() method

intent touri

Search Google for: intent touri

Search Android Developers Group for: intent touri

Search Android Beginers Group for: intent touri

Search Google Code for: intent touri

Search Android Issues Database for: intent touri

RemoteViews setOnclickPendingIntent

Search for: RemoteViews setOnclickPendingIntent

setPendingIntentTemplate

Search for: setPendingIntentTemplate

Read this discussion why setData(intent.toUri) is used

Ok I get it!!!!

this is to do with (I think) how two *innocent* intents could be deemed the same even if they are different owing to their *extras".

The uniqueness of intents when dealing with pending intents does not take into account the "EXTRAS" but it does take into account the "data" portion.

And hence the following code simulating this uniqueness by converting an intent with extras first to a string (which will include the extras as appended string) and then set that unique string as data URI. :) I get it.

The following link documents explicitly what are supported by RemoteView

This is also the main documentation link for app widgets from google.

netmite RemoteViews.java

Search for: netmite RemoteViews.java

what remoteviews are allowed?

Looks like all views with annotation of RemoteViews.RemoteView.

An interesting way of finding this out

Any view that has an annotation of RemoteViews.RemoteView is a remoteview


put an import on RemoteView interface
highlight RemoteView
Right click
Go to References
Choose "in Project"

You will see a list of views that are capable of remote views


absolutelayout
framelayout
linearlayout
relativelayout

analogclock
button
chronometer
imagebutton
progressbar

Gridview
stackview
textview
datetimeview
iamgeview

AdapterViewFlipper
viewflipper

absolutelayout
framelayout
linearlayout
relativelayout

analogclock
button
chronometer
imagebutton
progressbar
viewflipper

datetimeview
iamgeview
textview

RemoteViewsFactory

Search Google for: RemoteViewsFactory

Search Android Developers Group for: RemoteViewsFactory

Search Android Beginers Group for: RemoteViewsFactory

Search Google Code for: RemoteViewsFactory

Search Android Issues Database for: RemoteViewsFactory

onCreate and RemoteViewsFactory

Search for: onCreate and RemoteViewsFactory

There are a number of callbacks on this factory. The main ones correspond to a collection view adapter. However there are "onCreate" and "onDestroy".

One is bound to wonder when do they get called?

These factories are explicitly constructed.

The variable of construction is the intent.

If this were to serve multiple clients it can only distinguish this from the incoming intent that was used for its construction. No other method of this class takes the intent or view or widget context.

Also because the "intent" is passed in as a constructor it is too late if this is to decide to serve certain clients through oncreate.

Does this mean this factory is somewhere cached based on the "intent" uniqueness???

you can construct a remoteview only from a layout by loading stuff


public RemoteViews getViewAt(int position) 
{        
    RemoteViews rv = 
        new RemoteViews(this.mContext.getPackageName(),
            R.layout.item_layout);
    return rv;
}

<?xml version="1.0" encoding="utf-8"?>
<TextView  xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/widget_list_item_id"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="Temporary"
/>

//basic setup
load the layout with the list view in it
identify the list view with an id
set a remote adapter factory for the list view id

//setup an onclick callback with an action
Configure an intent to self invoke
create a pending intent out of this intent
  pending intent needs to be be uniquie for this widget id
call setOnClickPendingIntentTemplate
  set this pending intent for any list item click

//provide a custom onreceive
override onreceive to get this special pendign intent
define a special action for this pending intent
for all other actions call the base onreceive

provide a list item view 
  this view goes inside the list
create an intent with extras pointing to this row
pass the intent through setOnClickFillIntent
  this row information will then be attached
  to the previous pending intent template

//Define an action string in your provider
public static final String ACTION_LIST_CLICK = 
    "com.androidbook.homewidgets.listclick";

//Override your onReceive to specialize it    
@Override
public void onReceive(Context context, Intent intent) 
{
    if (intent.getAction().equals(BDayWidgetProvider.ACTION_LIST_CLICK))
    {
        //this action is not one widget actions
        //this is a specific action that is directed here
        dealwithListAction(context,intent);
        return;
    }
    
    //make sure you call this 
    super.onReceive(context, intent);
}
public void dealwithListAction(Context context, Intent  intent)
{
    Toast t = Toast.makeText(context,"Clicked",Toast.LENGTH_SHORT);
    t.show();
}

class TestRemoteViewsFactory 
implements RemoteViewsService.RemoteViewsFactory 
{    
    private Context mContext;    
    private int mAppWidgetId;
    private static String tag="TRVF";
    public TestRemoteViewsFactory(Context context, Intent intent) 
    {        
        mContext = context;        
        mAppWidgetId = 
            intent.getIntExtra(
                AppWidgetManager.EXTRA_APPWIDGET_ID,                
                AppWidgetManager.INVALID_APPWIDGET_ID);
        
        Log.d(tag,"factory created");
    }

    //Called when your factory is first constructed. 
    //The same factory may be shared across multiple 
    //RemoteViewAdapters depending on the intent passed.    
    public void onCreate() 
    {        
        Log.d(tag,"onCreate called for widget id:" + mAppWidgetId);
    }
    
    //Called when the last RemoteViewsAdapter that is 
    //associated with this factory is unbound. 
    public void onDestroy() 
    {        
        Log.d(tag,"destroy called for widget id:" + mAppWidgetId);
    }    
       
    //The total number of items
    //in this list
    public int getCount() 
    { 
       return 20; 
    }
       
   public RemoteViews getViewAt(int position) 
   {        
        Log.d(tag,"getview called:" + position);
        RemoteViews rv = 
            new RemoteViews(
                this.mContext.getPackageName(),
                R.layout.list_item_layout);
        this.loadItemOnClickExtras(rv, position);
        return rv;
   }    
   private void loadItemOnClickExtras(RemoteViews rv, int position)
   {
       Intent ei = new Intent();
       ei.putExtra(TestListWidgetProvider.ACTION_LIST_ITEM_TEXT,
               "Position of the item Clicked:" + position);
       rv.setOnClickFillInIntent(R.id.widget_list_item_id, ei);
       return;
   }

   //This allows for the use of a custom loading view 
   //which appears between the time that getViewAt(int) 
   //is called and returns. If null is returned, 
   //a default loading view will be used.
   public RemoteViews getLoadingView() 
   {        
       return null;    
   }
   
   //Not sure how this matters.
   //How many different types of views
   //are there in this list.
   public int getViewTypeCount() 
   {        
       return 1;    
   }
   
   //The internal id of the item
   //at this position
   public long getItemId(int position) 
   {        
       return position;    
   }
   
   //True if the same id 
   //always refers to the same object. 
   public boolean hasStableIds() 
   {        
       return true;    
   }
   
   //Called when notifyDataSetChanged() is triggered 
   //on the remote adapter. This allows a RemoteViewsFactory 
   //to respond to data changes by updating 
   //any internal references. 
   //Note: expensive tasks can be safely performed 
   //synchronously within this method. 
   //In the interim, the old data will be displayed 
   //within the widget.
   public void onDataSetChanged() 
   {        
        Log.d(tag,"onDataSetChanged");
   }
}

RemoteViewFactory api


public class TestRemoteViewsService 
extends android.widget.RemoteViewsService 
{    
   @Override    
   public RemoteViewsFactory onGetViewFactory(Intent intent) 
   {
      return new TestRemoteViewsFactory(
            this.getApplicationContext(), intent);
   }
}

well, earlier in this page, you have learned that a broadcast receiver is responsible for returning a remoteviews object to the app widget manager. This remoteviews object then gets painted in the space.

In 3.0 this remoteviews object can contain a child list view id that is tied to a service name. This is done by setting a pending intent on that list view id. This information is probably kept in a registry somewhere.

Now when it is time to display that remvoteviews object, someone will invoke the service using the pending intent and gets back a remote views factory, which is just like a list view adapter except that it returns remove views.

It is possible that this factory may be reused multiple times, and hence giving raise to the onCrate() method in that factory which is suggested to be used for onetime initialization.

Now that souce code for ICS is available I can see some light

Yes it is. It is cached based on the uniqueness of intents. So everytime an update of the widget gets called, the factory is not recreated. The old one is used as long as the update is for the same widget id and no other parameters of the intent has changed.

You can see this in the source code of RemoteViewsService class in the android source code.

Well this is called when the last widget id goes away for your widget as you delete the widgets. The ondestroy() is triggered.

this can be seen in the source code of AppWidgetService.java

It is not the last widget id. everytime a widget is removed from the home screen, the unique widet id for that instance is gone. At that point any remote service intent that is tied to that widget are decremented or dereferenced. If the ref count goes to zero, meaning no widget id is using that intent then the corresponding factory on destroy is called.

Notice that you can provide the same RemoteViewService for creating multiple types of RemoteViewFactory objects depending on the inent that is received.

If you just dragged on a widget and dragged off a widget I believe you should see the onDestroy() called.

It may be counterproductive to pass widget id to the intent that kicks off the remote view service

Because when you do that, especially when you make the intent unique by forcing the extra, you end up with too many factories: one for each widget id

Perhaps you dont need that.

1. Consider the widget id that is being removed because we dragged the widget to the trash can

2. Walk through all the intents (an intent may be serving multiple widget ids)

3. For each intent, remove the widget id from its list

4.If the intent has no widget ids in the process then call destroy on that factory

1. Do not overload your service intent with widget id. if you do, you will defeat the purpose of factory caching.

2. You can do the initialization for your factory either in the constructor or in the onCreate() method. These are one to one.

3. The constructor and the onCreate() gets called when you drag your first widget on to the home page. If you drag it again for a second instance of that widget, if you do it right, your oncreate() or the constructor should not be called again.

4. As you start removing the widgets, the last widget id removal should trigger onDestroy().

5. I have tested this in ICS and it works. The ondestroy() never got called when I have tested under 3.0 earlier this year.

6. You can work with your service intents in such a way that a single service can instantiate all your factories for your widgets in that package. You will load up the widgets either with extras or actions to diffrentiate them enough for each remote view. when you do this make sure you dont totally differentiate them all the way to the widget instances ids. So there is a balance.