From AsyncTask Back to the Main Thread

To finish off, let’s switch to the view layer and get PhotoGalleryFragment’s RecyclerView to display some captions.

First define a ViewHolder as an inner class.

Listing 25.14  Adding a ViewHolder implementation (PhotoGalleryFragment.java)

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        ...
    }

    private class PhotoHolder extends RecyclerView.ViewHolder {
        private TextView mTitleTextView;

        public PhotoHolder(View itemView) {
            super(itemView);

            mTitleTextView = (TextView) itemView;
        }

        public void bindGalleryItem(GalleryItem item) {
            mTitleTextView.setText(item.toString());
        }
    }

    private class FetchItemsTask extends AsyncTask<Void,Void,Void> {
        ...
    }
}

Next, add a RecyclerView.Adapter to provide PhotoHolders as needed based on a list of GalleryItems.

Listing 25.15  Adding a RecyclerView.Adapter implementation (PhotoGalleryFragment.java)

public class PhotoGalleryFragment extends Fragment {

    private static final String TAG = "PhotoGalleryFragment";
    ...
    private class PhotoHolder extends RecyclerView.ViewHolder {
        ...
    }

    private class PhotoAdapter extends RecyclerView.Adapter<PhotoHolder> {

        private List<GalleryItem> mGalleryItems;

        public PhotoAdapter(List<GalleryItem> galleryItems) {
            mGalleryItems = galleryItems;
        }

        @Override
        public PhotoHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
            TextView textView = new TextView(getActivity());
            return new PhotoHolder(textView);
        }

        @Override
        public void onBindViewHolder(PhotoHolder photoHolder, int position) {
            GalleryItem galleryItem = mGalleryItems.get(position);
            photoHolder.bindGalleryItem(galleryItem);
        }

        @Override
        public int getItemCount() {
            return mGalleryItems.size();
        }
    }
    ...
}

Now that you have the appropriate nuts and bolts in place for RecyclerView, add code to set up and attach an adapter when appropriate.

Listing 25.16  Implementing setupAdapter() (PhotoGalleryFragment.java)

public class PhotoGalleryFragment extends Fragment {

    private static final String TAG = "PhotoGalleryFragment";

    private RecyclerView mPhotoRecyclerView;
    private List<GalleryItem> mItems = new ArrayList<>();
    ...
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_photo_gallery, container, false);

        mPhotoRecyclerView = (RecyclerView) v.findViewById(R.id.photo_recycler_view);
        mPhotoRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 3));

        setupAdapter();

        return v;
    }

    private void setupAdapter() {
        if (isAdded()) {
            mPhotoRecyclerView.setAdapter(new PhotoAdapter(mItems));
        }
    }
    ...
}

The setupAdapter() method you just added looks at the current model state, namely the List of GalleryItems, and configures the adapter appropriately on your RecyclerView. You call setupAdapter() in onCreateView(…) so that every time a new RecyclerView is created, it is reconfigured with an appropriate adapter. You also want to call it every time your set of model objects changes.

Notice that you check to see whether isAdded() is true before setting the adapter. This confirms that the fragment has been attached to an activity, and in turn that getActivity() will not be null.

Remember that fragments can exist unattached to any activity. Before now, this possibility has not come up because your method calls have been driven by callbacks from the framework. In this scenario, if a fragment is receiving callbacks, then it definitely is attached to an activity. No activity, no callbacks.

However, now that you are using an AsyncTask you are triggering some callbacks from a background thread. Thus you cannot assume that the fragment is attached to an activity. You must check to make sure that your fragment is still attached. If it is not, then operations that rely on that activity (like creating your PhotoAdapter, which in turn creates a TextView using the hosting activity as the context) will fail. This is why, in your code above, you check that isAdded() is true before setting the adapter.

Now you need to call setupAdapter() after data has been fetched from Flickr. Your first instinct might be to call setupAdapter() at the end of FetchItemsTask’s doInBackground(…). This is not a good idea. Remember that you have two Flashes in the store now – one helping multiple customers, and one on the phone with Flickr. What will happen if the second Flash tries to help customers after hanging up the phone? Odds are good that the two Flashes will step on each other’s toes.

On a computer, this toe-stepping-on results in objects in memory becoming corrupted. Because of this, you are not allowed to update the UI from a background thread, nor is it safe or advisable to do so.

What to do? AsyncTask has another method you can override called onPostExecute(…). onPostExecute(…) is run after doInBackground(…) completes. More importantly, onPostExecute(…) is run on the main thread, not the background thread, so it is safe to update the UI within it.

Modify FetchItemsTask to update mItems and call setupAdapter() after fetching your photos to update the RecyclerView’s data source.

Listing 25.17  Adding adapter update code (PhotoGalleryFragment.java)

private class FetchItemsTask extends AsyncTask<Void,Void,Void List<GalleryItem>> {
    @Override
    protected Void List<GalleryItem> doInBackground(Void... params) {

        return new FlickrFetchr().fetchItems();
        return null;
    }

    @Override
    protected void onPostExecute(List<GalleryItem> items) {
        mItems = items;
        setupAdapter();
    }
}

You made three changes here. First, you changed the type of the FetchItemsTask’s third generic parameter. This parameter is the type of result produced by your AsyncTask. It sets the type of value returned by doInBackground(…) as well as the type of onPostExecute(…)’s input parameter.

Second, you modified doInBackground(…) to return your list of GalleryItems. By doing this you fixed your code so that it compiles properly. You also passed your list of items off so that it can be used from within onPostExecute(…).

Finally, you added an implementation of onPostExecute(…). This method accepts as input the list you fetched and returned inside doInBackground(…), puts it in mItems, and updates your RecyclerView’s adapter.

With that, your work for this chapter is complete. Run PhotoGallery, and you should see text displayed for each GalleryItem you downloaded (similar to Figure 25.2).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset