Java tutorial
/* * Copyright 2014 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package koma.movieapp.ui; import android.app.Activity; import android.app.Fragment; import android.app.ListFragment; import android.app.LoaderManager; import android.content.Context; import android.content.Intent; import android.content.Loader; import android.content.SharedPreferences; import android.database.Cursor; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Parcelable; import android.preference.PreferenceManager; import android.content.AsyncTaskLoader; import android.support.v4.view.ViewCompat; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.DecelerateInterpolator; import android.widget.ImageView; import android.widget.TextView; import com.uwetrottmann.tmdb.Tmdb; import com.uwetrottmann.tmdb.entities.Movie; import com.uwetrottmann.tmdb.entities.ResultsPage; import com.uwetrottmann.tmdb.services.MoviesService; import koma.movieapp.util.ImageLoader; import com.bumptech.glide.GenericRequestBuilder; import com.bumptech.glide.ListPreloader; import koma.movieapp.Config; import koma.movieapp.R; import koma.movieapp.ui.widget.CollectionView; import koma.movieapp.ui.widget.CollectionViewCallbacks; import koma.movieapp.ui.widget.MessageCardView; import koma.movieapp.util.PrefUtils; import koma.movieapp.util.UIUtils; import java.text.DateFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.TimeZone; import static koma.movieapp.util.LogUtils.LOGD; import static koma.movieapp.util.LogUtils.LOGE; import static koma.movieapp.util.LogUtils.LOGV; import static koma.movieapp.util.LogUtils.LOGW; import static koma.movieapp.util.LogUtils.makeLogTag; /** * A {@link ListFragment} showing a list of sessions. The fragment arguments * indicate what is the list of sessions to show. It may be a set of tag * filters or a search query. */ public class MoviesFragment extends Fragment implements LoaderManager.LoaderCallbacks<ArrayList<Movie>>, CollectionViewCallbacks { private static final String TAG = makeLogTag(MoviesFragment.class); // Disable track branding public static final String EXTRA_NO_TRACK_BRANDING = "com.google.android.iosched.extra.NO_TRACK_BRANDING"; private static final String STATE_SESSION_QUERY_TOKEN = "session_query_token"; private static final String STATE_ARGUMENTS = "arguments"; /** * The handler message for updating the search query. */ private static final int MESSAGE_QUERY_UPDATE = 1; /** * The delay before actual requerying in millisecs. */ private static final int QUERY_UPDATE_DELAY_MILLIS = 100; /** * The number of rows ahead to preload images for */ private static final int ROWS_TO_PRELOAD = 2; private static final int ANIM_DURATION = 250; private static final int CARD_DISMISS_ACTION_DELAY = MessageCardView.ANIM_DURATION - 50; private Context mAppContext; //private ImageLoader mImageLoader; // the cursor whose data we are currently displaying private int mSessionQueryToken; private String mCurrentSearchString; //private Uri mCurrentUri = ScheduleContract.Sessions.CONTENT_URI; private Cursor mCursor; private ArrayList<Movie> mMovieList; private String mToolbarTitle; private boolean mIsSearchCursor; private boolean mNoTrackBranding; // this variable is relevant when we start the sessions loader, and indicates the desired // behavior when load finishes: if true, this is a full reload (for example, because filters // have been changed); if not, it's just a refresh because data has changed. private boolean mSessionDataIsFullReload = false; private ImageLoader mImageLoader; private int mDefaultMovieColor; private CollectionView mCollectionView; private TextView mEmptyView; private View mLoadingView; private boolean mWasPaused = false; private static final int HERO_GROUP_ID = 123; private Bundle mArguments; private DateFormat mDateFormat = DateFormat.getDateInstance(DateFormat.SHORT); private DateFormat mTimeFormat = DateFormat.getTimeInstance(DateFormat.SHORT); private static final String CARD_ANSWER_ATTENDING_REMOTELY = "CARD_ANSWER_ATTENDING_REMOTELY"; private static final String CARD_ANSWER_ATTENDING_IN_PERSON = "CARD_ANSWER_ATTENDING_IN_PERSON"; private static final String CARD_ANSWER_YES = "CARD_ANSWER_YES"; private static final String CARD_ANSWER_NO = "CARD_ANSWER_NO"; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == MESSAGE_QUERY_UPDATE) { String query = (String) msg.obj; //reloadFromArguments(BaseActivity.intentToFragmentArguments( // new Intent(Intent.ACTION_SEARCH, ScheduleContract.Sessions.buildSearchUri(query))));*/ } } }; private Preloader mPreloader; public boolean canCollectionViewScrollUp() { return ViewCompat.canScrollVertically(mCollectionView, -1); } public void setContentTopClearance(int topClearance) { mCollectionView.setContentTopClearance(topClearance); } // Called when there is a change on sessions in the content provider private void onSessionsContentChanged() { LOGD(TAG, "ThrottledContentObserver fired (sessions). Content changed."); if (!isAdded()) { LOGD(TAG, "Ignoring ContentObserver event (Fragment not added)."); return; } LOGD(TAG, "Requesting sessions cursor reload as a result of ContentObserver firing."); reloadMovieData(false); } private void reloadMovieData(boolean fullReload) { LOGD(TAG, "Reloading session data: " + (fullReload ? "FULL RELOAD" : "light refresh")); mSessionDataIsFullReload = fullReload; getLoaderManager().restartLoader(mSessionQueryToken, mArguments, MoviesFragment.this); } @Override public void onPause() { super.onPause(); mWasPaused = true; } @Override public void onResume() { super.onResume(); if (mWasPaused) { mWasPaused = false; LOGD(TAG, "Reloading data as a result of onResume()"); reloadMovieData(false); } } public interface Callbacks { public void onMovieSelected(String sessionId, View clickedView); } private static Callbacks sDummyCallbacks = new Callbacks() { @Override public void onMovieSelected(String sessionId, View clickedView) { } }; private Callbacks mCallbacks = sDummyCallbacks; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // if (mImageLoader == null) { mImageLoader = new ImageLoader(this.getActivity()); } mDefaultMovieColor = getResources().getColor(R.color.default_movie_color); mToolbarTitle = ""; final TimeZone tz = PrefUtils.getDisplayTimeZone(getActivity()); mDateFormat.setTimeZone(tz); mTimeFormat.setTimeZone(tz); if (savedInstanceState != null) { mSessionQueryToken = savedInstanceState.getInt(STATE_SESSION_QUERY_TOKEN); if (mSessionQueryToken > 0) { // Only if this is a config change should we initLoader(), to reconnect with an // existing loader. Otherwise, the loader will be init'd when reloadFromArguments // is called. getLoaderManager().initLoader(mSessionQueryToken, null, MoviesFragment.this); //getLoaderManager().initLoader(0, null, this); } } } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mAppContext = getActivity().getApplicationContext(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_movies, container, false); mCollectionView = (CollectionView) root.findViewById(R.id.movies_collection_view); mPreloader = new Preloader(ROWS_TO_PRELOAD); mCollectionView.setOnScrollListener(mPreloader); mEmptyView = (TextView) root.findViewById(R.id.empty_text); mLoadingView = root.findViewById(R.id.loading); return root; } void reloadFromArguments(Bundle arguments) { // Load new arguments if (arguments == null) { arguments = new Bundle(); } else { // since we might make changes, don't meddle with caller's copy arguments = (Bundle) arguments.clone(); } // save arguments so we can reuse it when reloading from content observer events mArguments = arguments; mToolbarTitle = arguments.getString("title"); getActivity().setTitle(mToolbarTitle); /*LOGD(TAG, "MoviesFragment reloading from arguments: " + arguments); mCurrentUri = arguments.getParcelable("_uri"); if (mCurrentUri == null) { // if no URI, default to all sessions URI LOGD(TAG, "MoviesFragment did not get a URL, defaulting to all sessions."); //arguments.putParcelable("_uri", ScheduleContract.Sessions.CONTENT_URI); //mCurrentUri = ScheduleContract.Sessions.CONTENT_URI; }*/ mSessionQueryToken = mArguments.getInt("queryType"); mCurrentSearchString = arguments.getString("searchString"); mNoTrackBranding = mArguments.getBoolean(EXTRA_NO_TRACK_BRANDING); //LOGD(TAG, "MoviesFragment reloading, uri=" + mCurrentUri); reloadMovieData(true); // full reload } void requestQueryUpdate(String query) { mHandler.removeMessages(MESSAGE_QUERY_UPDATE); mHandler.sendMessageDelayed(Message.obtain(mHandler, MESSAGE_QUERY_UPDATE, query), QUERY_UPDATE_DELAY_MILLIS); } @Override public void onAttach(Activity activity) { super.onAttach(activity); if (!(activity instanceof Callbacks)) { throw new ClassCastException("Activity must implement fragment's callbacks."); } mAppContext = getActivity().getApplicationContext(); mCallbacks = (Callbacks) activity; SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(activity); sp.registerOnSharedPreferenceChangeListener(mPrefChangeListener); } @Override public void onDetach() { super.onDetach(); mCallbacks = sDummyCallbacks; SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity()); sp.unregisterOnSharedPreferenceChangeListener(mPrefChangeListener); } public void animateReload() { //int curTop = mCollectionView.getTop(); mCollectionView.setAlpha(0); //mCollectionView.setTop(getResources().getDimensionPixelSize(R.dimen.browse_sessions_anim_amount)); //mCollectionView.animate().y(curTop).alpha(1).setDuration(ANIM_DURATION).setInterpolator(new DecelerateInterpolator()); mCollectionView.animate().alpha(1).setDuration(ANIM_DURATION).setInterpolator(new DecelerateInterpolator()); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(STATE_SESSION_QUERY_TOKEN, mSessionQueryToken); outState.putParcelable(STATE_ARGUMENTS, mArguments); } // LoaderCallbacks interface @Override public Loader<ArrayList<Movie>> onCreateLoader(int id, Bundle data) { LOGD(TAG, "onCreateLoader, id=" + id + ", data=" + data); final Intent intent = BaseActivity.fragmentArgumentsToIntent(data); String searchString = intent.getStringExtra("searchString"); return new MovieListLoader(getActivity(), mCurrentSearchString); } @Override public void onLoadFinished(Loader<ArrayList<Movie>> loader, ArrayList<Movie> data) { if (getActivity() == null) { return; } int token = loader.getId(); LOGD(TAG, "Loader finished: " + (token == Config.NOW_PLAYING_TOKEN ? "now playing" : token == Config.UPCOMING_TOKEN ? "upcoming" : token == Config.POPULAR_TOKEN ? "popular" : token == Config.SEARCH_TOKEN ? "search" : "unknown")); mMovieList = data; LOGD(TAG, "Will now update collection view."); updateCollectionView(); } @Override public void onLoaderReset(Loader<ArrayList<Movie>> loader) { } private final SharedPreferences.OnSharedPreferenceChangeListener mPrefChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sp, String key) { if (isAdded()) { if (PrefUtils.PREF_LOCAL_TIMES.equals(key)) { updateCollectionView(); } else if (PrefUtils.PREF_ATTENDEE_AT_VENUE.equals(key)) { if (mCursor != null) { reloadMovieData(true); } } } } }; private void updateCollectionView() { if (mMovieList.isEmpty()) { LOGD(TAG, "updateCollectionView: not ready yet... no iterator"); // not ready return; } LOGD(TAG, "MoviesFragment updating CollectionView... " + (mSessionDataIsFullReload ? "(FULL RELOAD)" : "(light refresh)")); mMaxDataIndexAnimated = 0; CollectionView.Inventory inv; if (!mMovieList.isEmpty()) { hideEmptyView(); inv = prepareInventory(); } else { showEmptyView(); inv = new CollectionView.Inventory(); } Parcelable state = null; if (!mSessionDataIsFullReload) { // it's not a full reload, so we want to keep scroll position, etc state = mCollectionView.onSaveInstanceState(); } mCollectionView.setCollectionAdapter(this); mCollectionView.updateInventory(inv, mSessionDataIsFullReload); if (state != null) { mCollectionView.onRestoreInstanceState(state); } mSessionDataIsFullReload = false; } private void hideEmptyView() { mEmptyView.setVisibility(View.GONE); mLoadingView.setVisibility(View.GONE); } private void showEmptyView() { // final String searchQuery = ScheduleContract.Sessions.isSearchUri(mCurrentUri) ? // ScheduleContract.Sessions.getSearchQuery(mCurrentUri) : null; mEmptyView.setText(""); mEmptyView.setVisibility(View.VISIBLE); mLoadingView.setVisibility(View.GONE); } // Creates the CollectionView groups based on the cursor data. private CollectionView.Inventory prepareInventory() { LOGD(TAG, "Preparing collection view inventory."); ArrayList<CollectionView.InventoryGroup> mainGroup = new ArrayList<CollectionView.InventoryGroup>(); HashMap<String, CollectionView.InventoryGroup> mainGroupName = new HashMap<String, CollectionView.InventoryGroup>(); int dataIndex = -1; final boolean expandedMode = true; int nextGroupId = HERO_GROUP_ID + 1000; final int displayCols = getResources().getInteger(R.integer.explore_2nd_level_grid_columns); mPreloader.setDisplayCols(displayCols); // ArrayList<CollectionView.InventoryGroup> list = mainGroup; // HashMap<String, CollectionView.InventoryGroup> map = mainGroupName; // // CollectionView.InventoryGroup group = // new CollectionView.InventoryGroup(0) // .setDisplayCols(displayCols); for (Movie movie : mMovieList) { ++dataIndex; String groupLabel; groupLabel = "Movies"; CollectionView.InventoryGroup group; // "list" and "map" are just shorthand variables pointing to the right list and map ArrayList<CollectionView.InventoryGroup> list = mainGroup; HashMap<String, CollectionView.InventoryGroup> map = mainGroupName; // Create group, if it doesn't exist yet if (!map.containsKey(groupLabel)) { LOGV(TAG, "Creating new group: " + groupLabel); group = new CollectionView.InventoryGroup(nextGroupId++).setDisplayCols(displayCols) .setShowHeader(!TextUtils.isEmpty(groupLabel)).setHeaderLabel(groupLabel); map.put(groupLabel, group); list.add(group); } else { LOGV(TAG, "Adding to existing group: " + groupLabel); group = map.get(groupLabel); } LOGV(TAG, "...adding to group '" + groupLabel + "' with custom data index " + dataIndex); group.addItemWithCustomDataIndex(dataIndex); } ArrayList<CollectionView.InventoryGroup> groups = new ArrayList<>(); groups.addAll(mainGroup); CollectionView.Inventory inventory = new CollectionView.Inventory(); for (CollectionView.InventoryGroup g : groups) { inventory.addGroup(g); } return inventory; } @Override public View newCollectionHeaderView(Context context, ViewGroup parent) { LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); return inflater.inflate(R.layout.list_item_explore_header, parent, false); } @Override public void bindCollectionHeaderView(Context context, View view, int groupId, String groupLabel) { TextView tv = (TextView) view.findViewById(android.R.id.text1); if (tv != null) { tv.setText(groupLabel); } } @Override public View newCollectionItemView(Context context, int groupId, ViewGroup parent) { final LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); int layoutId; layoutId = R.layout.list_item_movie; // if (useExpandedMode()) { // layoutId = R.layout.list_item_movie; // } else { // // Group HERO_GROUP_ID is the hero -- use a larger layout // layoutId = (groupId == HERO_GROUP_ID) ? R.layout.list_item_session_hero : // R.layout.list_item_session_summarized; // } return inflater.inflate(layoutId, parent, false); } private StringBuilder mBuffer = new StringBuilder(); private int mMaxDataIndexAnimated = 0; @Override public void bindCollectionItemView(Context context, View view, int groupId, int indexInGroup, int dataIndex, Object tag) { if (mMovieList.isEmpty() || mMovieList.get(dataIndex) == null) { return; } Movie movie = mMovieList.get(dataIndex); final String movieId = movie.id.toString(); if (movieId == null) return; final String movieTitle = movie.title; final String movieRating = movie.vote_average.toString(); final String movieBackdrop = movie.backdrop_path; System.out.println("Movie title in BindCollectionItemView: " + movieTitle); int movieColor = getResources().getColor(R.color.default_movie_color); int darkMovieColor = 0; final TextView titleView = (TextView) view.findViewById(R.id.movie_title); final View movieTargetView = view.findViewById(R.id.movie_target); if (movieColor == 0) { movieColor = mDefaultMovieColor; } darkMovieColor = UIUtils.scaleMovieColorToDefaultBG(movieColor); ImageView photoView = (ImageView) view.findViewById(R.id.session_photo_colored); if (photoView != null) { if (!mPreloader.isDimensSet()) { final ImageView finalPhotoView = photoView; photoView.post(new Runnable() { @Override public void run() { mPreloader.setDimens(finalPhotoView.getWidth(), finalPhotoView.getHeight()); } }); } // // colored filter on the images // photoView.setColorFilter(mNoTrackBranding // ? new PorterDuffColorFilter( // getResources().getColor(R.color.no_track_branding_session_tile_overlay), // PorterDuff.Mode.SRC_ATOP) // : UIUtils.makeSessionImageScrimColorFilter(darkMovieColor)); } else { photoView = (ImageView) view.findViewById(R.id.session_photo_colored); } ViewCompat.setTransitionName(photoView, "photo_" + movieId); // when we load a photo, it will fade in from transparent so the // background of the container must be the session color to avoid a white flash ViewParent parent = photoView.getParent(); if (parent != null && parent instanceof View) { ((View) parent).setBackgroundColor(darkMovieColor); } else { photoView.setBackgroundColor(darkMovieColor); } // render title //titleView.setTextColor(getResources().getColor(R.color.body_text_1_inverse)); //titleView.setTextColor(getResources().getColor(R.color.body_text_1_inverse)); //titleView.setBackgroundColor(getResources().getColor(R.color.material_blue_grey_800)); //titleView.setBackgroundColor(getResources().getColor(R.color.theme_primary)); titleView.setText(movieTitle == null ? "?" : movieTitle); //photoView.setColorFilter(new PorterDuffColorFilter(getResources().getColor(R.color.data_item_background_with_alpha),PorterDuff.Mode.SCREEN)); // set the images if (movieBackdrop != null) { mImageLoader.loadImage(Config.TMDB_IMAGE_BASE_URL + "w780" + movieBackdrop, photoView); } final View finalPhotoView = photoView; movieTargetView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mCallbacks.onMovieSelected(movieId, finalPhotoView); } }); // animate this card if (dataIndex > mMaxDataIndexAnimated) { mMaxDataIndexAnimated = dataIndex; } } /* private void setupLocalOrRemoteCard(final MessageCardView card) { card.setText(getString(R.string.question_local_or_remote)); card.setButton(0, getString(R.string.attending_remotely), CARD_ANSWER_ATTENDING_REMOTELY, false, 0); card.setButton(1, getString(R.string.attending_in_person), CARD_ANSWER_ATTENDING_IN_PERSON, true, 0); final Context context = getActivity().getApplicationContext(); final Activity activity = getActivity(); card.setListener(new MessageCardView.OnMessageCardButtonClicked() { @Override public void onMessageCardButtonClicked(final String tag) { final boolean inPerson = CARD_ANSWER_ATTENDING_IN_PERSON.equals(tag); card.dismiss(true); if (activity != null) { Toast.makeText(activity, inPerson ? R.string.explore_attending_in_person_toast : R.string.explore_attending_remotely_toast, Toast.LENGTH_LONG).show(); } // post delayed to give card time to animate mHandler.postDelayed(new Runnable() { @Override public void run() { PrefUtils.setAttendeeAtVenue(context, inPerson); PrefUtils.markAnsweredLocalOrRemote(context); } }, CARD_DISMISS_ACTION_DELAY); } }); card.show(); } */ private void animateSessionAppear(final View view) { } private class Preloader extends ListPreloader<String> { private int[] photoDimens; private int displayCols; public Preloader(int maxPreload) { super(maxPreload); } public void setDisplayCols(int displayCols) { this.displayCols = displayCols; } public boolean isDimensSet() { return photoDimens != null; } public void setDimens(int width, int height) { if (photoDimens == null) { photoDimens = new int[] { width, height }; } } @Override protected int[] getDimensions(String s) { return photoDimens; } @Override protected List<String> getItems(int start, int end) { // Our start and end are rows, we need to adjust them into data columns // The keynote is 1 row with 1 data item, so we need to adjust. int keynoteDataOffset = (displayCols - 1); int dataStart = start * displayCols - keynoteDataOffset; int dataEnd = end * displayCols - keynoteDataOffset; List<String> urls = new ArrayList<String>(); if (!mMovieList.isEmpty()) { for (Movie movie : mMovieList) { String backdrop; backdrop = movie.backdrop_path; //LOGD(TAG, "PRELOADER getItems: Backdrop = " + backdrop); //System.out.println("PRELOADER getItems: Backdrop = " + backdrop); if (backdrop != null) { urls.add(Config.TMDB_IMAGE_BASE_URL + "w780" + backdrop); } else { urls.add(null); } } } return urls; } @Override protected GenericRequestBuilder getRequestBuilder(String url) { return mImageLoader.beginImageLoad(url, null, true /*crop*/); } } private static class MovieListLoader extends AsyncTaskLoader<ArrayList<Movie>> { ArrayList<Movie> mMovies; Tmdb tmdb; String mSearchString; public MovieListLoader(Context context, String searchString) { super(context); mSearchString = searchString; tmdb = new Tmdb(); tmdb.setApiKey(Config.TMDB_API_KEY); } /** * This is where the bulk of our work is done. This function is * called in a background thread and should generate a new set of * data to be published by the loader. */ @Override public ArrayList<Movie> loadInBackground() { ArrayList<Movie> movieList = new ArrayList<>(); MoviesService moviesService = null; ResultsPage resultsPage = null; try { moviesService = tmdb.moviesService(); switch (this.getId()) { case Config.POPULAR_TOKEN: resultsPage = moviesService.popular(); break; case Config.NOW_PLAYING_TOKEN: resultsPage = moviesService.nowPlaying(); break; case Config.UPCOMING_TOKEN: resultsPage = moviesService.upcoming(); break; case Config.SEARCH_TOKEN: if (mSearchString != null) { resultsPage = tmdb.searchService().movie(mSearchString); } break; default: break; } if (resultsPage != null) { List<Movie> tempList = resultsPage.results; for (int i = 0; i < tempList.size(); i++) { if (!(tempList.get(i).adult)) { movieList.add(tempList.get(i)); } } } } catch (Exception e) { LOGE(TAG, "Network error"); } // Done! return movieList; } /** * Called when there is new data to deliver to the client. The * super class will take care of delivering it; the implementation * here just adds a little more logic. */ @Override public void deliverResult(ArrayList<Movie> movies) { if (isReset()) { // An async query came in while the loader is stopped. We // don't need the result. if (movies != null) { onReleaseResources(movies); } } ArrayList<Movie> oldMovies = mMovies; mMovies = movies; if (isStarted()) { // If the Loader is currently started, we can immediately // deliver its results. super.deliverResult(movies); } // At this point we can release the resources associated with // 'oldMovies' if needed; now that the new result is delivered we // know that it is no longer in use. if (oldMovies != null) { onReleaseResources(oldMovies); } } /** * Handles a request to start the Loader. */ @Override protected void onStartLoading() { if (mMovies != null) { // If we currently have a result available, deliver it // immediately. deliverResult(mMovies); } if (takeContentChanged() || mMovies == null) { // If the data has changed since the last time it was loaded // or is not currently available, start a load. forceLoad(); } } /** * Handles a request to stop the Loader. */ @Override protected void onStopLoading() { // Attempt to cancel the current load task if possible. cancelLoad(); } /** * Handles a request to cancel a load. */ @Override public void onCanceled(ArrayList<Movie> movies) { super.onCanceled(movies); // At this point we can release the resources associated with 'movies' // if needed. onReleaseResources(movies); } /** * Handles a request to completely reset the Loader. */ @Override protected void onReset() { super.onReset(); // Ensure the loader is stopped onStopLoading(); // At this point we can release the resources associated with 'movies' // if needed. if (mMovies != null) { onReleaseResources(mMovies); mMovies = null; } } /** * Helper function to take care of releasing resources associated * with an actively loaded data set. */ protected void onReleaseResources(ArrayList<Movie> movies) { // For a simple ArrayList<> there is nothing to do. For something // like a Cursor, we would close it here. } } //private static final int TAG_METADATA_TOKEN = 0x4; }