Android app widget with ListView


Finally I got a taste of App Widget while doing a recent project.So, I am going to publish a series of tutorial on App Widget which may include

  1. app widget with listview
  2. populate app widget listview with data from web
  3. download images and show on imageview of appwidget with listview
  4. setting update interval on appwidget with listview
  5. how to make appwidget update work after phone reboot

Let’s first dive into basics of App Widget.
1: AppWidgetProvider
This is governing body of App Widget. Meaning everything of App Widget is controlled from here. By control means

  • Widget Update
  • Widget Delete
  • Widget enabled/disabled

to name a few.
2: Xml file for AppWidgetProvider:
Let’s make it an analogy like Activity has Layout.xml file,App Widget has appwidget-provider xml file @res/xml So every AppWidget must have these two building blocks. Let’s look upon Xml file for AppWidgetProvider and name it widgetinfo.xml

<?xml version="1.0" encoding="utf-8"?>
<!--Activity to be launched,when you first install widget(drag n drop widget to homescreen) we will ignore this now -->
android:configure="com.wordpress.laaptu.ConfigActivity"

<!-- the layout of the widget,just like setContentView(id) of Activity-->
android:initialLayout="@layout/widget_layout"

<!-- Minimum width and height of the widget -->
android:minHeight="110dip"
android:minWidth="250dip"

<!-- Image to be seen when you go to select a widget -->
android:previewImage="@drawable/widget_preview"

<!-- How to resize the widget -->
android:resizeMode="vertical|horizontal"

<!-- Update interval i.e. @ this interval WidgetProvider onUpdate method will be called -->
<!-- Default is 30 min,you can set lower value,but it will take 30 min ,but it takes higher value than 30 min-->
<!-- 30 min =30x60x1000 -->
android:updatePeriodMillis="1800000"/>





For widget to be recognized,you must set it on AndroidManifest.xml just like you set Activity,in following manner

 <receiver android:name=".WidgetProvider" >
            <intent-filter>

            <!-- This widget provider receives broadcast with following action 
name or simply onUpdate of AppWidgetProvider is called -->
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <!-- linking up xml file of appwidget-provider to AppWidgetProvider -->
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widgetinfo" />
        </receiver>

Now let us create a layout.xml file for our appwidget and let’s name it widget_layout.xml @ res/layout which contains ListView

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

    <!-- ListView to be shown on widget -->
    <ListView
        android:id="@+id/listViewWidget"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <!-- Empty view is show if list items are empty -->
    <TextView
        android:id="@+id/empty_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="@string/empty_string"
        android:textColor="#ffffff"
        android:textSize="20sp"
        android:visibility="gone" />

</LinearLayout>

Now,in order for ListView to be populated,we need few more classes

  1. RemoteViewsService
  2. RemoteViewsFactory

1:RemoteViewsService:
Just consider this class as the class which tells the ListView of appwidget to take what type of data. By data meaning what RemoteViewsFactory.To make it more simple,if you have done ListView population,this class defines the Adapter for the ListView.Let us name RemoteViewsService as WidgetService.java

public class WidgetService extends RemoteViewsService {
/*
* So pretty simple just defining the Adapter of the listview
* here Adapter is ListProvider
* */

@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
int appWidgetId = intent.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);

return (new ListProvider(this.getApplicationContext(), intent));
}

}



2:RemoteViewsFactory:
If you have done ListView population,this class is the Adpater of ListView. Let us name our RemoteViewsFactory as

/**
* If you are familiar with Adapter of ListView,this is the same as adapter
* with few changes
*
*/
public class ListProvider implements RemoteViewsFactory {
private ArrayList listItemList = new ArrayList();
private Context context = null;
private int appWidgetId;

public ListProvider(Context context, Intent intent) {
this.context = context;
appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);

populateListItem();
}

private void populateListItem() {
for (int i = 0; i &lt; 10; i++) {
ListItem listItem = new ListItem();
listItem.heading = "Heading" + i;
listItem.content = i
+ " This is the content of the app widget listview.Nice content though";
listItemList.add(listItem);
}

}

@Override
public int getCount() {
return listItemList.size();
}

@Override
public long getItemId(int position) {
return position;
}

/*
*Similar to getView of Adapter where instead of View
*we return RemoteViews
*
*/
@Override
public RemoteViews getViewAt(int position) {
final RemoteViews remoteView = new RemoteViews(
context.getPackageName(), R.layout.list_row);
ListItem listItem = listItemList.get(position);
remoteView.setTextViewText(R.id.heading, listItem.heading);
remoteView.setTextViewText(R.id.content, listItem.content);

return remoteView;
}
}

Now let us go to governing body of widget i.e. AppWidgetProvider

public class WidgetProvider extends AppWidgetProvider {

/**
* this method is called every 30 mins as specified on widgetinfo.xml
* this method is also called on every phone reboot
**/

@Override
public void onUpdate(Context context, AppWidgetManager 
                appWidgetManager,int[] appWidgetIds) {

/*int[] appWidgetIds holds ids of multiple instance 
 * of your widget
 * meaning you are placing more than one widgets on 
 * your homescreen*/
 final int N = appWidgetIds.length;
 for (int i = 0; i &lt; N; ++i) {
     RemoteViews remoteViews = updateWidgetListView(context,
                                           appWidgetIds[i]);
     appWidgetManager.updateAppWidget(appWidgetIds[i], 
                                           remoteViews);
    }
 super.onUpdate(context, appWidgetManager, appWidgetIds);
}

private RemoteViews updateWidgetListView(Context context,
                                     int appWidgetId) {

   //which layout to show on widget
   RemoteViews remoteViews = new RemoteViews(
         context.getPackageName(),R.layout.widget_layout);

  //RemoteViews Service needed to provide adapter for ListView
  Intent svcIntent = new Intent(context, WidgetService.class);
  //passing app widget id to that RemoteViews Service
  svcIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
  //setting a unique Uri to the intent
  //don't know its purpose to me right now
  svcIntent.setData(Uri.parse(
                    svcIntent.toUri(Intent.URI_INTENT_SCHEME)));
  //setting adapter to listview of the widget
  remoteViews.setRemoteAdapter(appWidgetId, R.id.listViewWidget,
                                svcIntent);
  //setting an empty view in case of no data
  remoteViews.setEmptyView(R.id.listViewWidget, R.id.empty_view);
  return remoteViews;
 }

}


I have put comments on the code to let you understand different aspect of the AppWidgetProvider. Lastly,to make the AppWidget ListView to use Adapter,on must define RemoteViewsService on AndroidManifest.xml as well.

 <service
   android:name=".WidgetService"
   android:permission="android.permission.BIND_REMOTEVIEWS" />

This is all,to show an AppWidget with ListView on it. You can checkout My Github to download all the codes of this tutorial. On next tutorial,I will show how to populate app widget listview from remote data(i.e from web). Till then Happy Coding

Advertisements

24 thoughts on “Android app widget with ListView

  1. Pingback: Populate AppWidget ListView with remote data(data from web) | Laaptu
  2. Pingback: Android Showing Remote Image on ImageView of App-Widget ListView | Laaptu
  3. Pingback: Android Setting Update Interval On Appwidget With Listview | Laaptu
  4. thanks for the tutorial, it’s way better than the tutorial provided by Google Dev

    however, I wasn’t able to make it work. The widget appeared in widget list, and I can drag and put it on homescreen, 10 items appeared on the list but it displays “loading…” instead of any kind of string I tried to enter.

    I switched your ListItem to a simple String Array since I only needed one string for each item, and also switched the ++i to i++ in WidgetProvider. Since I’m coding for API 14+ it told me to change the setRemoteAdapter since it was depreciated, so I just removed the appwidgetId in it. The rest of the programs are copy-pasted except for the R.layout and R.id

  5. Pingback: Android widget with ListView doesn't load items correclty | SuperBlog
  6. Pingback: Android app widget with ListView | Laaptu - appgong
  7. Pingback: Android Widget with ListView displaying Loading views only
  8. Pingback: Get ListView from widget layout | Zou Answers
  9. How to set listener in this widget list adapter. So that according to position that list should be clickable. I mean, I want to make widget list items clickable.
    Please help me out.

    @Override
    public RemoteViews getViewAt(int position) {
    final RemoteViews remoteView = new RemoteViews(
    context.getPackageName(), R.layout.list_row);
    ListItem listItem = listItemList.get(position);
    remoteView.setTextViewText(R.id.heading, listItem.heading);
    remoteView.setTextViewText(R.id.content, listItem.content);

    return remoteView;
    }

  10. All my ListView items were displaying “Loading” until now. The solution was the following: I replaced “return 0” by “return 1” in getViewTypeCount() method of ListProvider class. Like this:
    @Override
    public int getViewTypeCount() {
    return 1;
    }

  11. I tried to port this for xamarin c# Android getting error all the time, i have no idea what i’ve done wrong

    Java.Lang.RuntimeException: Unable to bind to service md5037fca43706e0b812912372d4076c895.WidgetService@3888bf9f with Intent { dat=intent: cmp=StimeApplication.StimeApplication/md5037fca43706e0b812912372d4076c895.WidgetService (has extras) }: java.lang.NullPointerException: Attempt to invoke interface method ‘void android.widget.RemoteViewsService$RemoteViewsFactory.onCreate()’ on a null object reference —> Java.Lang.NullPointerException: Attempt to invoke interface method ‘void android.widget.RemoteViewsService$RemoteViewsFactory.onCreate()’ on a null object reference
    at — End of managed exception stack trace —
    at java.lang.NullPointerException: Attempt to invoke interface method ‘void android.widget.RemoteViewsService$RemoteViewsFactory.onCreate()’ on a null object reference
    at at android.widget.RemoteViewsService.onBind(RemoteViewsService.java:243)
    at at android.app.ActivityThread.handleBindService(ActivityThread.java:2939)
    at at android.app.ActivityThread.access$1900(ActivityThread.java:169)
    at at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1511)
    at at android.os.Handler.dispatchMessage(Handler.java:111)
    at at android.os.Looper.loop(Looper.java:194)
    at at android.app.ActivityThread.main(ActivityThread.java:5552)
    at at java.lang.reflect.Method.invoke(Native Method)
    at at java.lang.reflect.Method.invoke(Method.java:372)
    at at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:964)
    at at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:759)
    — End of inner exception stack trace —
    at — End of managed exception stack trace —
    at java.lang.RuntimeException: Unable to bind to service md5037fca43706e0b812912372d4076c895.WidgetService@3888bf9f with Intent { dat=intent: cmp=StimeApplication.StimeApplication/md5037fca43706e0b812912372d4076c895.WidgetService (has extras) }: java.lang.NullPointerException: Attempt to invoke interface method ‘void android.widget.RemoteViewsService$RemoteViewsFactory.onCreate()’ on a null object reference
    at at android.app.ActivityThread.handleBindService(ActivityThread.java:2952)
    at at android.app.ActivityThread.access$1900(ActivityThread.java:169)
    at at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1511)
    at at android.os.Handler.dispatchMessage(Handler.java:111)
    at at android.os.Looper.loop(Looper.java:194)
    at at android.app.ActivityThread.main(ActivityThread.java:5552)
    at at java.lang.reflect.Method.invoke(Native Method)
    at at java.lang.reflect.Method.invoke(Method.java:372)
    at at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:964)
    at at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:759)
    at Caused by: java.lang.NullPointerException: Attempt to invoke interface method ‘void android.widget.RemoteViewsService$RemoteViewsFactory.onCreate()’ on a null object reference
    at at android.widget.RemoteViewsService.onBind(RemoteViewsService.java:243)
    at at android.app.ActivityThread.handleBindService(ActivityThread.java:2939)
    at … 9 more

  12. Great tutorial. Just need example of ListView onItemClick. I tried to add onClick but failed. Can you please provide that. It’d be great. 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s