Android应用程序中实现Dagger2的7个基本步骤

第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&region=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可以直接调用标注的方法,完成赋值或者其他的一些操作

   所以在我们的例子中,我们需要注入两个类:MovieDaoclass和MovieApiServiceclass。因此,我们将创建两个模块:ApiModuleDbModule(这可以在单个模块中完成,也可以将本地和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.AdapterViewModel实现移动到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})

参考: 源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值