Skip to content

Commit

Permalink
Merge pull request #41 from novoda/movies/arch-components/lifecycle
Browse files Browse the repository at this point in the history
Architecture Components refactor: LiveData & ViewModel
  • Loading branch information
Xavi Rigau committed Jun 7, 2018
2 parents 949ad50 + 2070478 commit ba69cdb
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 111 deletions.
2 changes: 2 additions & 0 deletions MoviesExample/app/build.gradle
Expand Up @@ -24,6 +24,8 @@ dependencies {
implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.0'

implementation 'android.arch.lifecycle:extensions:1.1.1'
annotationProcessor 'android.arch.lifecycle:compiler:1.1.1'
implementation 'com.github.bumptech.glide:glide:4.7.1'

implementation 'com.jakewharton:butterknife:8.5.1'
Expand Down
@@ -1,12 +1,14 @@
package com.novoda.demo.movies;

import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;

import com.novoda.demo.movies.model.Movie;
import com.novoda.demo.movies.model.Video;
Expand All @@ -16,12 +18,11 @@

public class MainActivity extends AppCompatActivity {

private MovieService movieService;
private MoviesAdapter adapter;

@BindView(R.id.movies_list)
RecyclerView resultList;
private MovieService.Callback callback;
private MoviesViewModel moviesViewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
Expand All @@ -30,7 +31,8 @@ protected void onCreate(Bundle savedInstanceState) {
ButterKnife.bind(this);
setTitle("Top Rated Movies");

movieService = ((MoviesApplication) getApplication()).movieService();
MovieService movieService = ((MoviesApplication) getApplication()).movieService();
moviesViewModel = ViewModelProviders.of(this, new MoviesViewModelFactory(movieService)).get(MoviesViewModel.class);

resultList.setLayoutManager(new LinearLayoutManager(this));

Expand All @@ -42,46 +44,25 @@ public void onMovieSelected(final Movie movie) {

@Override
public void onPageLoadRequested(final int page) {
movieService.loadMore();
moviesViewModel.loadMore();
}
});
resultList.setAdapter(adapter);
callback = new MovieService.Callback() {
@Override
public void onNewData(MoviesSate moviesSate) {
adapter.setMoviesSate(moviesSate);
}

moviesViewModel.moviesLiveData().observe(this, new Observer<MoviesSate>() {
@Override
public void onFailure(Throwable e) {
Log.e("Movies", "while loading movies", e);
public void onChanged(MoviesSate moviesSate) {
adapter.setMoviesSate(moviesSate);
}
};
}

@Override
protected void onStart() {
super.onStart();
movieService.subscribe(callback);
}

@Override
protected void onStop() {
super.onStop();
movieService.unsubscribe(callback);
});
}

private void startLoadingTrailer(Movie movie) {
movieService.loadTrailerFor(movie, new MovieService.TrailerCallback() {
moviesViewModel.loadTrailerFor(movie).observe(this, new Observer<Video>() {
@Override
public void onTrailerLoaded(Video video) {
public void onChanged(@Nullable Video video) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(video.trailerUrl())));
}

@Override
public void onFailure(Throwable e) {
Log.e("Movies", "while loading videos", e);
}
});
}
}
@@ -1,107 +1,64 @@
package com.novoda.demo.movies;

import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.util.Log;

import com.novoda.demo.movies.api.MoviesApi;
import com.novoda.demo.movies.api.MoviesResponse;
import com.novoda.demo.movies.api.VideosResponse;
import com.novoda.demo.movies.model.Movie;
import com.novoda.demo.movies.model.Video;

import java.util.ArrayList;
import java.util.List;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import rx.Observable;
import rx.Observer;
import rx.functions.Func1;
import rx.schedulers.Schedulers;

public class MovieService {

private final MoviesApi api;
private MoviesSate moviesSate = new MoviesSate(new ArrayList<Movie>(), 1);

private Callback callback;
private final MutableLiveData<List<Movie>> moviesLiveData = new MutableLiveData<>();
private final MutableLiveData<List<Video>> videosLiveData = new MutableLiveData<>();

public MovieService(MoviesApi api) {
this.api = api;
}

public void subscribe(Callback callback) {
this.callback = callback;
callback.onNewData(moviesSate);
if (moviesSate.isEmpty()) {
loadMore();
}
}

public void unsubscribe(Callback callback) {
this.callback = null;
}

public void loadMore() {
api.topRated(moviesSate.pageNumber()).enqueue(new retrofit2.Callback<MoviesResponse>() {
public LiveData<List<Movie>> loadMore(int page) {
api.topRated(page).enqueue(new Callback<MoviesResponse>() {
@Override
public void onResponse(Call<MoviesResponse> call, Response<MoviesResponse> response) {
if (response == null || response.body() == null || response.body().results == null) {
return;
}
List<Movie> movies = moviesSate.movies();
movies.addAll(response.body().results);
moviesSate = new MoviesSate(movies, moviesSate.pageNumber() + 1);
callback.onNewData(moviesSate);
moviesLiveData.postValue(response.body().results);
}

@Override
public void onFailure(Call<MoviesResponse> call, Throwable throwable) {
callback.onFailure(throwable);
public void onFailure(Call<MoviesResponse> call, Throwable e) {
Log.e("Movies", "while loading movies", e);
}
});
return moviesLiveData;
}

public void loadTrailerFor(Movie movie, final TrailerCallback trailerCallback) {
api.videos(movie.id)
.flatMapObservable(new Func1<VideosResponse, Observable<Video>>() {
@Override
public Observable<Video> call(final VideosResponse videosResponse) {
return Observable.from(videosResponse.results);
}
})
.takeFirst(new Func1<Video, Boolean>() {
@Override
public Boolean call(final Video video) {
return video.trailerUrl() != null;
}
})
.subscribeOn(Schedulers.io())
.subscribe(new Observer<Video>() {
@Override
public void onCompleted() {
// noop
}

@Override
public void onError(final Throwable e) {
trailerCallback.onFailure(e);
}

@Override
public void onNext(final Video video) {
trailerCallback.onTrailerLoaded(video);
}
});
}

interface Callback {
void onNewData(MoviesSate moviesSate);

void onFailure(Throwable e);
}

interface TrailerCallback {
void onTrailerLoaded(Video video);
public LiveData<List<Video>> loadTrailerFor(Movie movie) {
api.videos(movie.id).enqueue(new Callback<VideosResponse>() {
@Override
public void onResponse(Call<VideosResponse> call, Response<VideosResponse> response) {
if (response == null || response.body() == null || response.body().results == null) {
return;
}
videosLiveData.postValue(response.body().results);
}

void onFailure(Throwable e);
@Override
public void onFailure(Call<VideosResponse> call, Throwable e) {
Log.e("Movies", "while loading videos", e);
}
});
return videosLiveData;
}

}
Expand Up @@ -2,9 +2,6 @@

import android.app.Application;

import com.novoda.demo.movies.Dependencies;
import com.novoda.demo.movies.MovieService;

public class MoviesApplication extends Application {

private MovieService movieService;
Expand Down
@@ -0,0 +1,51 @@
package com.novoda.demo.movies;

import android.arch.core.util.Function;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.Transformations;
import android.arch.lifecycle.ViewModel;

import com.novoda.demo.movies.model.Movie;
import com.novoda.demo.movies.model.Video;

import java.util.ArrayList;
import java.util.List;

public class MoviesViewModel extends ViewModel {

private final MovieService movieService;
private MoviesSate moviesSate = new MoviesSate(new ArrayList<Movie>(), 1);

MoviesViewModel(MovieService movieService) {
this.movieService = movieService;
}

public LiveData<MoviesSate> moviesLiveData() {
return Transformations.map(movieService.loadMore(moviesSate.pageNumber()),
new Function<List<Movie>, MoviesSate>() {
@Override
public MoviesSate apply(List<Movie> input) {
List<Movie> movies = moviesSate.movies();
movies.addAll(input);
moviesSate = new MoviesSate(movies, moviesSate.pageNumber() + 1);
return moviesSate;
}
});
}

public void loadMore() {
movieService.loadMore(moviesSate.pageNumber());
}

public LiveData<Video> loadTrailerFor(Movie movie) {
return Transformations.map(movieService.loadTrailerFor(movie), new Function<List<Video>, Video>() {
@Override
public Video apply(List<Video> input) {
if (input.size() > 0) {
return input.get(0);
}
return null;
}
});
}
}
@@ -0,0 +1,23 @@
package com.novoda.demo.movies;

import android.arch.lifecycle.ViewModel;
import android.arch.lifecycle.ViewModelProvider;
import android.support.annotation.NonNull;

public class MoviesViewModelFactory implements ViewModelProvider.Factory {

private MovieService movieService;

MoviesViewModelFactory(MovieService movieService) {
this.movieService = movieService;
}

@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (modelClass.isAssignableFrom(MoviesViewModel.class)) {
return (T) new MoviesViewModel(movieService);
}
throw new IllegalArgumentException("Unknown ViewModel class");
}
}
1 change: 0 additions & 1 deletion MoviesExample/core/build.gradle
Expand Up @@ -5,7 +5,6 @@ dependencies {

compile 'com.squareup.retrofit2:retrofit:2.2.0'
compile 'com.squareup.retrofit2:converter-gson:2.2.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.2.0'
}

sourceCompatibility = "1.7"
Expand Down
Expand Up @@ -7,7 +7,6 @@
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

public class Dependencies {
Expand All @@ -34,7 +33,6 @@ private Retrofit providesRetrofit(OkHttpClient httpClient) {
.client(httpClient)
.baseUrl("https://api.themoviedb.org/3/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}

Expand Down
Expand Up @@ -4,7 +4,6 @@
import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.Query;
import rx.Single;

public interface MoviesApi {

Expand All @@ -14,5 +13,5 @@ public interface MoviesApi {
Call<MoviesResponse> topRated(@Query("page") int page);

@GET("movie/{id}/videos?api_key="+API_KEY)
Single<VideosResponse> videos(@Path("id") String movieId);
Call<VideosResponse> videos(@Path("id") String movieId);
}

0 comments on commit ba69cdb

Please sign in to comment.