Java tutorial
/* * Copyright 2016. Dmitry Malkovich * * 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 com.dmitrymalkovich.android.popularmoviesapp; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.database.Cursor; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import com.dmitrymalkovich.android.popularmoviesapp.data.MovieContract; import com.dmitrymalkovich.android.popularmoviesapp.network.Movie; import com.dmitrymalkovich.android.popularmoviesapp.details.MovieDetailActivity; import com.dmitrymalkovich.android.popularmoviesapp.details.MovieDetailFragment; import java.util.ArrayList; import java.util.List; import butterknife.Bind; import butterknife.ButterKnife; /** * An activity representing a grid of Movies. This activity * has different presentations for handset and tablet-size devices. */ public class MovieListActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor>, FetchMoviesTask.Listener, MovieListAdapter.Callbacks { private static final String EXTRA_MOVIES = "EXTRA_MOVIES"; private static final String EXTRA_SORT_BY = "EXTRA_SORT_BY"; private static final int FAVORITE_MOVIES_LOADER = 0; /** * Whether or not the activity is in two-pane mode, i.e. running on a tablet * device. */ private boolean mTwoPane; private RetainedFragment mRetainedFragment; private MovieListAdapter mAdapter; private String mSortBy = FetchMoviesTask.MOST_POPULAR; @Bind(R.id.movie_list) RecyclerView mRecyclerView; @Bind(R.id.toolbar) Toolbar mToolbar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_movie_list); ButterKnife.bind(this); mToolbar.setTitle(R.string.title_movie_list); setSupportActionBar(mToolbar); String tag = RetainedFragment.class.getName(); this.mRetainedFragment = (RetainedFragment) getSupportFragmentManager().findFragmentByTag(tag); if (this.mRetainedFragment == null) { this.mRetainedFragment = new RetainedFragment(); getSupportFragmentManager().beginTransaction().add(this.mRetainedFragment, tag).commit(); } mRecyclerView.setLayoutManager( new GridLayoutManager(this, getResources().getInteger(R.integer.grid_number_cols))); // To avoid "E/RecyclerView: No adapter attached; skipping layout" mAdapter = new MovieListAdapter(new ArrayList<Movie>(), this); mRecyclerView.setAdapter(mAdapter); // For large-screen layouts (res/values-w900dp). mTwoPane = findViewById(R.id.movie_detail_container) != null; if (savedInstanceState != null) { mSortBy = savedInstanceState.getString(EXTRA_SORT_BY); if (savedInstanceState.containsKey(EXTRA_MOVIES)) { List<Movie> movies = savedInstanceState.getParcelableArrayList(EXTRA_MOVIES); mAdapter.add(movies); findViewById(R.id.progress).setVisibility(View.GONE); // For listening content updates for tow pane mode if (mSortBy.equals(FetchMoviesTask.FAVORITES)) { getSupportLoaderManager().initLoader(FAVORITE_MOVIES_LOADER, null, this); } } updateEmptyState(); } else { // Fetch Movies only if savedInstanceState == null fetchMovies(mSortBy); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); ArrayList<Movie> movies = mAdapter.getMovies(); if (movies != null && !movies.isEmpty()) { outState.putParcelableArrayList(EXTRA_MOVIES, movies); } outState.putString(EXTRA_SORT_BY, mSortBy); // Needed to avoid confusion, when we back from detail screen (i. e. top rated selected but // favorite movies are shown and onCreate was not called in this case). if (!mSortBy.equals(FetchMoviesTask.FAVORITES)) { getSupportLoaderManager().destroyLoader(FAVORITE_MOVIES_LOADER); } } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.movie_list_activity, menu); switch (mSortBy) { case FetchMoviesTask.MOST_POPULAR: menu.findItem(R.id.sort_by_most_popular).setChecked(true); break; case FetchMoviesTask.TOP_RATED: menu.findItem(R.id.sort_by_top_rated).setChecked(true); break; case FetchMoviesTask.FAVORITES: menu.findItem(R.id.sort_by_favorites).setChecked(true); break; } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.sort_by_top_rated: if (mSortBy.equals(FetchMoviesTask.FAVORITES)) { getSupportLoaderManager().destroyLoader(FAVORITE_MOVIES_LOADER); } mSortBy = FetchMoviesTask.TOP_RATED; fetchMovies(mSortBy); item.setChecked(true); break; case R.id.sort_by_most_popular: if (mSortBy.equals(FetchMoviesTask.FAVORITES)) { getSupportLoaderManager().destroyLoader(FAVORITE_MOVIES_LOADER); } mSortBy = FetchMoviesTask.MOST_POPULAR; fetchMovies(mSortBy); item.setChecked(true); break; case R.id.sort_by_favorites: mSortBy = FetchMoviesTask.FAVORITES; item.setChecked(true); fetchMovies(mSortBy); default: break; } return super.onOptionsItemSelected(item); } @Override public void open(Movie movie, int position) { if (mTwoPane) { Bundle arguments = new Bundle(); arguments.putParcelable(MovieDetailFragment.ARG_MOVIE, movie); MovieDetailFragment fragment = new MovieDetailFragment(); fragment.setArguments(arguments); getSupportFragmentManager().beginTransaction().replace(R.id.movie_detail_container, fragment).commit(); } else { Intent intent = new Intent(this, MovieDetailActivity.class); intent.putExtra(MovieDetailFragment.ARG_MOVIE, movie); startActivity(intent); } } @Override public void onFetchFinished(Command command) { if (command instanceof FetchMoviesTask.NotifyAboutTaskCompletionCommand) { mAdapter.add(((FetchMoviesTask.NotifyAboutTaskCompletionCommand) command).getMovies()); updateEmptyState(); findViewById(R.id.progress).setVisibility(View.GONE); } } @Override public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) { mAdapter.add(cursor); updateEmptyState(); findViewById(R.id.progress).setVisibility(View.GONE); } @Override public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { findViewById(R.id.progress).setVisibility(View.VISIBLE); return new CursorLoader(this, MovieContract.MovieEntry.CONTENT_URI, MovieContract.MovieEntry.MOVIE_COLUMNS, null, null, null); } @Override public void onLoaderReset(Loader<Cursor> cursorLoader) { // Not used } private void fetchMovies(String sortBy) { if (!sortBy.equals(FetchMoviesTask.FAVORITES)) { findViewById(R.id.progress).setVisibility(View.VISIBLE); FetchMoviesTask.NotifyAboutTaskCompletionCommand command = new FetchMoviesTask.NotifyAboutTaskCompletionCommand( this.mRetainedFragment); new FetchMoviesTask(sortBy, command).execute(); } else { getSupportLoaderManager().initLoader(FAVORITE_MOVIES_LOADER, null, this); } } private void updateEmptyState() { if (mAdapter.getItemCount() == 0) { if (mSortBy.equals(FetchMoviesTask.FAVORITES)) { findViewById(R.id.empty_state_container).setVisibility(View.GONE); findViewById(R.id.empty_state_favorites_container).setVisibility(View.VISIBLE); } else { findViewById(R.id.empty_state_container).setVisibility(View.VISIBLE); findViewById(R.id.empty_state_favorites_container).setVisibility(View.GONE); } } else { findViewById(R.id.empty_state_container).setVisibility(View.GONE); findViewById(R.id.empty_state_favorites_container).setVisibility(View.GONE); } } /** * RetainedFragment with saving state mechanism. * The saving state mechanism helps to not lose user's progress even when app is in the * background state or user rotate device and also to avoid performing code which * will lead to "java.lang.IllegalStateException: Can not perform some actions after * onSaveInstanceState". As the result we have commands which we cannot execute now, * but we have to store it and execute later. * * @see com.dmitrymalkovich.android.popularmoviesapp.FetchMoviesTask.NotifyAboutTaskCompletionCommand */ public static class RetainedFragment extends Fragment implements FetchMoviesTask.Listener { private boolean mPaused = false; // Currently allow to wait one command, because more is not needed. In future it can be // extended to list etc. Using "MacroCommand" which contain includes other commands as waiting command. private Command mWaitingCommand = null; public RetainedFragment() { } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } @Override public void onPause() { super.onPause(); mPaused = true; } @Override public void onResume() { super.onResume(); mPaused = false; if (mWaitingCommand != null) { onFetchFinished(mWaitingCommand); } } @Override public void onFetchFinished(Command command) { if (getActivity() instanceof FetchMoviesTask.Listener && !mPaused) { FetchMoviesTask.Listener listener = (FetchMoviesTask.Listener) getActivity(); listener.onFetchFinished(command); mWaitingCommand = null; } else { // Save the command for later. mWaitingCommand = command; } } } }