adrielcafe / voyager

🛸 A pragmatic navigation library for Jetpack Compose
https://voyager.adriel.cafe
MIT License
2.48k stars 124 forks source link

[Question] How to manually inject dependency into ScreenModel or StateScreenModel? #142

Open ydhnwb opened 1 year ago

ydhnwb commented 1 year ago

I have ScreenModel and StateScreenModel like this:

class ListScreenModel : ScreenModel {
    val items : List<String>
        get() = (0..99).map { "Item #$it | ${UUID.randomUUID().toString().substringBefore('-')}" }

    override fun onDispose() {
        super.onDispose()
        println("ListScreenModel: dispose list")
    }
}
class DetailScreenModel(
    val index: Int,
    ) : StateScreenModel<DetailScreenModel.State>(State.Loading) {

    sealed class State {
        object Init : State()
        object Loading: State()
        data class Result(val res: String): State()
    }

    //mocking api request with 1s delay
    fun getItem(index: Int) {
        coroutineScope.launch {
            delay(1_000)
            mutableState.value = State.Result("Item #$index")
        }
    }

    override fun onDispose() {
        super.onDispose()
        println("Disposing: DetailScreenModel")
    }
}

I want my ScreenModel or StateScreenModel have an instance to NoteDataSource, like this:

class DetailScreenModel(
    val index: Int,
    val noteDataSource: NoteDataSource
    ) : StateScreenModel<DetailScreenModel.State>(State.Loading) {

    sealed class State {
        object Init : State()
        object Loading: State()
        data class Result(val res: String): State()
    }

    fun getAllNotes() {
        val notes = noteDataSource.getAllNotes()
        //...add it to the State.Result
    }

    ....
}

But I don't know where to add. Is it safe to add the instance inside the Screen like this below?

        val screenModel = rememberScreenModel {
            DetailScreenModel(indexPassed, NoteDataSource()) 
        }

Or might be there is a better solution?

Qw4z1 commented 9 months ago

That is how I do it atm and it works just fine as long as you don't need to share instances. You're just passing in an instance via a constructor. Nothing special.

If you want to share instances you might want to use a CompositionLocalProvider or something. I recommend looking at the source code for Koin to get inspiration on how a DI framework does it. =)

The only think to watch out for is that you might need a unique screen id on the screen to tell Voyager that it's a different screen. Otherwise your model might be resued with the same id as you used in the constructor (someone please correct me if I'm wrong here).

Also, unless you need to use getItem in more places (e.g. refresh), you could just fetch the item in the init function.


init {
        coroutineScope.launch {
            delay(1_000)
            mutableState.value = State.Result("Item #$index")
        }
}