Question

I am trying to adapt the pattern from Google I/O using AsyncTask to download images from the network and place them into ListView items within an AppWidget. I haven't found many resources on doing such a thing, except for this post.

The problem is, that poster didn't have a collection view, only a single item. When I try to follow that pattern, the whole appwidget is updated with just the single list item every time the onPostExecute() runs, not what I need. If you remove call to updateAppWidget() in onPostExecute(), the views are not updated, I'm guessing because of the inter-process communication that must occur. I'm not really sure where to go from here, can anyone help?

Here is my definition for the AsyncTask:

public ThumbnailAsyncTask(RemoteViews rv, int appWidgetID, AppWidgetManager appWidgetManager)
{
    mTarget = rv;
    this.widgetID = appWidgetID;
    this.widgetManager = appWidgetManager;  
}

@Override
protected Bitmap doInBackground(String... params)
{
    String url = params[0];
    return getBitmapFromURL(url);
}

@Override
protected void onPostExecute(Bitmap result)
{
    mTarget.setImageViewBitmap(R.id.thumbnail, result);
    widgetManager.updateAppWidget(widgetID, mTarget);
}

private Bitmap getBitmapFromURL(String str)
{
    Bitmap bmp = null;
    HttpURLConnection connection = null;

    try
    {
        URL url = new URL(str);
        connection = openConnectionWithTimeout(url);
        bmp = BitmapFactory.decodeStream(connection.getInputStream());
    }
    catch (IOException e)
    {
        e.printStackTrace();
    }
    finally
    {
        connection.disconnect();
    }
    return bmp;
}
}

and the getViewAt() from the RemoteViewsFactory:

public RemoteViews getViewAt(int position)
{
    RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.list_item);
    ArrayList<ResultDetails> mResults = mJSONParser.getResults();

    if (mResults.get(position).hasHeading())
        rv.setTextViewText(R.id.title, mResults.get(position).getHeading());
    if (mResults.get(position).hasBody())
        rv.setTextViewText(R.id.description, mResults.get(position).getBody(true));
    if (mResults.get(position).hasTimestamp())
        rv.setTextViewText(R.id.date, mResults.get(position).getTimestamp(true));

    Bundle extras = new Bundle();
    extras.putString(LINK_ITEM, mResults.get(position).getExternalURL());
    Intent fillInIntent = new Intent();
    fillInIntent.putExtras(extras);
    rv.setOnClickFillInIntent(R.id.list_item, fillInIntent);

    if (mResults.get(position).hasThumbnailURL())
    {
        AppWidgetManager mgr = AppWidgetManager.getInstance(mContext);
        new ThumbnailAsyncTask(rv, mAppWidgetId, mgr).execute(mResults.get(position).getThumbnailURL());
    }

    return rv;
}
Was it helpful?

Solution

I wasn't happy with the long load times, so I figured out a way to quickly load a placeholder image, and then load the thumbnails in an AsyncTask, and then call notifyAppWidgetViewDataChanged() once to refresh the appwidget after they are all loaded. This results in instant appearance of the data without the images, and is a pretty good solution for my application. I used a boolean value in my RemoteViewsFactory to indicate if I had already downloaded the data with no images, so the it wouldn't get stuck in an endless refresh cycle.

class ThumbnailAsyncTask extends AsyncTask<Void, Void, Void>
{
private AppWidgetManager widgetManager;
private List<ResultDetails> details;
private Context context;

public ThumbnailAsyncTask(AppWidgetManager
        appWidgetManager, List<ResultDetails> details, Context context)
{
    this.details = details;
    this.context = context;
    this.widgetManager = appWidgetManager;
}

@Override
protected Void doInBackground(Void... params)
{
    for (ResultDetails item : details)
    {
        if (item.hasThumbnailURL())
        {
            item.setThumbnail(getBitmapFromURL(item.getThumbnailURL()));
        }
    }
    return null;
}
@Override
protected void onPostExecute(Void result)
{
    widgetManager.notifyAppWidgetViewDataChanged(widgetManager.getAppWidgetIds(
                        new ComponentName(context, WidgetProvider.class)), R.id.list_view);

}

public RemoteViews getViewAt(int position)
{
    RemoteViews rv;
    rv = new RemoteViews(mContext.getPackageName(), R.layout.list_item);
    ArrayList<ResultDetails> mResults = mSearchResultsGenerator.getResults();
    ResultDetails result = mResults.get(position);

    if (result.hasHeading())
        rv.setTextViewText(R.id.title, result.getHeading());
    if (result.hasBody())
        rv.setTextViewText(R.id.description, result.getBody(true));
    if (result.hasTimestamp())
        rv.setTextViewText(R.id.date, result.getTimestamp(true));
    if (result.hasPrice())
        rv.setTextViewText(R.id.price, result.getPrice());

    Bundle extras = new Bundle();
    extras.putString(LINK_ITEM, result.getExternalURL());
    Intent fillInIntent = new Intent();
    fillInIntent.putExtras(extras);
    rv.setOnClickFillInIntent(R.id.list_item, fillInIntent);

    if (result.hasThumbnail())
    {
        rv.setImageViewBitmap(R.id.thumbnail, result.getThumbnail());
    }
    else
    {
        rv.setImageViewBitmap(R.id.thumbnail, noImageBitmap);
    }

    return rv;
}
public void onDataSetChanged()
{
    if (isOnline(mContext))
    {
        if (!hasData)
        {
            mSearchResultsGenerator.generate();
            if (mSearchResultsGenerator.isUpdateSucceeded())
            {
                AppWidgetManager mgr = AppWidgetManager.getInstance(mContext);
                hasData = true;
                new ThumbnailAsyncTask(mgr, mSearchResultsGenerator.getResults(), mContext).execute();
            }
            else
                makeNoConnectionToast();
        }
        else
        {

        }
    }
    else
        makeNoConnectionToast();
}

OTHER TIPS

I believe you need to call AppWidgetManager.notifyAppWidgetViewDataChanged() to refresh collection views in an AppWidget

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top