Pages

Tuesday 11 March 2014

Collection widget with event handling in android

Hello Friends,

Here we are going to learn how to create Collection widget with each item click event in android.

Final output :



What we required for widget ?


Widget Provider (xml and receiver)
XML stores widget information and reciver update that widget details.

Widget RemoteView service
Remote View (ListView collection) update service which will call RemoteViewFactory.

Widget RemoteViewFactory
RemoteViewFactory used for setting data in listview or gridview, also handling each item getView and its event.


Widget Provider (xml and receiver)

You need to create one xml folder in your res directory and create one file named widget_provider.xml

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_provider_layout"
    android:minHeight="100dip"
    android:minResizeHeight="100dip"
    android:minResizeWidth="100dip"
    android:minWidth="100dip"
    android:previewImage="@drawable/ic_launcher"
    android:resizeMode="vertical|horizontal"
    android:updatePeriodMillis="1800000"
    android:widgetCategory="keyguard|home_screen" >

</appwidget-provider>

You can find more detail about this xml metadata on http://developer.android.com/guide/topics/appwidgets/index.html#MetaData

Note that here I have created widget_provider_layout.xml file as below.


<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="@dimen/widget_margin" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#88ffffff" >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="Hello Widget"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textColor="#fff"
            android:textStyle="bold" />
    </LinearLayout>

</FrameLayout>

WidgetProvider class:

Now we required one widget provider class (also called widget receiver)


package com.dharmangsoni.widgets;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;

public class WidgetProvider extends AppWidgetProvider {

 @Override
 public void onReceive(Context context, Intent intent) {
  super.onReceive(context, intent);
 }

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

Now we are going to register this provider to AndroidManifest.xml so we can see the output of widget.


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.dharmangsoni.collectionwidget"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.dharmangsoni.collectionwidget.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- Widget Receiver -->
        <receiver android:name="com.dharmangsoni.widgets.WidgetProvider" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_provider" />
        </receiver>

    </application>

</manifest>


Now we are ready to see the output of widget. Run application and add widget from widget menu:


Ok now we are going to create our required layout for widget as below:


<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="@dimen/widget_margin" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#bbDEDFDE"
        android:orientation="vertical" >

        <LinearLayout
            android:id="@+id/widgetLayoutMain"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#aaDEDFDE"
            android:orientation="horizontal"
            android:paddingLeft="5dp"
            android:paddingRight="5dp" >

            <ImageView
                android:id="@+id/widgetImgLauncher"
                android:layout_width="32dp"
                android:layout_height="32dp"
                android:layout_gravity="center_vertical"
                android:src="@drawable/ic_launcher" />

            <LinearLayout
                android:id="@+id/widgetTopBar"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical"
                android:paddingBottom="4dp"
                android:paddingLeft="8dp"
                android:paddingTop="4dp" >

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/app_name"
                    android:textAppearance="?android:attr/textAppearanceSmall"
                    android:textColor="#4B4B4D"
                    android:textStyle="bold" />

                <TextView
                    android:id="@+id/txvWidgetTitle"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="dharmangsoni.blogspot.in"
                    android:textAppearance="?android:attr/textAppearanceSmall"
                    android:textColor="#4B4B4D" />
            </LinearLayout>
        </LinearLayout>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#aaa" />

        <ListView
            android:id="@+id/widgetCollectionList"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >
        </ListView>
    </LinearLayout>

</FrameLayout>

Output will be :




Now we are ready with our receiver and xml provider and layout file.

Before create RemoteViewService we have to create RemoteViewFactory for handling listview data.

Widget RemoteViewFactory:

package com.dharmangsoni.widgets;

import java.util.ArrayList;
import java.util.List;

import android.annotation.SuppressLint;
import android.content.Context;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService.RemoteViewsFactory;

@SuppressLint("NewApi")
public class WidgetDataProvider implements RemoteViewsFactory {

 List mCollections = new ArrayList();

 Context mContext = null;

 public WidgetDataProvider(Context context, Intent intent) {
  mContext = context;
 }

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

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

 @Override
 public RemoteViews getLoadingView() {
  return null;
 }

 @Override
 public RemoteViews getViewAt(int position) {
  RemoteViews mView = new RemoteViews(mContext.getPackageName(),
    android.R.layout.simple_list_item_1);
  mView.setTextViewText(android.R.id.text1, mCollections.get(position));
                mView.setTextColor(android.R.id.text1, Color.BLACK);
  return mView;
 }

 @Override
 public int getViewTypeCount() {
  return 1;
 }

 @Override
 public boolean hasStableIds() {
  return true;
 }

 @Override
 public void onCreate() {
  initData();
 }

 @Override
 public void onDataSetChanged() {
  initData();
 }

 private void initData() {
  mCollections.clear();
  for (int i = 1; i <= 10; i++) {
   mCollections.add("ListView item " + i);
  }
 }

 @Override
 public void onDestroy() {

 }

}

Creating RemoteViewService :


package com.dharmangsoni.widgets;

import android.annotation.SuppressLint;
import android.content.Intent;
import android.widget.RemoteViewsService;

@SuppressLint("NewApi")
public class WidgetService extends RemoteViewsService {

 @Override
 public RemoteViewsFactory onGetViewFactory(Intent intent) {

  WidgetDataProvider dataProvider = new WidgetDataProvider(
    getApplicationContext(), intent);
  return dataProvider;
 }

}

Adding service entry into Manifest file under <application> tag:
<!-- Widget service -->
        <service
            android:name="com.dharmangsoni.widgets.WidgetService"
            android:permission="android.permission.BIND_REMOTEVIEWS" />


Updating WidgetProvider to initialize collection view.


package com.dharmangsoni.widgets;

import android.annotation.SuppressLint;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.widget.RemoteViews;

import com.dharmangsoni.collectionwidget.R;

public class WidgetProvider extends AppWidgetProvider {

 @Override
 public void onReceive(Context context, Intent intent) {
  super.onReceive(context, intent);
 }

 @Override
 public void onUpdate(Context context, AppWidgetManager appWidgetManager,
   int[] appWidgetIds) {
  for (int widgetId : appWidgetIds) {
   RemoteViews mView = initViews(context, appWidgetManager, widgetId);
   appWidgetManager.updateAppWidget(widgetId, mView);
  }

  super.onUpdate(context, appWidgetManager, appWidgetIds);
 }


 @SuppressWarnings("deprecation")
 @SuppressLint("NewApi")
 private RemoteViews initViews(Context context,
   AppWidgetManager widgetManager, int widgetId) {

  RemoteViews mView = new RemoteViews(context.getPackageName(),
    R.layout.widget_provider_layout);

  Intent intent = new Intent(context, WidgetService.class);
  intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);

  intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
  mView.setRemoteAdapter(widgetId, R.id.widgetCollectionList, intent);

  return mView;
 }

}

Now run your app will give you following output:




Now adding each item click event for collection and making toast for an item.

To do we have to add setOnClickFillInIntent in getViewAt in WedgetDataProvider.


. . . 
@Override
 public RemoteViews getViewAt(int position) {
  RemoteViews mView = new RemoteViews(mContext.getPackageName(),
    android.R.layout.simple_list_item_1);
  mView.setTextViewText(android.R.id.text1, mCollections.get(position));
  mView.setTextColor(android.R.id.text1, Color.BLACK);
  
  final Intent fillInIntent = new Intent();
  fillInIntent.setAction(WidgetProvider.ACTION_TOAST);
  final Bundle bundle = new Bundle();
  bundle.putString(WidgetProvider.EXTRA_STRING,
    mCollections.get(position));
  fillInIntent.putExtras(bundle);
  mView.setOnClickFillInIntent(android.R.id.text1, fillInIntent);
  return mView;
 }
. . . 


Now handling click event in WidgetProvider->onReceive:


. . . . 
public class WidgetProvider extends AppWidgetProvider {

 public static final String ACTION_TOAST = "com.dharmangsoni.widgets.ACTION_TOAST";
 public static final String EXTRA_STRING = "com.dharmangsoni.widgets.EXTRA_STRING";

 @Override
 public void onReceive(Context context, Intent intent) {
  if (intent.getAction().equals(ACTION_TOAST)) {
   String item = intent.getExtras().getString(EXTRA_STRING);
   Toast.makeText(context, item, Toast.LENGTH_LONG).show();
  }
  super.onReceive(context, intent);
 }

        @SuppressLint("NewApi")
 @Override
 public void onUpdate(Context context, AppWidgetManager appWidgetManager,
   int[] appWidgetIds) {
  for (int widgetId : appWidgetIds) {
   RemoteViews mView = initViews(context, appWidgetManager, widgetId);

   // Adding collection list item handler
   final Intent onItemClick = new Intent(context, WidgetProvider.class);
   onItemClick.setAction(ACTION_TOAST);
   onItemClick.setData(Uri.parse(onItemClick
     .toUri(Intent.URI_INTENT_SCHEME)));
   final PendingIntent onClickPendingIntent = PendingIntent
     .getBroadcast(context, 0, onItemClick,
       PendingIntent.FLAG_UPDATE_CURRENT);
   mView.setPendingIntentTemplate(R.id.widgetCollectionList,
     onClickPendingIntent);

   appWidgetManager.updateAppWidget(widgetId, mView);
  }
  super.onUpdate(context, appWidgetManager, appWidgetIds);
 }
. . . 



Download Full source :
https://www.dropbox.com/s/u019v6arbae37oh/CollectionWidget.tar.gz

Hope you like it.

1 comment:

  1. Hey, really thanks for your blog, but an issue is still vague.
    What is last code block in onupdate means?
    Isn't the wideget provider sed a message and received by the onreceiver?
    So why the Adding collection list item handler is needs?

    ReplyDelete