第1步:向应用build.gradle文件添加必要的依赖项(Dagger2 )
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:cardview-v7:28.0.0'
/* Retrofit using RxJava2, Okhttp, Okhttp logging interceptor, Gson */
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'
testImplementation 'com.squareup.okhttp3:mockwebserver:3.9.1'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
/* Picasso lib for image loading */
implementation 'com.squareup.picasso:picasso:2.71828'
/* Android Architecture Component - ConstraintLayout */
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
/* Android Architecture Component - LiveData & ViewModel */
implementation 'android.arch.lifecycle:extensions:1.1.1'
/* Android Architecture Component - Navigation */
implementation 'android.arch.navigation:navigation-fragment:1.0.0-alpha08'
implementation 'android.arch.navigation:navigation-ui:1.0.0-alpha08'
/* Android Architecture Component - Room Persistance Lib */
implementation 'android.arch.persistence.room:runtime:1.1.1'
implementation 'android.arch.persistence.room:rxjava2:1.1.1'
annotationProcessor 'android.arch.persistence.room:compiler:1.1.1'
/* Dagger2 */
implementation 'com.google.dagger:dagger-android:2.17'
implementation 'com.google.dagger:dagger-android-support:2.17'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.17'
annotationProcessor 'com.google.dagger:dagger-compiler:2.17'
testImplementation 'org.mockito:mockito-core:2.7.22'
androidTestImplementation 'org.mockito:mockito-android:2.7.22'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'android.arch.core:core-testing:1.1.1'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
第2步:配置Room数据库
1.创建实体类
@Entity(primaryKeys = ("id"))
public class MovieEntity implements Parcelable {
@SerializedName("id")
@Expose
private Long id;
@Expose
private Long page;
@Expose
private Long totalPages;
@SerializedName(value="header", alternate={"title", "name"})
@Expose
private String header;
@SerializedName("poster_path")
@Expose
private String posterPath;
@SerializedName(value="description", alternate={"overview", "synopsis"})
private String description;
@SerializedName("release_date")
@Expose
private String releaseDate;
@SerializedName("genres")
@Expose
@TypeConverters(GenreListTypeConverter.class)
private List<Genre> genres;
@SerializedName("videos")
@Expose
@TypeConverters(VideoListTypeConverter.class)
private List<Video> videos;
@Expose
@TypeConverters(CrewListTypeConverter.class)
private List<Crew> crews;
@Expose
@TypeConverters(CastListTypeConverter.class)
private List<Cast> casts;
@Expose
@TypeConverters(ReviewListTypeConverter.class)
private List<Review> reviews;
@Expose
@TypeConverters(StringListConverter.class)
private List<String> categoryTypes;
@Expose
@TypeConverters(MovieListTypeConverter.class)
private List<MovieEntity> similarMovies;
@SerializedName("runtime")
@Expose
private Long runtime;
@SerializedName("status")
@Expose
private String status;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getPage() {
return page;
}
public void setPage(Long page) {
this.page = page;
}
public Long getTotalPages() {
return totalPages;
}
public void setTotalPages(Long totalPages) {
this.totalPages = totalPages;
}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
public String getPosterPath() {
if(posterPath != null && !posterPath.startsWith("http")) {
posterPath = String.format(AppConstants.IMAGE_URL, posterPath);
}
return posterPath;
}
public void setPosterPath(String posterPath) {
this.posterPath = posterPath;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getReleaseDate() {
return releaseDate;
}
public void setReleaseDate(String releaseDate) {
this.releaseDate = releaseDate;
}
public List<Genre> getGenres() {
return genres;
}
public void setGenres(List<Genre> genres) {
this.genres = genres;
}
public List<Video> getVideos() {
return videos;
}
public void setVideos(List<Video> videos) {
this.videos = videos;
}
public List<Crew> getCrews() {
return crews;
}
public void setCrews(List<Crew> crews) {
this.crews = crews;
}
public List<Cast> getCasts() {
return casts;
}
public void setCasts(List<Cast> casts) {
this.casts = casts;
}
public List<Review> getReviews() {
return reviews;
}
public void setReviews(List<Review> reviews) {
this.reviews = reviews;
}
public List<MovieEntity> getSimilarMovies() {
return similarMovies;
}
public void setSimilarMovies(List<MovieEntity> similarMovies) {
this.similarMovies = similarMovies;
}
public Long getRuntime() {
return runtime;
}
public void setRuntime(Long runtime) {
this.runtime = runtime;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public List<String> getCategoryTypes() {
return categoryTypes;
}
public void setCategoryTypes(List<String> categoryTypes) {
this.categoryTypes = categoryTypes;
}
public boolean isLastPage() {
return getPage() >= getTotalPages();
}
public MovieEntity() {
this.casts = new ArrayList<>();
this.crews = new ArrayList<>();
this.genres = new ArrayList<>();
this.videos = new ArrayList<>();
this.reviews = new ArrayList<>();
this.categoryTypes = new ArrayList<>();
this.similarMovies = new ArrayList<>();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeValue(this.id);
dest.writeValue(this.page);
dest.writeValue(this.totalPages);
dest.writeString(this.header);
dest.writeString(this.posterPath);
dest.writeString(this.description);
dest.writeString(this.releaseDate);
dest.writeTypedList(this.genres);
dest.writeTypedList(this.videos);
dest.writeTypedList(this.crews);
dest.writeTypedList(this.casts);
dest.writeTypedList(this.reviews);
dest.writeStringList(this.categoryTypes);
dest.writeTypedList(this.similarMovies);
dest.writeValue(this.runtime);
dest.writeString(this.status);
}
protected MovieEntity(Parcel in) {
this.id = (Long) in.readValue(Long.class.getClassLoader());
this.page = (Long) in.readValue(Long.class.getClassLoader());
this.totalPages = (Long) in.readValue(Long.class.getClassLoader());
this.header = in.readString();
this.posterPath = in.readString();
this.description = in.readString();
this.releaseDate = in.readString();
this.genres = in.createTypedArrayList(Genre.CREATOR);
this.videos = in.createTypedArrayList(Video.CREATOR);
this.crews = in.createTypedArrayList(Crew.CREATOR);
this.casts = in.createTypedArrayList(Cast.CREATOR);
this.reviews = in.createTypedArrayList(Review.CREATOR);
this.categoryTypes = in.createStringArrayList();
this.similarMovies = in.createTypedArrayList(MovieEntity.CREATOR);
this.runtime = (Long) in.readValue(Long.class.getClassLoader());
this.status = in.readString();
}
public static final Creator<MovieEntity> CREATOR = new Creator<MovieEntity>() {
@Override
public MovieEntity createFromParcel(Parcel source) {
return new MovieEntity(source);
}
@Override
public MovieEntity[] newArray(int size) {
return new MovieEntity[size];
}
};
}
2.创建Dao接口
@Dao
public interface MovieDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
long[] insertMovies(List<MovieEntity> movies);
@Insert(onConflict = OnConflictStrategy.REPLACE)
long insertMovie(MovieEntity movie);
@Update(onConflict = OnConflictStrategy.REPLACE)
int updateMovie(MovieEntity movie);
@Query("SELECT * FROM `MovieEntity` where id = :id")
MovieEntity getMovieById(Long id);
@Query("SELECT * FROM `MovieEntity` where id = :id")
Flowable<MovieEntity> getMovieDetailById(Long id);
@Query("SELECT * FROM `MovieEntity` where page = :page")
List<MovieEntity> getMoviesByPage(Long page);
}
3.创建数据库类
@Database(entities = {MovieEntity.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
public abstract MovieDao movieDao();
private static volatile AppDatabase INSTANCE;
public static AppDatabase getInstance(Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = buildDatabase(context);
}
}
}
return INSTANCE;
}
private static AppDatabase buildDatabase(Context context) {
return Room.databaseBuilder(context,
AppDatabase.class, "Entertainment.db")
.allowMainThreadQueries().build();
}
}
第3步:配置Api服务:
1.创建rest api响应模型类
public class MovieApiResponse {
public MovieApiResponse() {
this.results = new ArrayList<>();
}
private long page;
@SerializedName("total_pages")
private long totalPages;
@SerializedName("total_results")
private long totalResults;
private List<MovieEntity> results;
public long getPage() {
return page;
}
public void setPage(long page) {
this.page = page;
}
public long getTotalPages() {
return totalPages;
}
public void setTotalPages(long totalPages) {
this.totalPages = totalPages;
}
public long getTotalResults() {
return totalResults;
}
public void setTotalResults(long totalResults) {
this.totalResults = totalResults;
}
public List<MovieEntity> getResults() {
return results;
}
public void setResults(List<MovieEntity> results) {
this.results = results;
}
}
2.创建rest api接口Service
public interface MovieApiService {
@GET("movie/{type}?language=en-US®ion=US")
Observable<MovieApiResponse> fetchMoviesByType(@Path("type") String type,
@Query("page") long page);
}
第4步:配置Repository类
@Singleton
public class MovieRepository {
private MovieDao movieDao;
private MovieApiService movieApiService;
public MovieRepository(MovieDao movieDao,
MovieApiService movieApiService) {
this.movieDao = movieDao;
this.movieApiService = movieApiService;
}
public Observable<Resource<List<MovieEntity>>> loadMoviesByType(Long page,
String type) {
return new NetworkBoundResource<List<MovieEntity>, MovieApiResponse>() {
@Override
protected void saveCallResult(@NonNull MovieApiResponse item) {
List<MovieEntity> movieEntities = new ArrayList<>();
for(MovieEntity movieEntity : item.getResults()) {
MovieEntity storedMovieEntity = movieDao.getMovieById(movieEntity.getId());
if(storedMovieEntity == null) movieEntity.setCategoryTypes(Arrays.asList(type));
else {
List<String> categories = storedMovieEntity.getCategoryTypes();
categories.add(type);
movieEntity.setCategoryTypes(categories);
}
movieEntity.setPage(item.getPage());
movieEntity.setTotalPages(item.getTotalPages());
movieEntities.add(movieEntity);
}
movieDao.insertMovies(movieEntities);
}
@Override
protected boolean shouldFetch() {
return true;
}
@NonNull
@Override
protected Flowable<List<MovieEntity>> loadFromDb() {
List<MovieEntity> movieEntities = movieDao.getMoviesByPage(page);
if(movieEntities == null || movieEntities.isEmpty()) {
return Flowable.empty();
}
return Flowable.just(AppUtils.getMoviesByType(type, movieEntities));
}
@NonNull
@Override
protected Observable<Resource<MovieApiResponse>> createCall() {
return movieApiService.fetchMoviesByType(type, page)
.flatMap(movieApiResponse -> Observable.just(movieApiResponse == null
? Resource.error("", new MovieApiResponse())
: Resource.success(movieApiResponse)));
}
}.getAsObservable();
}
public Observable<Resource<MovieEntity>> fetchMovieDetails(Long movieId) {
return new NetworkBoundResource<MovieEntity, MovieEntity>() {
@Override
protected void saveCallResult(@NonNull MovieEntity item) {
MovieEntity movieEntity = movieDao.getMovieById(item.getId());
if(movieEntity == null) movieDao.insertMovie(item);
else movieDao.updateMovie(item);
}
@Override
protected boolean shouldFetch() {
return true;
}
@NonNull
@Override
protected Flowable<MovieEntity> loadFromDb() {
MovieEntity movieEntity = movieDao.getMovieById(movieId);
if(movieEntity == null) return Flowable.empty();
return Flowable.just(movieEntity);
}
@NonNull
@Override
protected Observable<Resource<MovieEntity>> createCall() {
String id = String.valueOf(movieId);
return Observable.combineLatest(movieApiService.fetchMovieDetail(id),
movieApiService.fetchMovieVideo(id),
movieApiService.fetchCastDetail(id),
movieApiService.fetchSimilarMovie(id, 1),
movieApiService.fetchMovieReviews(id),
(movieEntity, videoResponse, creditResponse, movieApiResponse, reviewApiResponse) -> {
if(videoResponse != null) {
movieEntity.setVideos(videoResponse.getResults());
}
if(creditResponse != null) {
movieEntity.setCrews(creditResponse.getCrew());
movieEntity.setCasts(creditResponse.getCast());
}
if(movieApiResponse != null) {
movieEntity.setSimilarMovies(movieApiResponse.getResults());
}
if(reviewApiResponse != null) {
movieEntity.setReviews(reviewApiResponse.getResults());
}
return Resource.success(movieEntity);
});
}
}.getAsObservable();
}
public Observable<Resource<List<MovieEntity>>> searchMovies(Long page,
String query) {
return new NetworkBoundResource<List<MovieEntity>, MovieApiResponse>() {
@Override
protected void saveCallResult(@NonNull MovieApiResponse item) {
List<MovieEntity> movieEntities = new ArrayList<>();
for(MovieEntity movieEntity : item.getResults()) {
MovieEntity storedMovieEntity = movieDao.getMovieById(movieEntity.getId());
if(storedMovieEntity == null) movieEntity.setCategoryTypes(Arrays.asList(query));
else {
List<String> categories = storedMovieEntity.getCategoryTypes();
categories.add(query);
movieEntity.setCategoryTypes(categories);
}
movieEntity.setPage(item.getPage());
movieEntity.setTotalPages(item.getTotalPages());
movieEntities.add(movieEntity);
}
movieDao.insertMovies(movieEntities);
}
@Override
protected boolean shouldFetch() {
return true;
}
@NonNull
@Override
protected Flowable<List<MovieEntity>> loadFromDb() {
List<MovieEntity> movieEntities = movieDao.getMoviesByPage(page);
if(movieEntities == null || movieEntities.isEmpty()) {
return Flowable.empty();
}
return Flowable.just(AppUtils.getMoviesByType(query, movieEntities));
}
@NonNull
@Override
protected Observable<Resource<MovieApiResponse>> createCall() {
return movieApiService.searchMoviesByQuery(query, "1")
.flatMap(movieApiResponse -> Observable.just(movieApiResponse == null
? Resource.error("", new MovieApiResponse())
: Resource.success(movieApiResponse)));
}
}.getAsObservable();
}
}
步骤5:配置ViewModel类
该类负责获取数据(无论是从网页API或本地缓存)。
该视图模型是负责与数据变化时更新UI,ViewModel将初始化Repository类的实例,同时将数据更新到UI。
为此,ViewModel必须能够访问 MovieDao
类和MovieApiService
类,这就是Dagger2的用武之地。我们将MovieDao
类和MovieApiService
类注入ViewModel 类中。
public class MovieListViewModel extends BaseViewModel {
@Inject
public MovieListViewModel(MovieDao movieDao, MovieApiService movieApiService) {
movieRepository = new MovieRepository(movieDao, movieApiService);
}
private String type;
private MovieRepository movieRepository;
private MutableLiveData<Resource<List<MovieEntity>>> moviesLiveData = new MutableLiveData<>();
public void setType(String type) {
this.type = type;
}
public void loadMoreMovies(Long currentPage) {
movieRepository.loadMoviesByType(currentPage, type)
.doOnSubscribe(disposable -> addToDisposable(disposable))
.subscribe(resource -> getMoviesLiveData().postValue(resource));
}
public boolean isLastPage() {
return moviesLiveData.getValue() != null &&
!moviesLiveData.getValue().data.isEmpty() ?
moviesLiveData.getValue().data.get(0).isLastPage() :
false;
}
public MutableLiveData<Resource<List<MovieEntity>>> getMoviesLiveData() {
return moviesLiveData;
}
}
配置Dagger
@Provides是用来标注类中的方法的,一般配合@Model使用。通过依赖注入获取实例化对象时,@Provide可以直接调用标注的方法,完成赋值或者其他的一些操作
所以在我们的例子中,我们需要注入两个类:MovieDao
class和MovieApiService
class。因此,我们将创建两个模块:ApiModule
和DbModule
(这可以在单个模块中完成,也可以将本地和Web服务分开)。
@Module
public class ApiModule {
@Provides
@Singleton
Gson provideGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
// gsonBuilder.registerTypeAdapter(MOVIE_ARRAY_LIST_CLASS_TYPE, new MoviesJsonDeserializer());
return gsonBuilder.create();
}
@Provides
@Singleton
Cache provideCache(Application application) {
long cacheSize = 10 * 1024 * 1024; // 10 MB
File httpCacheDirectory = new File(application.getCacheDir(), "http-cache");
return new Cache(httpCacheDirectory, cacheSize);
}
@Provides
@Singleton
NetworkInterceptor provideNetworkInterceptor(Application application) {
return new NetworkInterceptor(application.getApplicationContext());
}
@Provides
@Singleton
OkHttpClient provideOkhttpClient(Cache cache, NetworkInterceptor networkInterceptor) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.cache(cache);
httpClient.addInterceptor(networkInterceptor);
httpClient.addInterceptor(logging);
httpClient.addNetworkInterceptor(new RequestInterceptor());
httpClient.connectTimeout(30, TimeUnit.SECONDS);
httpClient.readTimeout(30, TimeUnit.SECONDS);
return httpClient.build();
}
@Provides
@Singleton
Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
return new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(BASE_URL)
.client(okHttpClient)
.build();
}
@Provides
@Singleton
MovieApiService provideMovieApiService(Retrofit retrofit) {
return retrofit.create(MovieApiService.class);
}
@Provides
@Singleton
TvApiService provideTvApiService(Retrofit retrofit) {
return retrofit.create(TvApiService.class);
}
}
@Module
public class DbModule {
@Provides
@Singleton
AppDatabase provideDatabase(@NonNull Application application) {
return Room.databaseBuilder(application,
AppDatabase.class, "Entertainment.db")
.allowMainThreadQueries().build();
}
@Provides
@Singleton
MovieDao provideMovieDao(@NonNull AppDatabase appDatabase) {
return appDatabase.movieDao();
}
@Provides
@Singleton
TvDao provideTvDao(@NonNull AppDatabase appDatabase) {
return appDatabase.tvDao();
}
}
1. ViewModelFactory类:
现在我们需要将这两个模块注入我们的ViewModel
。
ViewModelFactory
基本上可以帮助您为您的Activity和Fragment动态创建ViewModel。该ViewModelFactory
有Map<Class<? extends ViewModel>, Provider<ViewModel>> 集合。Fragment和Activity现在可以注入ViewModelFactory并检索它们ViewModel
。
@Singleton
public class ViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
@Inject
public ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
this.creators = creators;
}
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
Provider<? extends ViewModel> creator = creators.get(modelClass);
if (creator == null) {
for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
if (modelClass.isAssignableFrom(entry.getKey())) {
creator = entry.getValue();
break;
}
}
}
if (creator == null) {
throw new IllegalArgumentException("unknown model class " + modelClass);
}
try {
return (T) creator.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
2. ViewModelKey类:
ViewModelKeys
帮助您映射您的ViewModel
类,以便ViewModelFactory
正确提供/注入它们。
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MapKey
public @interface ViewModelKey {
Class<? extends ViewModel> value();
}
3. ViewModelModule类
ViewModelModule
用于提供视图上通过Dagger视图模型所使用的ViewModelFactory
类
@Module
public abstract class ViewModelModule {
@Binds
abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory factory);
/ *
*这个方法基本上
*使用@IntoMap注释将此对象注入Map
*使用MovieListViewModel.class作为键,
*和将构建MovieListViewModel的提供者
*对象。
*
* * /
@Binds
@IntoMap
@ViewModelKey(MovieListViewModel.class)
protected abstract ViewModel movieListViewModel(MovieListViewModel moviesListViewModel);
}
基本上,
- 我们可以
ViewModelModule
用来定义我们的ViewModels
。 - 我们为每个
ViewModel
使用ViewModelKey
该类的人提供密钥。 - 然后在我们的Activity / Fragment中,我们使用
ViewModelFactory
类来注入相应的ViewModel
。(我们将在创建Activity时查看更多细节)
5. ActivityModule类
由于我们使用的是dagger-android支持库,我们可以使用Android注入。对于在此类中定义的活动,ActivityModule
生成AndroidInjector
(这是存在于dagger-android框架中的新dagger-android类)。这允许我们AndroidInjection.inject(this)
在onCreate()
方法中将事物注入活动中。
@Module
public abstract class ActivityModule {
@ContributesAndroidInjector(modules = FragmentModule.class)
abstract MainActivity contributeMainActivity();
}
注意:我们可以定义所有需要注入的Activity。例如,在我们的例子中,这将生成AndroidInjector<MainActivity>
。
6. AppComponent类
任何带有注释的类都@Component
定义了模块与需要依赖关系的类之间的连接。我们定义了一个@Component.Builder
将从我们的自定义Application类调用的接口。这会将我们的应用程序对象设置为AppComponent
。因此AppComponent
,应用程序实例内部可用。因此,我们的模块可以访问此应用程序实例,例如ApiModule
在需要时。
/*
* We mark this interface with the @Component annotation.
* And we define all the modules that can be injected.
* Note that we provide AndroidSupportInjectionModule.class
* here. This class was not created by us.
* It is an internal class in Dagger 2.10.
* Provides our activities and fragments with given module.
* */
@Component(modules = {
ApiModule.class,
DbModule.class,
ViewModelModule.class,
ActivityModule.class,
AndroidSupportInjectionModule.class})
@Singleton
public interface AppComponent {
/* We will call this builder interface from our custom Application class.
* This will set our application object to the AppComponent.
* So inside the AppComponent the application instance is available.
* So this application instance can be accessed by our modules
* such as ApiModule when needed
* */
@Component.Builder
interface Builder {
@BindsInstance
Builder application(Application application);
AppComponent build();
}
/*
* This is our custom Application class
* */
void inject(AppController appController);
}
第6步:配置Application类
所以现在我们在项目中创建一个自定义Application类。
/*
* we use our AppComponent (now prefixed with Dagger)
* to inject our Application class.
* This way a DispatchingAndroidInjector is injected which is
* then returned when an injector for an activity is requested.
* */
public class AppController extends Application implements HasActivityInjector {
@Inject
DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;
@Override
public DispatchingAndroidInjector<Activity> activityInjector() {
return dispatchingAndroidInjector;
}
@Override
public void onCreate() {
super.onCreate();
DaggerAppComponent.builder()
.application(this)
.build()
.inject(this);
}
}
注意:不要忘记将此自定义Application类添加到AndroidManifest.xml
<application
android:name=".AppController"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
第7步:配置MainActivity类
所以现在我们需要创建我们的Activity类。
public class MainActivity extends AppCompatActivity {
/*
* Step 1: Here as mentioned in Step 5, we need to
* inject the ViewModelFactory. The ViewModelFactory class
* has a list of ViewModels and will provide
* the corresponding ViewModel in this activity
* */
@Inject
ViewModelFactory viewModelFactory;
/*
* I am using DataBinding
* */
private MainActivityBinding binding;
/*
* This is our ViewModel class
* */
private MovieListViewModel movieListViewModel;
private MoviesListAdapter moviesListAdapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
/*
* Step 2: Remember in our ActivityModule, we
* defined MainActivity injection? So we need
* to call this method in order to inject the
* ViewModelFactory into our Activity
* */
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
initialiseView();
initialiseViewModel();
}
/*
* Initialising the View using Data Binding
* */
private void initialiseView() {
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
moviesListAdapter = new MoviesListAdapter(this);
binding.moviesList.setLayoutManager(new LinearLayoutManager(getApplicationContext(), LinearLayoutManager.HORIZONTAL, false));
binding.moviesList.setAdapter(moviesListAdapter);
/* SnapHelper to change the background of the activity based on the list item
* currently visible */
SnapHelper startSnapHelper = new PagerSnapHelper(position -> {
MovieEntity movie = moviesListAdapter.getItem(position);
binding.overlayLayout.updateCurrentBackground(movie.getPosterPath());
});
startSnapHelper.attachToRecyclerView(binding.moviesList);
}
/*
* Step 3: Initialising the ViewModel class here.
* We are adding the ViewModelFactory class here.
* We are observing the LiveData
* */
private void initialiseViewModel() {
movieListViewModel = ViewModelProviders.of(this, viewModelFactory).get(MovieListViewModel.class);
movieListViewModel.getMoviesLiveData().observe(this, resource -> {
if(resource.isLoading()) {
displayLoader();
} else if(!resource.data.isEmpty()) {
updateMoviesList(resource.data);
} else handleErrorResponse();
});
/* Fetch movies list */
movieListViewModel.loadMoreMovies();
}
private void displayLoader() {
binding.moviesList.setVisibility(View.GONE);
binding.loaderLayout.rootView.setVisibility(View.VISIBLE);
binding.loaderLayout.loader.start();
}
private void hideLoader() {
binding.moviesList.setVisibility(View.VISIBLE);
binding.loaderLayout.rootView.setVisibility(View.GONE);
binding.loaderLayout.loader.stop();
}
private void updateMoviesList(List<MovieEntity> movies) {
hideLoader();
binding.emptyLayout.emptyContainer.setVisibility(View.GONE);
binding.moviesList.setVisibility(View.VISIBLE);
moviesListAdapter.setItems(movies);
}
private void handleErrorResponse() {
hideLoader();
binding.moviesList.setVisibility(View.GONE);
binding.emptyLayout.emptyContainer.setVisibility(View.VISIBLE);
}
}
我们看到了如何注入ViewModelFactory
我们的MainActivity
。如果您想将ViewModelFactory
片段注入片段,我们需要进行以下修改:
1. FragmentModule类:
创建一个名为的新类FragmentModule
。
@Module
public abstract class FragmentModule {
/*
* We define the name of the Fragment we are going
* to inject the ViewModelFactory into. i.e. in our case
* The name of the fragment: MovieListFragment
*/
@ContributesAndroidInjector
abstract MovieListFragment contributeMovieListFragment();
}
2.修改ActivityModule类:
将新创建的内容添加FragmentModule
到要注入的活动中。即在我们的情况下MainActivity
。
@Module
public abstract class ActivityModule {
/*
* We modify our ActivityModule by adding the
* FragmentModule to the Activity which contains
* the fragment
*/
@ContributesAndroidInjector(modules = FragmentModule.class)
abstract MainActivity contributeMainActivity();
}
3.修改MainActivity类:
HasSupportFragmentInjector
如果我们想ViewModelFactory
在Fragment中注入类,我们需要在Activity中实现。此外,将我们所有的RecyclerView.Adapter
和ViewModel
实现移动到Fragment类。
public class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector {
/*
* Step 1: Rather than injecting the ViewModelFactory
* in the activity, we are going to implement the
* HasActivityInjector and inject the ViewModelFactory
* into our MovieListFragment
* */
@Inject
DispatchingAndroidInjector<Fragment> dispatchingAndroidInjector;
@Override
public DispatchingAndroidInjector<Fragment> supportFragmentInjector() {
return dispatchingAndroidInjector;
}
/*
* I am using DataBinding
* */
private MainActivityBinding binding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
/*
* Step 2: We still need to inject this method
* into our activity so that our fragment can
* inject the ViewModelFactory
* */
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
initialiseView();
}
/*
* Initialising the View using Data Binding
* */
private void initialiseView() {
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
}
public void updateBackground(String url) {
binding.overlayLayout.updateCurrentBackground(url);
}
}
4.添加MovieFragment类:
最后,我们需要创建Fragment类。我们将ViewModelFactory
片段注入片段并初始化viewModel。剩下的实现是一样的。
public class MovieListFragment extends Fragment {
/*
* Step 1: Here, we need to inject the ViewModelFactory.
* */
@Inject
ViewModelFactory viewModelFactory;
/*
* I am using DataBinding
* */
private MovieFragmentBinding binding;
/*
* This is our ViewModel class
* */
MovieListViewModel movieListViewModel;
private MoviesListAdapter moviesListAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*
* Step 2: Remember in our FragmentModule, we
* defined MovieListFragment injection? So we need
* to call this method in order to inject the
* ViewModelFactory into our Fragment
* */
AndroidSupportInjection.inject(this);
initialiseViewModel();
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_movie_list, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initialiseView();
}
private void initialiseView() {
moviesListAdapter = new MoviesListAdapter(getActivity());
binding.moviesList.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
binding.moviesList.setAdapter(moviesListAdapter);
/* SnapHelper to change the background of the activity based on the list item
* currently visible */
SnapHelper startSnapHelper = new PagerSnapHelper(position -> {
MovieEntity movie = moviesListAdapter.getItem(position);
((MainActivity)getActivity()).updateBackground(movie.getPosterPath());
});
startSnapHelper.attachToRecyclerView(binding.moviesList);
}
private void initialiseViewModel() {
movieListViewModel = ViewModelProviders.of(this, viewModelFactory).get(MovieListViewModel.class);
movieListViewModel.getMoviesLiveData().observe(this, resource -> {
if(resource.isLoading()) {
displayLoader();
} else if(!resource.data.isEmpty()) {
updateMoviesList(resource.data);
} else handleErrorResponse();
});
/* Fetch movies list */
movieListViewModel.loadMoreMovies();
}
private void displayLoader() {
binding.moviesList.setVisibility(View.GONE);
binding.loaderLayout.rootView.setVisibility(View.VISIBLE);
}
private void hideLoader() {
binding.moviesList.setVisibility(View.VISIBLE);
binding.loaderLayout.rootView.setVisibility(View.GONE);
}
private void updateMoviesList(List<MovieEntity> movies) {
hideLoader();
binding.emptyLayout.emptyContainer.setVisibility(View.GONE);
binding.moviesList.setVisibility(View.VISIBLE);
moviesListAdapter.setItems(movies);
}
private void handleErrorResponse() {
hideLoader();
binding.moviesList.setVisibility(View.GONE);
binding.emptyLayout.emptyContainer.setVisibility(View.VISIBLE);
}
}
5.将FragmentModule类添加到AppComponent:
@Component(modules = {
ApiModule.class,
DbModule.class,
ViewModelModule.class,
ActivityModule.class,
FragmentModule.class,
AndroidSupportInjectionModule.class})
参考: 源码