In previous entry, I explained introduction of Dagger2 in Android (Android – DI – Dagger).
In this time, from Google Document (Using Dagger in Android apps), explain more details of Dagger2.
Component and Module
Component and Module is main part of DI.
- Component : Bridge between Activity, Fragment and Module. 1 Activity 1 Component is basic
- Module : Functions, covered one function.
Sample
Components, and Application class
ApplicationComponent.kt
Main Component. It contains Subcomponent and modules (Submodule as modules, and create Factory)
@Component(modules=[NetworkModule::class, DataSubComponentModule::class]) interface ApplicationComponent { // Main Component injection // fun inject(activity: MainActivity) fun dataComponent() : DataSubComponent.Factory }
DataSubComponent.kt
@ActivityScope @Subcomponent interface DataSubComponent { // Factory that is used to create instances of this subcomponent @Subcomponent.Factory interface Factory { fun create(): DataSubComponent } // This tells Dagger that MainActivity requests injection from LoginComponent // so that this subcomponent graph needs to satisfy all the dependencies of the // fields that MainActivity is injecting fun inject(activity: MainActivity) // Other injection fragment etc... fun inject(fragment: DataFragment) }
Add inject codes (All Activity and Fragment to use this component), Prepare Factory to access from ApplicationComponent
Modules
DataSubComponentModule.kt
@Module(subcomponents = [DataSubComponent::class]) class DataSubComponentModule {}
NetworkModule.kt
@Module class NetworkModule { // @Provides tell Dagger how to create instances of the type that this function // returns (i.e. DataRetrofitService). // Function parameters are the dependencies of this type. @Singleton @Provides fun provideDataRetrofitService() : DataRetrofitService { return Retrofit.Builder() .baseUrl("https://dj110.it") .build() .create(DataRetrofitService::class.java) } @Provides fun provideDataRetrofitService(okHttpClient: OkHttpClient) : DataRetrofitService { return Retrofit.Builder() .baseUrl("https://dj110.it") .build() .create(DataRetrofitService::class.java) } }
Provides Service code to call them from Component.
Model and ViewModel
Data.kt
data class Data(val name: String)
This is just data. Data from Retrofit response.
DataViewModel.kt
@ActivityScope class DataViewModel @Inject constructor( private val dataRepository: DataRepository ){}
ViewModel (of MVVM). Manage data and communicate with Model and View (Activity, Fragment) This can be test in JUnit general Test
Scope
ActivityScope.kt
// Definition of a custom scope called ActivityScope @Scope @Retention(value = AnnotationRetention.RUNTIME) annotation class ActivityScope
Repository and Service
DataRepository.kt
@Singleton class DataRepository @Inject constructor( private val localDataSource: DataLocalDataSource, private val remoteDataSource: DataRemoteDataSource ) { } class DataLocalDataSource @Inject constructor() {} class DataRemoteDataSource @Inject constructor(private val dataService: DataRetrofitService) {}
Repository is data manipulation codes
DataRetrofitService.kt
interface DataRetrofitService { @GET("/data") fun getData() : Call<Data> }
This is Retrofit Service codes, define API request and response
Activity and Fragment
DataFragment.kt
class DataFragment : Fragment() { @Inject lateinit var dataViewModel: DataViewModel override fun onAttach(context: Context) { super.onAttach(context) (activity as MainActivity).dataSubComponent.inject(this) // Now you can access dataViewModel here and onCreateView too // (shared instance with the Activity and the other Fragment) } }
Have One ViewModel, and Get component from activity. And inject
MainActivity.kt
class MainActivity : AppCompatActivity() { // You want Dagger to provide an instance of LoginViewModel from the graph // Field injection should only be used in Android framework classes where constructor injection cannot be used. @Inject lateinit var dataViewModel : DataViewModel // Reference to data graph lateinit var dataSubComponent: DataSubComponent // Activity -> ViewModel -> Repository -> DataSource -> Retrofit override fun onCreate(savedInstanceState: Bundle?) { // Make Dagger instantiate @Inject fields in this activity // Main Component injection //(applicationContext as MyApplication).appComponent.inject(this) // dataViewModel is available // Subcomponent injection dataSubComponent = (applicationContext as MyApplication).appComponent.dataComponent().create() dataSubComponent.inject(this) // dataViewModel is available super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }
Have one ViewModel, and have Component from Application
MyApplication.kt
class MyApplication : Application() { val appComponent = DaggerApplicationComponent.create() }
Register ApplicationComponent using Dagger.
Test
Let’ try ViewModel test (no concrete implementation)
ViewModel test is simple JUnit test. In Android, there are 2 types of test by default
- androidTest
- test
We do test for ViewModel in test.
build.gradle
testImplementation 'org.mockito:mockito-core:3.3.3' testImplementation 'android.arch.core:core-testing:1.1.1' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
This is test dependency. Use mockito for API mock.
DataViewModelTest.kt
class DataViewModelTest { @get:Rule var instantExecutorRule = InstantTaskExecutorRule() private lateinit var repository: DataRepository private lateinit var viewModel: DataViewModel @Before fun setUp() { repository = mock(DataRepository::class.java) viewModel = DataViewModel(repository) } @Test fun testDataViewMode() { } }
Use InstantTaskExecutorRule to use code as synchronous.
ViewModel is defined in @Before. API is covered by Mock object.
コメント