mx.com.adolfogarcia.popularmovies.view.fragment.MovieDetailFragment.java Source code

Java tutorial

Introduction

Here is the source code for mx.com.adolfogarcia.popularmovies.view.fragment.MovieDetailFragment.java

Source

/*
 * Copyright 2015 Jess Adolfo Garca Pasquel
 *
 * 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 mx.com.adolfogarcia.popularmovies.view.fragment;

import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.ShareActionProvider;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;

import org.parceler.Parcels;

import mx.com.adolfogarcia.popularmovies.PopularMoviesApplication;
import mx.com.adolfogarcia.popularmovies.R;
import mx.com.adolfogarcia.popularmovies.databinding.MovieDetailFragmentBinding;
import mx.com.adolfogarcia.popularmovies.model.domain.Movie;
import mx.com.adolfogarcia.popularmovies.model.domain.Trailer;
import mx.com.adolfogarcia.popularmovies.model.view.MovieDetailViewModel;

import static mx.com.adolfogarcia.popularmovies.data.MovieContract.CachedMovieEntry;
import static mx.com.adolfogarcia.popularmovies.model.view.MovieDetailViewModel.MovieDetailQuery;
import static mx.com.adolfogarcia.popularmovies.model.view.MovieDetailViewModel.MovieTrailerQuery;
import static mx.com.adolfogarcia.popularmovies.model.view.MovieDetailViewModel.MovieReviewQuery;

/**
 * Displays detailed information for a given {@link Movie}. New instances of
 * this class must be created with the factory method
 * {@link #newInstance(Movie)}.
 *
 * @author Jess Adolfo Garca Pasquel
 */
public class MovieDetailFragment extends Fragment {

    /**
     * Identifies the messages written to the log by this class.
     */
    private static final String LOG_TAG = MovieDetailFragment.class.getSimpleName();

    /**
     * The MIME type for plain text.
     */
    private static final String PLAIN_TEXT_MEDIA_TYPE = "text/plain";

    /**
     * Identifies the {@link Loader} that retrieves the movie details cached in
     * the local database.
     */
    private static final int MOVIE_DETAIL_LOADER_ID = 532232;

    /**
     * Identifies the {@link Loader} that retrieves the trailer videos cached in
     * the local database for the movie specified to the {@code Fragment}.
     */
    private static final int MOVIE_TRAILER_LOADER_ID = 244185;

    /**
     * Identifies the {@link Loader} that retrieves the reviews cached in
     * the local database for the movie specified to the {@code Fragment}.
     */
    private static final int MOVIE_REVIEW_LOADER_ID = 950426;

    /**
     * Key used to access the {@link Movie} specified as argument at creation
     * time.
     * @see #newInstance(Movie)
     */
    private static final String ARG_MOVIE = "arg_movie";

    /**
     * Key used to save and retrieve the serialized {@link #mViewModel}.
     */
    private static final String STATE_VIEW_MODEL = "state_view_model";

    /**
     * Provides data and behaviour to the {@link MovieDetailFragment}.
     */
    private MovieDetailViewModel mViewModel;

    /**
     * Binds the view to the view model.
     * @see MovieDetailViewModel
     */
    private MovieDetailFragmentBinding mBinding = null;

    /**
     * Lets the user share the first movie trailer, if available.
     */
    private ShareActionProvider mShareActionProvider = null;

    /**
     * Creates a new instance of {@link MovieDetailFragment} for the specified
     * movie. You must use this factory method to create new instances.
     *
     * @param movie the {@link Movie} for which the details will be displayed.
     * @return A new instance of {@link MovieDetailFragment}.
     */
    public static MovieDetailFragment newInstance(Movie movie) {
        Bundle args = new Bundle();
        args.putParcelable(ARG_MOVIE, Parcels.wrap(movie));
        MovieDetailFragment fragment = new MovieDetailFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setHasOptionsMenu(true);
        if (getArguments() == null) {
            throw new IllegalStateException("No movie specified as Fragment argument.");
        }
        this.restoreState(savedInstanceState);
    }

    /**
     * Returns a new {@link MovieDetailViewModel} based on the movie data passed
     * in the {@link Fragment}'s arguments.
     *
     * @return a new {@link MovieDetailViewModel} based on the movie data passed
     *     in the {@link Fragment}'s arguments.
     */
    private MovieDetailViewModel newViewModel() {
        Movie movie = Parcels.unwrap(getArguments().getParcelable(ARG_MOVIE));
        MovieDetailViewModel viewModel = new MovieDetailViewModel();
        ((PopularMoviesApplication) getActivity().getApplication()).getComponent().inject(viewModel);
        viewModel.setMovie(movie);
        return viewModel;
    }

    /**
     * Loads the previous state, stored in the {@link Bundle} passed as argument,
     * into to {@link MovieCollectionFragment}. {@link #mViewModel} in particular.
     * If the argument is {@code null}, nothing is done.
     *
     * @param savedInstanceState the {@link MovieCollectionFragment}'s previous
     *                           state.
     */
    private void restoreState(Bundle savedInstanceState) {
        if (savedInstanceState == null) {
            return;
        }
        mViewModel = Parcels.unwrap(savedInstanceState.getParcelable(STATE_VIEW_MODEL));
        ((PopularMoviesApplication) getActivity().getApplication()).getComponent().inject(mViewModel);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putParcelable(STATE_VIEW_MODEL, Parcels.wrap(mViewModel));
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        getLoaderManager().initLoader(MOVIE_DETAIL_LOADER_ID, null, new MovieLoaderCallbacks());
        getLoaderManager().initLoader(MOVIE_TRAILER_LOADER_ID, null, new TrailerLoaderCallbacks());
        getLoaderManager().initLoader(MOVIE_REVIEW_LOADER_ID, null, new ReviewLoaderCallbacks());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_movie_detail, container, false);
        if (mViewModel == null) {
            mViewModel = newViewModel();
        }
        mBinding.setViewModel(mViewModel);
        // TODO: Bind onClick with Data Binding library. At this moment, it breaks compilation
        mBinding.checkboxFavorite.setOnClickListener((v) -> mViewModel.onClickFavorite((CheckBox) v));
        return mBinding.getRoot();
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.menu_fragment_movie_detail, menu);
        MenuItem item = menu.findItem(R.id.menu_item_share);
        mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(item);
        mShareActionProvider.setShareIntent(getShareFirstTrailerIntent());
    }

    /**
     * Returns an {@link Intent} that can be used to share the first movie
     * trailer or {@code null} if there are no trailers (or there is no app on
     * the device that may be used to share).
     *
     * @return an {@link Intent} that can be used to share the first movie
     *     trailer or {@code null} if one is not available.
     */
    private Intent getShareFirstTrailerIntent() {
        if (mViewModel == null || mViewModel.getTrailers().isEmpty()) {
            return null;
        }
        Trailer firstTrailer = mViewModel.getTrailers().get(0);
        Context context = getActivity();
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_SEND);
        intent.putExtra(Intent.EXTRA_TEXT, firstTrailer.getVideoUri().toString());
        intent.setType(PLAIN_TEXT_MEDIA_TYPE);
        if (intent.resolveActivity(context.getPackageManager()) == null) {
            Log.w(LOG_TAG, "Unable to create share intent. No application available to share.");
            intent = null;
        }
        return intent;
    }

    /**
     * Handles the callbacks for the {@link Loader} that retrieves the movie's
     * details from the {@code ContentProvider}. When loading is finished,
     * sets them on the {@link MovieDetailFragment#mViewModel} of the associated
     * {@link MovieDetailFragment}.
     */
    private class MovieLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {

        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            return new CursorLoader(MovieDetailFragment.this.getActivity(),
                    CachedMovieEntry.buildMovieUri(mViewModel.getMovie().getId()), MovieDetailQuery.PROJECTION,
                    null, null, null);
        }

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            mViewModel.setMovieData(data);
        }

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
            mViewModel.setMovieData(null);
        }
    }

    /**
     * Handles the callbacks for the {@link Loader} that retrieves the movie's
     * trailer videos from the {@code ContentProvider}. When loading is finished,
     * sets them on the {@link MovieDetailFragment#mViewModel} of the associated
     * {@link MovieDetailFragment}.
     */
    private class TrailerLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {

        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            return new CursorLoader(MovieDetailFragment.this.getActivity()
            // TODO: Load only trailers and from YouTube - use query parameters
                    , CachedMovieEntry.buildMovieVideosUri(mViewModel.getMovie().getId()),
                    MovieTrailerQuery.PROJECTION, null, null, MovieTrailerQuery.SORT_ORDER);
        }

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            mViewModel.setMovieTrailerData(data);
            // Set the share intent, if the provider has already been loaded
            if (mShareActionProvider != null) {
                mShareActionProvider.setShareIntent(getShareFirstTrailerIntent());
            }
        }

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
            mViewModel.setMovieTrailerData(null);
        }
    }

    /**
     * Handles the callbacks for the {@link Loader} that retrieves the movie's
     * reviews from the {@code ContentProvider}. When loading is finished,
     * sets them on the {@link MovieDetailFragment#mViewModel} of the associated
     * {@link MovieDetailFragment}.
     */
    private class ReviewLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {

        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            return new CursorLoader(MovieDetailFragment.this.getActivity(),
                    CachedMovieEntry.buildMovieReviewsUri(mViewModel.getMovie().getId()),
                    MovieReviewQuery.PROJECTION, null, null, MovieReviewQuery.SORT_ORDER);
        }

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            mViewModel.setMovieReviewData(data);
        }

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
            mViewModel.setMovieReviewData(null);
        }
    }

}