1

I used the Dagger 2.17 to get the application context in the repository to access the resources:

ContextInjection.kt:

@Module
class ContextModule(private val context: Context) {

    @Singleton
    @Provides
    fun providesContext(): Context {
        return context
    }
}

@Singleton
@Component(modules = [ContextModule::class])
interface ContextComponent {
    fun inject(): Context
}

Initialization.kt:

class Initialization : Application() {

    override fun onCreate() {
        super.onCreate()

        contextComponent = DaggerContextComponent.builder()
            .contextModule(ContextModule(this.applicationContext))
            .build()
    }

    companion object { // for access in repository
        lateinit var contextComponent: ContextComponent
    }
}

Repository.kt:

@Singleton
class Repository {

    @Inject
    lateinit var context: Context

    /** the method is invoked from view model class */
    fun loadList(pageIndex: Int): List<String> {
        context = Initialization.contextComponent.inject()
        val input = context.assets.open("tab1/item1/description.txt")
        ...
    }
}

ViewModel.kt:

class PageViewModel : ViewModel() {

    @NonNull
    private val repository = Repository()

    private val pageIndex = MutableLiveData<Int>()

    val items = Transformations.map<Int, List<String>>(pageIndex) { index: Int ->
        repository.loadList(index)
    }

    fun setIndex(index: Int) {
        pageIndex.value = index
    } 
}

This works, but I have the next question: is there any other (better) way to get the context in the repository using a Dagger?

Note: I am confused by the static invoke:

context = Initialization.contextComponent.inject()

Not sure if this is good practice.

Thank you for any answer/comment!

alexrnov
  • 2,346
  • 3
  • 18
  • 34

1 Answers1

5

You can use a dependency which provides these assets to the repository. And this dependency can contain a reference to the context. So your repository can simply query this dependency to get the assets it requires.

Here's a gist of it:

AssetProvider:

class AssetProvider @Inject constructor(
    private val context: Context
) {
    
    fun getDescription() = context.assets.open("tab1/item1/description.txt")
}

Repository:

@Singleton
class Repository @Inject constructor(
    private val assetProvider: AssetProvider
) {

    fun loadList(pageIndex: Int): List<String> {
        val input = assetProvider.getDescription()
        ...
    }
}

I like having repositories that have minimal dependency on Android specific stuff. So the repository logic is agnostic to the platform it runs on. This also helps in unit tests where you don't have to inject context to test your repository.

Saurabh Thorat
  • 18,131
  • 5
  • 53
  • 70
  • Thank you for the answer! Clarifying question, if possible. The repository is initialized in the view model (added an example in the question). Does this mean I need to pass the assetProvider to the view model first? – alexrnov Aug 01 '20 at 11:55
  • 1
    If you're using dependency injection, you can directly inject your repository into your viewmodel constructor, no need to initialize it in your viewmodel. – Saurabh Thorat Aug 01 '20 at 12:09
  • 1
    Thank you! You will find a lot of complex solutions out there. But this is what I was looking for – Natan Lotério Feb 16 '21 at 21:38