Android – Dagger2 – Basic

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.

Android
スポンサーリンク
Professional Programmer2

コメント