Dagger-Android

Sử dụng Dagger 2 một cách đơn giản

  • Báo cáo

Bài đăng này đã không được cập nhật trong 2 năm

Show

Giới thiệu

  • Gần đây mình mới được tiếp xúc với Dagger và khái niệm về Dependency injection trong project của mình. Có thể có một số bạn chưa nghe thấy từ khóa trên hay vẫn còn đang loay hoay để sử dụng nó thì đây là bài viết đơn giản để tiếp cận Dagger 2.
  • Nếu bạn đang băn khoăn về Dagger hay là tại sao phải sử dụng Dagger và cách sử dụng Dagger như thế nào, có thể tham khảo những nguồn khác mình đã để ở trên để hiểu thêm về khái niệm của chúng.
  • Chính vì có rất nhiều loại tài liệu khác nhau, nhiều nguồn khác nhau và nhiều cách sử dụng Dependency injection khác nhau nhưng đối với mình và một số bạn mới học thì việc sử dụng nó vào trong project thì cũng gặp một số vấn đề nhất định. Bài viết này sẽ giúp cho các bạn sử dụng Dagger một cách đơn giản hơn trong project của mình.
  • Mình sử dụng code demo là Kotlin vì hiện tại develop Android đang rất được ưa chuộng về các tính năng mới của nó. Nếu bạn chưa từng sử dụng, mình tin rằng sau khi sử dụng bạn sẽ cảm thấy thích nó !
  • Chúng ta sử dụng một ứng dụng nhỏ về MovieDB để cho các bạn một ví dụ sử dụng Dagger dễ hiểu nhất.

Sử dụng

Bước 1: Thêm những phụ thuộc cần thiết

  • Thêm phụ thuộc support của kotlin vào project build.gradledependencies { classpath 'com.android.tools.build:gradle:3.4.0-alpha01' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.71' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }
  • Phụ thuộc cần thiết của Dagger trong app build.gradle... apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { compileSdkVersion 28 defaultConfig { ... } ... } dependencies { ... implementation 'com.google.dagger:dagger-android:2.17' implementation 'com.google.dagger:dagger-android-support:2.17' kapt 'com.google.dagger:dagger-android-processor:2.17' /* Dagger2 - default dependency */ kapt 'com.google.dagger:dagger-compiler:2.17' /* Add Kotlin support */ implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.71' }

Bước 2: Cài đặt thư viện Room

Room là một thư viện nằm trong gói Android Jetpack mới được ra mắt gần đây trong Google I/O 2018. Nó là thư viện sinh ra nhằm cung cấp một lớp trừu tượng hóa trên nền của SQLite để thao tác dễ dàng hơn và mạnh mẽ hơn. Bạn có thể tìm hiểu thêm cách implement về Room ở đây.

Tạo class Entity cho Room

Class này cho phép bạn tạo các trường có tên như nào thì bảng sau này bạn lưu vào cũng sẽ như vậy. Nó cũng hỗ trợ khóa ngoại và khóa chính để bạn tùy chỉnh.@Entity(primaryKeys = ["id"]) data class MovieEntity( @SerializedName("id") val id: Long, @SerializedName(value = "header", alternate = ["title", "name"]) val header: String, @SerializedName("poster_path") var posterPath: String?, @SerializedName(value = "description", alternate = ["overview", "synopsis"]) var description: String?, @SerializedName("release_date") var releaseDate: String?, @SerializedName("runtime") var runTime: Long, var status: String? )

Tạo class Dao cho Room

Class này cho phép bạn viết những hàm trừu tượng để thêm, sửa, xóa dữ liệu có trong bảng bằng các hàm với các annotation có sẵn của Room.@Dao interface MovieDao { /* Method to insert the movies fetched from api * to room */ @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertMovies(movies: List): LongArray /* Method to fetch the movies stored locally */ @Query("SELECT * FROM `MovieEntity`") fun getMoviesByPage(): List }

Tạo database

Tạo database với model là class entity và một vài tham số có sẵn của database@Database(entities = [MovieEntity::class], version = 1, exportSchema = false) abstract class AppDatabase : RoomDatabase() { abstract fun movieDao(): MovieDao }

Bước 3: Cấu hình Api Service

Chúng ta đã cấu hình database SQLite ở local ở bước trên, đến đây chúng ta cần phải cấu hình api để gọi dữ liệu về.

Tạo Api responsedata class MovieApiResponse(val page: Long, val results: List, val total_results: Long, val total_pages: Long)

Bước 4: Cấu hình Repository

Đây là class chịu trách nhiệm xử lý tất cả các hoạt động liên quan đến dữ liệu, cả online và offline đều được xử lý ở đây. Để hiểu hơn tại sao lại có class này, bạn nên tham khảo mẫu Repository Pattern. Thông thường mẫu này kết hợp với Rx để trả callback khi có dữ liệu nhận được từ Api hoặc Local./* * One of the first things we do in the Repository class is to make it a Singleton. * */ * @Singleton class MovieRepository( private val movieDao: MovieDao, private val movieApiService: MovieApiService ) { fun loadMoviesByType(): Observable>> { return object : NetworkBoundResource, MovieApiResponse>() { override fun saveCallResult(item: MovieApiResponse) { movieDao.insertMovies(item.results) } override fun shouldFetch(): Boolean { return true } override fun loadFromDb(): Flowable> { val movieEntities = movieDao.getMoviesByPage() return if (movieEntities == null || movieEntities.isEmpty()) { Flowable.empty() } else Flowable.just(movieEntities) } override fun createCall(): Observable> { return movieApiService.fetchMoviesByType() .flatMap { movieApiResponse -> Observable.just( if (movieApiResponse == null) Resource.error("", MovieApiResponse(1, emptyList(), 0, 1)) else Resource.success(movieApiResponse) ) } } }.getAsObservable() } }

Bước 5: Cấu hình ViewModel

Như đã nhắc đến ở trên, Repository chịu trách nhiệm tìm và nạp dữ liệu về, còn về ViewModel thì chịu trách nhiệm việc cập nhật giao diện người dùng đối với các dữ liệu này.

Như ta thấy ở đây, ViewModel cần sử dụng đến MovieDaoMovieService để có thể lấy được dữ liệu từ Api và lưu trữ vào local database. Chính vì vậy nhiệm vụ của chúng ta là inject 2 phụ thuộc kia vào đây để sử dụng thông qua Dagger./* * We are injecting the MovieDao class * and the MovieApiService class to the ViewModel. * */ class MovieListViewModel @Inject constructor( movieDao: MovieDao, movieApiService: MovieApiService) : ViewModel() { /* You can see we are initialising the MovieRepository class here */ private val movieRepository: MovieRepository = MovieRepository(movieDao, movieApiService) /* We are using LiveData to update the UI with the data changes. */ private val moviesListLiveData = MutableLiveData>>() /* * Method called by UI to fetch movies list * */ fun loadMoreMovies() { movieRepository.loadMoviesByType() .subscribe { resource -> getMoviesLiveData().postValue(resource) } } /* * LiveData observed by the UI * */ fun getMoviesLiveData() = moviesListLiveData }

Bước 6: Cấu hình Dagger

Module

Trong Dagger, chúng ta sử dụng Module để chú thích cho các lớp cung cấp phụ thuộc. Mà trong ví dụ này, ta cần phải inject MovieDaoMovieApiService vào trong ViewModel, vậy nên chúng ta sẽ tạo ra 2 Module cung cấp các lớp đó tương ứng là ApiModuleApiModule. Các phương thức được chú thích bằng @Providers để chỉ hàm đó cung cấp gì.

  • ApiModule sẽ cung cấp các phương thức trả về những cài đặt đối với việc gọi Api có sẵn@Module class ApiModule { @Provides @Singleton internal fun provideGson(): Gson { val gsonBuilder = GsonBuilder() return gsonBuilder.create() } @Provides @Singleton internal fun provideCache(application: Application): Cache { val cacheSize = (10 * 1024 * 1024).toLong() // 10 MB val httpCacheDirectory = File(application.cacheDir, "http-cache") return Cache(httpCacheDirectory, cacheSize) } @Provides @Singleton internal fun provideOkhttpClient(cache: Cache): OkHttpClient { val logging = HttpLoggingInterceptor() logging.level = HttpLoggingInterceptor.Level.BODY val httpClient = OkHttpClient.Builder() httpClient.cache(cache) httpClient.addInterceptor(logging) httpClient.addNetworkInterceptor(RequestInterceptor()) httpClient.connectTimeout(30, TimeUnit.SECONDS) httpClient.readTimeout(30, TimeUnit.SECONDS) return httpClient.build() } @Provides @Singleton internal fun provideRetrofit(gson: Gson, okHttpClient: OkHttpClient): Retrofit { return Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .baseUrl("https://api.themoviedb.org/3/") .client(okHttpClient) .build() } @Provides @Singleton internal fun provideMovieApiService(retrofit: Retrofit): MovieApiService { return retrofit.create(MovieApiService::class.java) } }
  • DbModule sẽ cung cấp những phụ thuộc cho việc tương tác với database local thông qua Room library@Module class DbModule { @Provides @Singleton internal fun provideDatabase(application: Application): AppDatabase { return Room.databaseBuilder( application, AppDatabase::class.java, "Entertainment.db") .allowMainThreadQueries().build() } @Provides @Singleton internal fun provideMovieDao(appDatabase: AppDatabase): MovieDao { return appDatabase.movieDao() } }

Xử lý ViewModel

Về cơ bản thì ta sẽ thêm 2 Module trên vào ViewModel, vậy nên cần phải tạo lớp VIewModelFactory để giúp bạn tự động tạo ViewModel trên Activity hoặc Fragment của bạn.

  • ViewModelFactory có một danh sách các provider để có thể taọ bất kì ViewModel nào bị ràng buộc. Fragment hay Activity chỉ cần inject vào factory và lấy ra ViewModel của chúng.@Singleton class ViewModelFactory @Inject constructor(private val viewModels: MutableMap, Provider>) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { val creator = viewModels[modelClass] ?: viewModels.asIterable().firstOrNull { modelClass.isAssignableFrom(it.key) }?.value ?: throw IllegalArgumentException("unknown model class $modelClass") return try { creator.get() as T } catch (e: Exception) { throw RuntimeException(e) } } }
  • ViewModelKey  giúp cho việc ánh xạ các ViewModel của bạn cho phép ViewModelFactory có thể provider hoặc inject chúng.@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) @MapKey annotation class ViewModelKey(val value: KClass)
  • Tiếp tục chúng ta tạo ViewModelModule để cung cấp các bản của ViewModel thông qua Dagger, được sử dụng bởi ViewModelFactory.@Module internal abstract class ViewModelModule { @Binds internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory @Binds @IntoMap @ViewModelKey(MovieListViewModel::class) protected abstract fun movieListViewModel(moviesListViewModel: MovieListViewModel): ViewModel }

Về cơ bản thì chúng ta có thể tóm tắt lại như sau:

  • Chúng ta có thể sử dụng ViewModelModule để định nghĩa các ViewModel
  • ViewModelKey cung cấp cho mỗi một ViewModel một key duy nhất
  • Sau đó Activity hoặc Fragment sẽ sử dụng ViewModeFactory để inject ViewModel tương ứng với nó.

Xử lý trong Activity

Chúng ta sử dụng thư viện dagger-android nên có thể sử dụng Android Injection. ActivityModule sẽ tạo ra Android Injection để các hoạt động được định nghĩa ở đây.@Module abstract class ActivityModule { @ContributesAndroidInjector() abstract fun contributeMainActivity(): MainActivity }

Xử lý AppComponent

Bất kì lớp nào có chú thích @Component sẽ là chỗ để xác định kết nối giữa các Module với các lớp  yêu cầu phụ thuộc. Chúng ta xác định giao diện @Component.Builder sẽ được gọi từ lớp Application để xuyên suốt trong ứng dụng. Điều này sẽ khiến cho lớp đó trở thành AppComponent và tồn tại suốt vòng đời của ứng dụng và được truy cập bất cứ khi nào cần thiết.@Component( modules = [ ApiModule::class, DbModule::class, ViewModelModule::class, ActivityModule::class, AndroidSupportInjectionModule::class] ) @Singleton interface AppComponent { @Component.Builder interface Builder { @BindsInstance fun application(application: Application): Builder fun build(): AppComponent } /* * This is our custom Application class * */ fun inject(appController: AppController) }

Cấu hình lớp Application

Cuối cùng của việc cài đặt Dagger, chúng ta sẽ custom một Application để lưu trữ AppComponent và giữ chúng trong suốt quá trình chạy của ứng dụng.class AppController : Application(), HasActivityInjector { @Inject lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector override fun activityInjector(): DispatchingAndroidInjector? { return dispatchingAndroidInjector } override fun onCreate() { super.onCreate() DaggerAppComponent.builder() .application(this) .build() .inject(this) } }

Đừng quên thêm Application này vào AndroidManifest.xml nhé !!!

Bước 7: Cài đặt trên Activity

Bây giờ chúng ta tạo Activity và hoàn thành các bước trong này nhé, vậy là cũng đã hoàn thành rồiclass MainActivity : AppCompatActivity() { @Inject internal lateinit var viewModelFactory: ViewModelProvider.Factory private lateinit var binding: MainActivityBinding lateinit var moviesListViewModel: MovieListViewModel private lateinit var moviesListAdapter: MoviesListAdapter override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) initialiseView() initialiseViewModel() } private fun initialiseView() { binding = DataBindingUtil.setContentView(this, R.layout.activity_main) moviesListAdapter = MoviesListAdapter(this) binding.moviesList.layoutManager = LinearLayoutManager(applicationContext, LinearLayoutManager.HORIZONTAL, false) binding.moviesList.adapter = moviesListAdapter val startSnapHelper = PagerSnapHelper( object : RecyclerSnapItemListener { override fun onItemSnap(position: Int) { val movie = moviesListAdapter.getItem(position) binding.overlayLayout.updateCurrentBackground(movie.getFormattedPosterPath()) } } ) startSnapHelper.attachToRecyclerView(binding.moviesList) } private fun initialiseViewModel() { moviesListViewModel = ViewModelProviders.of(this, viewModelFactory).get(MovieListViewModel::class.java) moviesListViewModel.getMoviesLiveData().observe(this, Observer { resource -> if (resource!!.isLoading) { displayLoader() } else if (resource.data != null && !resource.data.isEmpty()) { updateMoviesList(resource.data) } else handleErrorResponse() }) moviesListViewModel.loadMoreMovies() } private fun displayLoader() { binding.moviesList.visibility = View.GONE binding.loaderLayout.rootView.visibility = View.VISIBLE } private fun hideLoader() { binding.moviesList.visibility = View.VISIBLE binding.loaderLayout.rootView.visibility = View.GONE } private fun updateMoviesList(movies: List) { hideLoader() binding.emptyLayout.emptyContainer.visibility = View.GONE binding.moviesList.visibility = View.VISIBLE moviesListAdapter.setItems(movies) } private fun handleErrorResponse() { hideLoader() binding.moviesList.visibility = View.GONE binding.emptyLayout.emptyContainer.visibility = View.VISIBLE } }


Trên đây là bài dịch hướng dẫn sử dụng đơn giản về Dagger theo các bước và có code demo khá dễ hiểu. Hi vọng mọi người thích và sử dụng Dagger cho ứng dụng của mình.

Bài viết có nguồn từ https://android.jlelse.eu/7-steps-to-implement-dagger-2-in-android-dabc16715a3a  

Sử dụng Dagger 2 một cách đơn giản

  • Báo cáo

Bài đăng này đã không được cập nhật trong 2 năm

Giới thiệu

  • Gần đây mình mới được tiếp xúc với Dagger và khái niệm về Dependency injection trong project của mình. Có thể có một số bạn chưa nghe thấy từ khóa trên hay vẫn còn đang loay hoay để sử dụng nó thì đây là bài viết đơn giản để tiếp cận Dagger 2.
  • Nếu bạn đang băn khoăn về Dagger hay là tại sao phải sử dụng Dagger và cách sử dụng Dagger như thế nào, có thể tham khảo những nguồn khác mình đã để ở trên để hiểu thêm về khái niệm của chúng.
  • Chính vì có rất nhiều loại tài liệu khác nhau, nhiều nguồn khác nhau và nhiều cách sử dụng Dependency injection khác nhau nhưng đối với mình và một số bạn mới học thì việc sử dụng nó vào trong project thì cũng gặp một số vấn đề nhất định. Bài viết này sẽ giúp cho các bạn sử dụng Dagger một cách đơn giản hơn trong project của mình.
  • Mình sử dụng code demo là Kotlin vì hiện tại develop Android đang rất được ưa chuộng về các tính năng mới của nó. Nếu bạn chưa từng sử dụng, mình tin rằng sau khi sử dụng bạn sẽ cảm thấy thích nó !
  • Chúng ta sử dụng một ứng dụng nhỏ về MovieDB để cho các bạn một ví dụ sử dụng Dagger dễ hiểu nhất.

Sử dụng

Bước 1: Thêm những phụ thuộc cần thiết

  • Thêm phụ thuộc support của kotlin vào project build.gradledependencies { classpath 'com.android.tools.build:gradle:3.4.0-alpha01' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.71' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }
  • Phụ thuộc cần thiết của Dagger trong app build.gradle... apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { compileSdkVersion 28 defaultConfig { ... } ... } dependencies { ... implementation 'com.google.dagger:dagger-android:2.17' implementation 'com.google.dagger:dagger-android-support:2.17' kapt 'com.google.dagger:dagger-android-processor:2.17' /* Dagger2 - default dependency */ kapt 'com.google.dagger:dagger-compiler:2.17' /* Add Kotlin support */ implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.71' }

Bước 2: Cài đặt thư viện Room

Room là một thư viện nằm trong gói Android Jetpack mới được ra mắt gần đây trong Google I/O 2018. Nó là thư viện sinh ra nhằm cung cấp một lớp trừu tượng hóa trên nền của SQLite để thao tác dễ dàng hơn và mạnh mẽ hơn. Bạn có thể tìm hiểu thêm cách implement về Room ở đây.

Tạo class Entity cho Room

Class này cho phép bạn tạo các trường có tên như nào thì bảng sau này bạn lưu vào cũng sẽ như vậy. Nó cũng hỗ trợ khóa ngoại và khóa chính để bạn tùy chỉnh.@Entity(primaryKeys = ["id"]) data class MovieEntity( @SerializedName("id") val id: Long, @SerializedName(value = "header", alternate = ["title", "name"]) val header: String, @SerializedName("poster_path") var posterPath: String?, @SerializedName(value = "description", alternate = ["overview", "synopsis"]) var description: String?, @SerializedName("release_date") var releaseDate: String?, @SerializedName("runtime") var runTime: Long, var status: String? )

Tạo class Dao cho Room

Class này cho phép bạn viết những hàm trừu tượng để thêm, sửa, xóa dữ liệu có trong bảng bằng các hàm với các annotation có sẵn của Room.@Dao interface MovieDao { /* Method to insert the movies fetched from api * to room */ @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertMovies(movies: List): LongArray /* Method to fetch the movies stored locally */ @Query("SELECT * FROM `MovieEntity`") fun getMoviesByPage(): List }

Tạo database

Tạo database với model là class entity và một vài tham số có sẵn của database@Database(entities = [MovieEntity::class], version = 1, exportSchema = false) abstract class AppDatabase : RoomDatabase() { abstract fun movieDao(): MovieDao }

Bước 3: Cấu hình Api Service

Chúng ta đã cấu hình database SQLite ở local ở bước trên, đến đây chúng ta cần phải cấu hình api để gọi dữ liệu về.

Tạo Api responsedata class MovieApiResponse(val page: Long, val results: List, val total_results: Long, val total_pages: Long)

Bước 4: Cấu hình Repository

Đây là class chịu trách nhiệm xử lý tất cả các hoạt động liên quan đến dữ liệu, cả online và offline đều được xử lý ở đây. Để hiểu hơn tại sao lại có class này, bạn nên tham khảo mẫu Repository Pattern. Thông thường mẫu này kết hợp với Rx để trả callback khi có dữ liệu nhận được từ Api hoặc Local./* * One of the first things we do in the Repository class is to make it a Singleton. * */ * @Singleton class MovieRepository( private val movieDao: MovieDao, private val movieApiService: MovieApiService ) { fun loadMoviesByType(): Observable>> { return object : NetworkBoundResource, MovieApiResponse>() { override fun saveCallResult(item: MovieApiResponse) { movieDao.insertMovies(item.results) } override fun shouldFetch(): Boolean { return true } override fun loadFromDb(): Flowable> { val movieEntities = movieDao.getMoviesByPage() return if (movieEntities == null || movieEntities.isEmpty()) { Flowable.empty() } else Flowable.just(movieEntities) } override fun createCall(): Observable> { return movieApiService.fetchMoviesByType() .flatMap { movieApiResponse -> Observable.just( if (movieApiResponse == null) Resource.error("", MovieApiResponse(1, emptyList(), 0, 1)) else Resource.success(movieApiResponse) ) } } }.getAsObservable() } }

Bước 5: Cấu hình ViewModel

Như đã nhắc đến ở trên, Repository chịu trách nhiệm tìm và nạp dữ liệu về, còn về ViewModel thì chịu trách nhiệm việc cập nhật giao diện người dùng đối với các dữ liệu này.

Như ta thấy ở đây, ViewModel cần sử dụng đến MovieDaoMovieService để có thể lấy được dữ liệu từ Api và lưu trữ vào local database. Chính vì vậy nhiệm vụ của chúng ta là inject 2 phụ thuộc kia vào đây để sử dụng thông qua Dagger./* * We are injecting the MovieDao class * and the MovieApiService class to the ViewModel. * */ class MovieListViewModel @Inject constructor( movieDao: MovieDao, movieApiService: MovieApiService) : ViewModel() { /* You can see we are initialising the MovieRepository class here */ private val movieRepository: MovieRepository = MovieRepository(movieDao, movieApiService) /* We are using LiveData to update the UI with the data changes. */ private val moviesListLiveData = MutableLiveData>>() /* * Method called by UI to fetch movies list * */ fun loadMoreMovies() { movieRepository.loadMoviesByType() .subscribe { resource -> getMoviesLiveData().postValue(resource) } } /* * LiveData observed by the UI * */ fun getMoviesLiveData() = moviesListLiveData }

Bước 6: Cấu hình Dagger

Module

Trong Dagger, chúng ta sử dụng Module để chú thích cho các lớp cung cấp phụ thuộc. Mà trong ví dụ này, ta cần phải inject MovieDaoMovieApiService vào trong ViewModel, vậy nên chúng ta sẽ tạo ra 2 Module cung cấp các lớp đó tương ứng là ApiModuleApiModule. Các phương thức được chú thích bằng @Providers để chỉ hàm đó cung cấp gì.

  • ApiModule sẽ cung cấp các phương thức trả về những cài đặt đối với việc gọi Api có sẵn@Module class ApiModule { @Provides @Singleton internal fun provideGson(): Gson { val gsonBuilder = GsonBuilder() return gsonBuilder.create() } @Provides @Singleton internal fun provideCache(application: Application): Cache { val cacheSize = (10 * 1024 * 1024).toLong() // 10 MB val httpCacheDirectory = File(application.cacheDir, "http-cache") return Cache(httpCacheDirectory, cacheSize) } @Provides @Singleton internal fun provideOkhttpClient(cache: Cache): OkHttpClient { val logging = HttpLoggingInterceptor() logging.level = HttpLoggingInterceptor.Level.BODY val httpClient = OkHttpClient.Builder() httpClient.cache(cache) httpClient.addInterceptor(logging) httpClient.addNetworkInterceptor(RequestInterceptor()) httpClient.connectTimeout(30, TimeUnit.SECONDS) httpClient.readTimeout(30, TimeUnit.SECONDS) return httpClient.build() } @Provides @Singleton internal fun provideRetrofit(gson: Gson, okHttpClient: OkHttpClient): Retrofit { return Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .baseUrl("https://api.themoviedb.org/3/") .client(okHttpClient) .build() } @Provides @Singleton internal fun provideMovieApiService(retrofit: Retrofit): MovieApiService { return retrofit.create(MovieApiService::class.java) } }
  • DbModule sẽ cung cấp những phụ thuộc cho việc tương tác với database local thông qua Room library@Module class DbModule { @Provides @Singleton internal fun provideDatabase(application: Application): AppDatabase { return Room.databaseBuilder( application, AppDatabase::class.java, "Entertainment.db") .allowMainThreadQueries().build() } @Provides @Singleton internal fun provideMovieDao(appDatabase: AppDatabase): MovieDao { return appDatabase.movieDao() } }

Xử lý ViewModel

Về cơ bản thì ta sẽ thêm 2 Module trên vào ViewModel, vậy nên cần phải tạo lớp VIewModelFactory để giúp bạn tự động tạo ViewModel trên Activity hoặc Fragment của bạn.

  • ViewModelFactory có một danh sách các provider để có thể taọ bất kì ViewModel nào bị ràng buộc. Fragment hay Activity chỉ cần inject vào factory và lấy ra ViewModel của chúng.@Singleton class ViewModelFactory @Inject constructor(private val viewModels: MutableMap, Provider>) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { val creator = viewModels[modelClass] ?: viewModels.asIterable().firstOrNull { modelClass.isAssignableFrom(it.key) }?.value ?: throw IllegalArgumentException("unknown model class $modelClass") return try { creator.get() as T } catch (e: Exception) { throw RuntimeException(e) } } }
  • ViewModelKey  giúp cho việc ánh xạ các ViewModel của bạn cho phép ViewModelFactory có thể provider hoặc inject chúng.@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) @MapKey annotation class ViewModelKey(val value: KClass)
  • Tiếp tục chúng ta tạo ViewModelModule để cung cấp các bản của ViewModel thông qua Dagger, được sử dụng bởi ViewModelFactory.@Module internal abstract class ViewModelModule { @Binds internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory @Binds @IntoMap @ViewModelKey(MovieListViewModel::class) protected abstract fun movieListViewModel(moviesListViewModel: MovieListViewModel): ViewModel }

Về cơ bản thì chúng ta có thể tóm tắt lại như sau:

  • Chúng ta có thể sử dụng ViewModelModule để định nghĩa các ViewModel
  • ViewModelKey cung cấp cho mỗi một ViewModel một key duy nhất
  • Sau đó Activity hoặc Fragment sẽ sử dụng ViewModeFactory để inject ViewModel tương ứng với nó.

Xử lý trong Activity

Chúng ta sử dụng thư viện dagger-android nên có thể sử dụng Android Injection. ActivityModule sẽ tạo ra Android Injection để các hoạt động được định nghĩa ở đây.@Module abstract class ActivityModule { @ContributesAndroidInjector() abstract fun contributeMainActivity(): MainActivity }

Xử lý AppComponent

Bất kì lớp nào có chú thích @Component sẽ là chỗ để xác định kết nối giữa các Module với các lớp  yêu cầu phụ thuộc. Chúng ta xác định giao diện @Component.Builder sẽ được gọi từ lớp Application để xuyên suốt trong ứng dụng. Điều này sẽ khiến cho lớp đó trở thành AppComponent và tồn tại suốt vòng đời của ứng dụng và được truy cập bất cứ khi nào cần thiết.@Component( modules = [ ApiModule::class, DbModule::class, ViewModelModule::class, ActivityModule::class, AndroidSupportInjectionModule::class] ) @Singleton interface AppComponent { @Component.Builder interface Builder { @BindsInstance fun application(application: Application): Builder fun build(): AppComponent } /* * This is our custom Application class * */ fun inject(appController: AppController) }

Cấu hình lớp Application

Cuối cùng của việc cài đặt Dagger, chúng ta sẽ custom một Application để lưu trữ AppComponent và giữ chúng trong suốt quá trình chạy của ứng dụng.class AppController : Application(), HasActivityInjector { @Inject lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector override fun activityInjector(): DispatchingAndroidInjector? { return dispatchingAndroidInjector } override fun onCreate() { super.onCreate() DaggerAppComponent.builder() .application(this) .build() .inject(this) } }

Đừng quên thêm Application này vào AndroidManifest.xml nhé !!!

Bước 7: Cài đặt trên Activity

Bây giờ chúng ta tạo Activity và hoàn thành các bước trong này nhé, vậy là cũng đã hoàn thành rồiclass MainActivity : AppCompatActivity() { @Inject internal lateinit var viewModelFactory: ViewModelProvider.Factory private lateinit var binding: MainActivityBinding lateinit var moviesListViewModel: MovieListViewModel private lateinit var moviesListAdapter: MoviesListAdapter override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) initialiseView() initialiseViewModel() } private fun initialiseView() { binding = DataBindingUtil.setContentView(this, R.layout.activity_main) moviesListAdapter = MoviesListAdapter(this) binding.moviesList.layoutManager = LinearLayoutManager(applicationContext, LinearLayoutManager.HORIZONTAL, false) binding.moviesList.adapter = moviesListAdapter val startSnapHelper = PagerSnapHelper( object : RecyclerSnapItemListener { override fun onItemSnap(position: Int) { val movie = moviesListAdapter.getItem(position) binding.overlayLayout.updateCurrentBackground(movie.getFormattedPosterPath()) } } ) startSnapHelper.attachToRecyclerView(binding.moviesList) } private fun initialiseViewModel() { moviesListViewModel = ViewModelProviders.of(this, viewModelFactory).get(MovieListViewModel::class.java) moviesListViewModel.getMoviesLiveData().observe(this, Observer { resource -> if (resource!!.isLoading) { displayLoader() } else if (resource.data != null && !resource.data.isEmpty()) { updateMoviesList(resource.data) } else handleErrorResponse() }) moviesListViewModel.loadMoreMovies() } private fun displayLoader() { binding.moviesList.visibility = View.GONE binding.loaderLayout.rootView.visibility = View.VISIBLE } private fun hideLoader() { binding.moviesList.visibility = View.VISIBLE binding.loaderLayout.rootView.visibility = View.GONE } private fun updateMoviesList(movies: List) { hideLoader() binding.emptyLayout.emptyContainer.visibility = View.GONE binding.moviesList.visibility = View.VISIBLE moviesListAdapter.setItems(movies) } private fun handleErrorResponse() { hideLoader() binding.moviesList.visibility = View.GONE binding.emptyLayout.emptyContainer.visibility = View.VISIBLE } }


Trên đây là bài dịch hướng dẫn sử dụng đơn giản về Dagger theo các bước và có code demo khá dễ hiểu. Hi vọng mọi người thích và sử dụng Dagger cho ứng dụng của mình.

Bài viết có nguồn từ https://android.jlelse.eu/7-steps-to-implement-dagger-2-in-android-dabc16715a3a