7

I work in a project that's slowly adopting Jetpack Compose. It's mostly a single Activity multiple Fragments app where we use the Android's navigation component to handle transitions to each screen (Fragment). Whenever we can, we are only replacing the Fragments' XML layout with Composables.

The cases up until now were handling like the following:

A. Fragments show composables and handle navigation:

class ScreenFragment : Fragment() {

    // For observing events that trigger navigation
    private val viewModel by lazyViewModel { ScreenViewModel() }

    override fun onCreateView( ... ): View {
        return ComposeView(requireContext()).apply {

            setViewCompositionStrategy(DisposeOnLifecycleDestroyed(viewLifecycleOwner))

            setContent {
                AppTheme {
                    Screen(onBackPressed = { findNavController().navigateUp() })
                }
            }
        }
    }    
    ...

}

B. Composable that handles everything else UI related:

@Composable
fun CreatePassword(
    onBackPressed: () -> Unit,
) {

    // For observing UI states and events
    val viewModel: CreatePasswordViewModel = viewModel()
    ...
}

As you can see, we have a reference for the screen's ViewModel in both our Fragments and our Composables. Up until now, this has been working fine, the composable viewModel() function was always returning the same existing instance of the Fragment's ViewModel.

The problems came when we needed the reference of a ViewModel scoped to an Activity on the composable:

  1. We create the ViewModel on the Activity:
class MainActivity : AppCompatActivity() {

    private val viewModel by lazyViewModel { MainViewModel() }
    ...
}
  1. Get a reference for the MainActivity's ViewModel on the Fragment like
class MainFragment : Fragment() {

    private val viewModel: MainViewModel by viewModels(::requireActivity)
    ...
}
  1. We get the reference of the ViewModel for composables as shown above (item B)

By doing so the Fragment has the same instance of the Activity's but the composable doesn't.

My questions is, would it be possible to get the reference of the Activity's ViewModel inside the composable? For now I'm simply passing down the Fragment's ViewModel as a parameter to my main composable screen.

4
  • 2
    viewModel() takes a ViewModelStoreOwner as a parameter that controls the scope of that ViewModel, just like your lazyViewModel. If you want to scope a ViewModel to your activity, why aren't you passing in your activity to viewModel()? Commented Feb 11, 2022 at 18:16
  • why you don't pass the viewmodel as an argument to CreatePassword composble ? Commented Feb 11, 2022 at 18:37
  • @ianhanniballake, I tried passing my Activity to viewModel() but I ended up getting a new instance of the viewModel too. I was getting my Activity as done here: stackoverflow.com/a/68423182/8189765 Commented Feb 11, 2022 at 20:53
  • @MobinYardim That's exactly what I'm doing now. Commented Feb 11, 2022 at 20:55

2 Answers 2

4

I managed to pass down the Activity's ViewModel by providing a ViewModelStoreOwner to my composable as following:

class ScreenFragment : Fragment() {

    // For observing events that trigger navigation
    private val viewModel by lazyViewModel { ScreenViewModel() }

    override fun onCreateView( ... ): View {
        return ComposeView(requireContext()).apply {

            setViewCompositionStrategy(DisposeOnLifecycleDestroyed(viewLifecycleOwner))

            setContent {
                val viewModelStoreOwner =
                    compositionLocalOf<ViewModelStoreOwner> { requireActivity() }
    
                AppTheme {
                    CompositionLocalProvider(
                        LocalViewModelStoreOwner provides viewModelStoreOwner.current
                    ) {
                        Screen(onBackPressed = { findNavController().navigateUp() })
                    }
                }
            }
        }
    }    
    ...

}
Sign up to request clarification or add additional context in comments.

Comments

1

Sounds like a classic dependency problem, at this point I would consider using some sort of Dependency Injection library and make sure to provide the viewModel instance as a singleton.

I can recommend Koin: https://insert-koin.io/

Or, more recently and trendy Hilt: https://developer.android.com/training/dependency-injection/hilt-android

Once using a good DI you can get an instance of the viewModel in your compose functions.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.