From ec67f27cd545bc1ddbb8266ac1551116d4cd82d3 Mon Sep 17 00:00:00 2001 From: Shashank Sinha Date: Tue, 5 Apr 2022 02:10:35 +0530 Subject: [PATCH] Setup persistence for NowPlayingFragment.java --- .../shashank/moviedb/data/local/MovieDao.java | 18 ++- .../moviedb/data/local/MovieDatabase.java | 6 +- .../data/remote/MovieRepositoryImpl.java | 116 +++++++++++++++--- .../shashank/moviedb/model/MovieResult.java | 1 + .../model/NowPlayingMovieIdsEntity.java | 25 ++++ .../moviedb/model/TrendingMovieIdsEntity.java | 25 ++++ .../ui/nowplaying/NowPlayingFragment.java | 2 +- .../com/shashank/moviedb/util/Constants.java | 4 + 8 files changed, 173 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/com/shashank/moviedb/model/NowPlayingMovieIdsEntity.java create mode 100644 app/src/main/java/com/shashank/moviedb/model/TrendingMovieIdsEntity.java diff --git a/app/src/main/java/com/shashank/moviedb/data/local/MovieDao.java b/app/src/main/java/com/shashank/moviedb/data/local/MovieDao.java index 7010620..c9cd9c6 100644 --- a/app/src/main/java/com/shashank/moviedb/data/local/MovieDao.java +++ b/app/src/main/java/com/shashank/moviedb/data/local/MovieDao.java @@ -7,6 +7,8 @@ import androidx.room.Query; import com.shashank.moviedb.model.MovieResult; +import com.shashank.moviedb.model.NowPlayingMovieIdsEntity; +import com.shashank.moviedb.model.TrendingMovieIdsEntity; import java.util.List; @@ -23,12 +25,22 @@ public interface MovieDao { void insertMovieResults(MovieResult... movieResults); @Insert(onConflict = OnConflictStrategy.REPLACE) - void insertMovieResult(MovieResult movieResult); + void insertTrendingMovieIds(TrendingMovieIdsEntity... trendingMovieIds); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + void insertNowPlayingMovieIds(NowPlayingMovieIdsEntity... trendingMovieIds); + @Query("SELECT * FROM movies") Flowable> getMovieResults(); - @Query("SELECT COUNT(*) FROM movies") - Flowable getMovieCount(); + @Query("SELECT * FROM trending_movie_ids") + Flowable> getTrendingMovieIds(); + + @Query("SELECT * FROM nowplaying_movie_ids") + Flowable> getNowPlayingMovieIds(); + + @Query("SELECT * FROM movies WHERE id IN (:movieIds)") + Flowable> getMovieResultsForMovieIds(List movieIds); } \ No newline at end of file diff --git a/app/src/main/java/com/shashank/moviedb/data/local/MovieDatabase.java b/app/src/main/java/com/shashank/moviedb/data/local/MovieDatabase.java index 9a86b39..6e46651 100644 --- a/app/src/main/java/com/shashank/moviedb/data/local/MovieDatabase.java +++ b/app/src/main/java/com/shashank/moviedb/data/local/MovieDatabase.java @@ -3,9 +3,13 @@ import androidx.room.Database; import androidx.room.RoomDatabase; import com.shashank.moviedb.model.MovieResult; +import com.shashank.moviedb.model.NowPlayingMovieIdsEntity; +import com.shashank.moviedb.model.TrendingMovieIdsEntity; @Database(entities = { - MovieResult.class + MovieResult.class, + TrendingMovieIdsEntity.class, + NowPlayingMovieIdsEntity.class }, version = 1, exportSchema = false) public abstract class MovieDatabase extends RoomDatabase { public abstract MovieDao getMovieDao(); diff --git a/app/src/main/java/com/shashank/moviedb/data/remote/MovieRepositoryImpl.java b/app/src/main/java/com/shashank/moviedb/data/remote/MovieRepositoryImpl.java index 1f43f59..3588019 100644 --- a/app/src/main/java/com/shashank/moviedb/data/remote/MovieRepositoryImpl.java +++ b/app/src/main/java/com/shashank/moviedb/data/remote/MovieRepositoryImpl.java @@ -1,5 +1,6 @@ package com.shashank.moviedb.data.remote; +import static com.shashank.moviedb.util.Constants.*; import static com.shashank.moviedb.util.Constants.TRENDING_TIME_WINDOW; import android.util.Log; @@ -9,13 +10,14 @@ import com.shashank.moviedb.data.ResourceCallback; import com.shashank.moviedb.data.Resource; import com.shashank.moviedb.data.local.MovieDao; -import com.shashank.moviedb.data.local.MovieDatabase; import com.shashank.moviedb.model.MovieDetail; import com.shashank.moviedb.model.MovieResponse; import com.shashank.moviedb.model.MovieResult; -import com.shashank.moviedb.util.Constants; +import com.shashank.moviedb.model.NowPlayingMovieIdsEntity; +import com.shashank.moviedb.model.TrendingMovieIdsEntity; import com.shashank.moviedb.util.Constants.QueryParams; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -24,7 +26,6 @@ import io.reactivex.Completable; import io.reactivex.CompletableObserver; import io.reactivex.Observer; -import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Action; import io.reactivex.functions.Consumer; @@ -50,8 +51,8 @@ public void fetchNowPlayingMovies(ResourceCallback resourceCallback) { movieApi.fetchNowPlayingMovie(QueryParams.PARAMS_NOW_PLAYING_MOVIE_API) .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .delay(Constants.DUMMY_NETWORK_DELAY, TimeUnit.MILLISECONDS) + //.observeOn(AndroidSchedulers.mainThread()) + .delay(DUMMY_NETWORK_DELAY, TimeUnit.MILLISECONDS) .subscribe(new Observer() { @Override public void onSubscribe(@NonNull Disposable d) { @@ -61,13 +62,17 @@ public void onSubscribe(@NonNull Disposable d) { @Override public void onNext(@NonNull MovieResponse movieResponse) { Log.d(TAG,"fetchNowPlayingMovies - onNext: movieResponse: "+movieResponse); - resourceCallback.onResponse(Resource.success(movieResponse)); + try { + processMovieResponse(movieResponse, resourceCallback, STATUS_NOW_PLAYING); + } catch (Exception e) { + resourceCallback.onResponse(Resource.error(e.getMessage(), null)); + } } @Override public void onError(@NonNull Throwable e) { Log.d(TAG, "fetchNowPlayingMovies - onError: e: "+e.getMessage()); - resourceCallback.onResponse(Resource.error(e.getMessage(), null)); + checkMovieDataInDatabase(resourceCallback, STATUS_NOW_PLAYING); } @Override @@ -85,7 +90,7 @@ public void fetchTrendingMovies(ResourceCallback resourceCallback) { movieApi.fetchTrendingMovie(TRENDING_TIME_WINDOW, QueryParams.PARAMS_TRENDING_MOVIE_API) .subscribeOn(Schedulers.io()) //.observeOn(AndroidSchedulers.mainThread()) - .delay(Constants.DUMMY_NETWORK_DELAY, TimeUnit.MILLISECONDS) + .delay(DUMMY_NETWORK_DELAY, TimeUnit.MILLISECONDS) .subscribe(new Observer() { @Override public void onSubscribe(@NonNull Disposable d) { @@ -95,15 +100,18 @@ public void onSubscribe(@NonNull Disposable d) { @Override public void onNext(@NonNull MovieResponse movieResponse) { Log.d(TAG,"fetchTrendingMovies - onNext: movieResponse: "+movieResponse); - //resourceCallback.onResponse(Resource.success(movieResponse)); - processMovieResponse(movieResponse, resourceCallback); + try { + processMovieResponse(movieResponse, resourceCallback, STATUS_TRENDING); + } catch (Exception e) { + resourceCallback.onResponse(Resource.error(e.getMessage(), null)); + } } @Override public void onError(@NonNull Throwable e) { Log.d(TAG, "xlr8: fetchTrendingMovies - onError: e: "+e.getMessage()); //resourceCallback.onResponse(Resource.error(e.getMessage(), null)); - checkDataInDatabase(resourceCallback); + checkMovieDataInDatabase(resourceCallback, STATUS_TRENDING); } @Override @@ -118,7 +126,7 @@ public void onComplete() { public void fetchMovieDetail(Long movieId, ResourceCallback resourceCallback) { if(movieId==null || movieId<=0L) { - resourceCallback.onResponse(Resource.error(Constants.INVALID_MOVIE_ID_ERROR_MSG,null)); + resourceCallback.onResponse(Resource.error(INVALID_MOVIE_ID_ERROR_MSG,null)); return; } @@ -151,9 +159,10 @@ public void onComplete() { } + /********************************************** PRIVATE HELPER FUNCTIONS *****************************************************************************/ // Save remote data in room db, read data from db and then pass it to callback - private void processMovieResponse(MovieResponse movieResponse, ResourceCallback callback) { + private void processMovieResponse(MovieResponse movieResponse, ResourceCallback callback, int movieStatus) throws Exception { Log.d(TAG, "xlr8: processMovieResponse called: movieResponse: "+movieResponse); if(movieResponse==null || movieResponse.getResults()==null || movieResponse.getResults().isEmpty()) { @@ -163,13 +172,18 @@ private void processMovieResponse(MovieResponse movieResponse, ResourceCallback List movies = movieResponse.getResults(); - // Insert movies data in database + // step 1- Insert movies data in database Completable.fromAction(new Action() { @Override public void run() throws Exception { + + // Step 1.1- Insert movie ids into respective Entity + insertMovieIdsIntoRespectiveEntity(movies, movieStatus); + + // Step 1.2- Insert MovieResult in main movie table movieDao.insertMovieResults(movies.toArray(new MovieResult[movies.size()])); } - }).subscribeOn(Schedulers.io()) + }).subscribeOn(Schedulers.computation()) .subscribe(new CompletableObserver() { @Override public void onSubscribe(@NonNull Disposable d) { @@ -180,8 +194,9 @@ public void onSubscribe(@NonNull Disposable d) { public void onComplete() { Log.d(TAG, "xlr8: processMovieResponse: onComplete"); // Movies inserted in DB Successfully - // Fetch movies data from DB and pass it to callback - checkDataInDatabase(callback); + + // Step 2- Fetch movies data from DB and pass it to UI + checkMovieDataInDatabase(callback, movieStatus); } @Override @@ -192,11 +207,74 @@ public void onError(@NonNull Throwable e) { }); } + private void insertMovieIdsIntoRespectiveEntity(List movies, int movieStatus) { + if(movieStatus== STATUS_NOW_PLAYING) { + List nowPlayingMovieIds = new ArrayList<>(); + for(MovieResult movie: movies) { + nowPlayingMovieIds.add(new NowPlayingMovieIdsEntity(movie.getId())); + } + movieDao.insertNowPlayingMovieIds(nowPlayingMovieIds.toArray(new NowPlayingMovieIdsEntity[nowPlayingMovieIds.size()])); - private void checkDataInDatabase(ResourceCallback callback) { + } else if(movieStatus== STATUS_TRENDING) { + List trendingMovieIds = new ArrayList<>(); + for(MovieResult movie: movies) { + trendingMovieIds.add(new TrendingMovieIdsEntity(movie.getId())); + } + movieDao.insertTrendingMovieIds(trendingMovieIds.toArray(new TrendingMovieIdsEntity[trendingMovieIds.size()])); + } + } + + // Check data in DB, if present - pass via callback + private void checkMovieDataInDatabase(ResourceCallback callback, int movieStatus) { Log.d(TAG, "xlr8 : checkDataInDatabase called"); + + // Step 2.1 - Get list of movie ids from respective movie status table + if(movieStatus==STATUS_TRENDING) { + movieDao.getTrendingMovieIds().subscribeOn(Schedulers.io()) + .subscribe(new Consumer>() { + @Override + public void accept(List trendingMovieIdsEntities) throws Exception { + if(trendingMovieIdsEntities!=null && !trendingMovieIdsEntities.isEmpty()) { + List trendingMovieIds = new ArrayList<>(); + for(TrendingMovieIdsEntity trendingMovieIdsEntity: trendingMovieIdsEntities) { + trendingMovieIds.add(trendingMovieIdsEntity.getMovieId()); + } + + // Step 2.2- fetch movie of these ids only + checkMovieDataInDatabaseForIds(callback, trendingMovieIds); + } else { + callback.onResponse(Resource.error("Error/Empty response while querying DB", null)); + } + } + }); + + } + // Step 2.1 - Get list of movie ids from respective movie status table + else if(movieStatus==STATUS_NOW_PLAYING) { + movieDao.getNowPlayingMovieIds().subscribeOn(Schedulers.io()) + .subscribe(new Consumer>() { + @Override + public void accept(List nowPlayingMovieIdsEntities) throws Exception { + if(nowPlayingMovieIdsEntities!=null && !nowPlayingMovieIdsEntities.isEmpty()) { + List nowPlayingMovieIds = new ArrayList<>(); + for(NowPlayingMovieIdsEntity nowPlayingMovieIdsEntity: nowPlayingMovieIdsEntities) { + nowPlayingMovieIds.add(nowPlayingMovieIdsEntity.getMovieId()); + } + + // Step 2.2- fetch movie of these ids only + checkMovieDataInDatabaseForIds(callback, nowPlayingMovieIds); + } else { + callback.onResponse(Resource.error("Error/Empty response while querying DB", null)); + } + } + }); + } + + } + + private void checkMovieDataInDatabaseForIds(ResourceCallback callback, List movieIds) { // Fetch movies data from DB - movieDao.getMovieResults().subscribeOn(Schedulers.io()) + movieDao.getMovieResultsForMovieIds(movieIds).subscribeOn(Schedulers.io()) .subscribe(new Consumer>() { @Override public void accept(List movieResults) throws Exception { diff --git a/app/src/main/java/com/shashank/moviedb/model/MovieResult.java b/app/src/main/java/com/shashank/moviedb/model/MovieResult.java index bbdeff6..6e5cb70 100644 --- a/app/src/main/java/com/shashank/moviedb/model/MovieResult.java +++ b/app/src/main/java/com/shashank/moviedb/model/MovieResult.java @@ -29,6 +29,7 @@ public class MovieResult { @ColumnInfo(name = "vote_average") @Nullable @SerializedName("vote_average") private Double voteAverage; + public MovieResult() { } public MovieResult(@NonNull Long id, @NonNull String originalTitle, @NonNull String title, @Nullable String posterPath, diff --git a/app/src/main/java/com/shashank/moviedb/model/NowPlayingMovieIdsEntity.java b/app/src/main/java/com/shashank/moviedb/model/NowPlayingMovieIdsEntity.java new file mode 100644 index 0000000..f3f16a3 --- /dev/null +++ b/app/src/main/java/com/shashank/moviedb/model/NowPlayingMovieIdsEntity.java @@ -0,0 +1,25 @@ +package com.shashank.moviedb.model; + +import androidx.room.ColumnInfo; +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +@Entity(tableName = "nowplaying_movie_ids") +public class NowPlayingMovieIdsEntity { + + @PrimaryKey(autoGenerate = false) + @ColumnInfo(name = "movie_id") + private Long movieId; + + public NowPlayingMovieIdsEntity(Long movieId) { + this.movieId = movieId; + } + + public Long getMovieId() { + return movieId; + } + + public void setMovieId(Long movieId) { + this.movieId = movieId; + } +} diff --git a/app/src/main/java/com/shashank/moviedb/model/TrendingMovieIdsEntity.java b/app/src/main/java/com/shashank/moviedb/model/TrendingMovieIdsEntity.java new file mode 100644 index 0000000..afbd5a3 --- /dev/null +++ b/app/src/main/java/com/shashank/moviedb/model/TrendingMovieIdsEntity.java @@ -0,0 +1,25 @@ +package com.shashank.moviedb.model; + +import androidx.room.ColumnInfo; +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +@Entity(tableName = "trending_movie_ids") +public class TrendingMovieIdsEntity { + + @PrimaryKey(autoGenerate = false) + @ColumnInfo(name = "movie_id") + private Long movieId; + + public TrendingMovieIdsEntity(Long movieId) { + this.movieId = movieId; + } + + public Long getMovieId() { + return movieId; + } + + public void setMovieId(Long movieId) { + this.movieId = movieId; + } +} diff --git a/app/src/main/java/com/shashank/moviedb/ui/nowplaying/NowPlayingFragment.java b/app/src/main/java/com/shashank/moviedb/ui/nowplaying/NowPlayingFragment.java index 822fbcf..add59fc 100644 --- a/app/src/main/java/com/shashank/moviedb/ui/nowplaying/NowPlayingFragment.java +++ b/app/src/main/java/com/shashank/moviedb/ui/nowplaying/NowPlayingFragment.java @@ -88,7 +88,7 @@ public void onChanged(Resource> listResource) { swipeRefreshLayout.setRefreshing(false); movieRecyclerView.setVisibility(View.VISIBLE); noInternetView.update(Status.SUCCESS, null); - List movies = ((MovieResponse)listResource.getData()).getResults(); + List movies = listResource.getData(); if(movies!=null && !movies.isEmpty()){ movieRecyclerAdapter.setMovies(movies); } diff --git a/app/src/main/java/com/shashank/moviedb/util/Constants.java b/app/src/main/java/com/shashank/moviedb/util/Constants.java index 46b47a4..bcd77fa 100644 --- a/app/src/main/java/com/shashank/moviedb/util/Constants.java +++ b/app/src/main/java/com/shashank/moviedb/util/Constants.java @@ -22,6 +22,10 @@ public abstract class Constants { public static final Long DUMMY_NETWORK_DELAY = 1500L; + public static final int STATUS_TRENDING = 0; + public static final int STATUS_NOW_PLAYING = 1; + +