Search code examples
androiddependency-injectionandroid-jetpack-composeandroid-viewmodelkoin

Koin's getStateViewModel is deprecated - what is the alternative?


I have the following implementation on my Fragment -

class HeroesDetailsFragment : Fragment() {

    private val navArgs: HeroesDetailsFragmentArgs by navArgs()

    private val heroesDetailsViewModel: HeroesDetailsViewModel by stateViewModel(state = { navArgs.toBundle() })

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        binding = FragmentHeroDetailsBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        initListeners()
        observeUiState()
        observeUiAction()
    }
}

My HeroesDetailsViewModel looks like this -

class HeroesDetailsViewModel(
    private val savedStateHandle: SavedStateHandle,
    private val heroesDetailsRepository: HeroesDetailsRepository
) : ViewModel() {

    private fun getArgsModel() = HeroesDetailsFragmentArgs.fromSavedStateHandle(savedStateHandle)

    init {
        val navArgs = getArgsModel()
        getAdditionalHeroDetails(navArgs.heroModel.id)
        observeUiEvents()
    }
}

And in my ViewModelModule I declare the following

val viewModelModule = module {
    // ...
    viewModel { params ->
        HeroesDetailsViewModel(params.get(), get())
    }
}

As you can see, I utilized the stateViewModel extension for Fragments that allows me to create a StateViewModel. The issue is that when trying to use the same functionality in Compose:

@Destination
@Composable
fun HeroDetailsScreen(
    model: HeroesListModel,
    viewModel: HeroesDetailsViewModel = getStateViewModel() //provides deprecation error
) {

}

I get the following deprecation message -

getStateViewModel will be merged to sharedViewModel - no need anymore of state parameter

I did not find any good references on this topic, and it seems weird for me because the Fragment extension stateViewModel is completely fine and not deprecated so I am missing information on what should I do to replace it.

My goal is to inject a ViewModel with state parameters that will initialize the SavedStateHandle object. Currently I am using Koin DI, will switch in the future to Dagger-Hilt so it would be also a nice bonus to see the solution both in Koin and in Dagger-Hilt.


Solution

  • So I finally found a way to inject the ViewModel with dynamic information coming from the Fragment. I was looking at the old Fragment / Activity way which includes handling bundles, but in Compose it's much easier as we don't need to use the SavedStateHandle object because we can handle process death by the rememberSaveable { } block, which decouples the need to inject a ViewModel with dynamic information and the need to save information for process death.

    This leaves the ViewModel to only ask for the relevant model and not bother handling process death. Just pure information.

    class HeroesDetailsViewModel(
        heroListModel : HeroesListModel,
        private val heroesDetailsRepositoryImpl: HeroesDetailsRepositoryImpl
    ) : ViewModel() {
    
        init {
            getAdditionalHeroDetails(heroListModel.id)
            observeUiEvents()
        }
    }
    

    So I added a model that will be injected when needed via the parameters field -

    @Destination
    @Composable
    fun HeroDetailsScreen(
        model: HeroesListModel,
        viewModel: HeroesDetailsViewModel = koinViewModel(parameters = { ParametersHolder(mutableListOf(model)) })
    ) {
    
    }
    

    And in my DI module the implementation actually is left the same -

    val viewModelModule = module {
        viewModelOf(::HeroesViewModel)
        viewModelOf(::HeroesListItemViewModel)
        viewModel { params ->
            HeroesDetailsViewModel(params.get(), get())
        }
    }
    

    Hopefully this saves some time for other people in the future 💪🙂