Search code examples
androidkotlinandroid-jetpack-composeandroid-viewmodeldagger-hilt

Jetpack Compose How to access dependency injected view model from everywhere without sending the view model as a parameter?


I am currently developing a customized PopupView to behave like ModalBottomSheetLayout but more manageable and advanced. I want to be able to access this PopupManager class from anywhere without passing it as a parameter to other functions so that the structure I will create is more robust and more manageable. Here is the code:

@HiltViewModel
class PopupManager @Inject constructor(
    initialValue: PopupViewStateValue = PopupViewStateValue.Hidden
) : ViewModel() {
    val popupViewState = mutableStateOf(initialValue)
}

class ActivityHelpCenterCompose : ComponentActivity() {

    private val popUpManager: PopupManager by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {

            Box {

                HelpCenterPageView(
                    modifier = Modifier.zIndex(1f),
                    popupManager = popUpManager
                )

                PopupView(
                    popupManager = popUpManager,
                    modifier = Modifier.zIndex(2f)
                ) {

                }

            }

        }

    }

}

As it can be seen, I am sending popup manager as a parameter to the composable functions which are HelpCenterPageView and PopupView and that is exactly what I want to avoid.

This is the code for the HelpCenterPageView and I am opening my popupview in the extension function that I wrote which is popupManager.expand in the NavigationBarView clickable.

@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@Composable
fun HelpCenterPageView(
    popupManager: PopupManager,
    modifier: Modifier?,
) {

    Scaffold(
        modifier = modifier ?: Modifier,
        topBar = {
        NavigationBarView(
            title = stringResource(id = R.string.profile_page_help),
            modifier = Modifier.clickable {
                popupManager.expand()
            }
        )
    }) {

        Column(
            modifier = Modifier
                .fillMaxSize()
                .verticalScroll(rememberScrollState())
        ) {

            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(top = 22.dp, start = 22.dp, end = 22.dp)
                    .background(
                        color = colorResource(
                            id = R.color.fz_background
                        )
                    )
            ) {

                TextView(
                    text = stringResource(id = R.string.help_page_training_center),
                    font = Font.F17SB.textStyle,
                    color = colorResource(id = R.color.fz_dark_colour),
                    modifier = null
                )

                Column(modifier = Modifier.padding(top = 22.dp)) {

                    UpComingTrainingView(
                        training = Trainings(),
                        onShareClick = { },
                        onWatchClick = { },
                        modifier = null
                    )

                    Spacer(modifier = Modifier.padding(top = 22.dp))

                    HelpCenterSectionView(
                        sectionTitle = stringResource(id = R.string.online_zoom_training_page_next_trainings),
                        sectionImage = painterResource(id = R.drawable.online_educations_icon),
                        modifier = null
                    ) {}

                    Spacer(modifier = Modifier.padding(top = 22.dp))

                    HelpCenterSectionView(
                        sectionTitle = stringResource(id = R.string.online_zoom_training_page_previous_trainings),
                        sectionImage = painterResource(id = R.drawable.education_records_history_property),
                        modifier = null
                    ) {

                    }

                }

            }

            Spacer(
                modifier = Modifier
                    .padding(top = 22.dp)
                    .fillMaxWidth()
                    .height(10.dp)
                    .background(
                        color = colorResource(
                            id = R.color.fz_cool_gray02
                        )
                    )
            )

            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(top = 22.dp, start = 22.dp, end = 22.dp)
            ) {

                TextView(
                    text = stringResource(id = R.string.profile_page_help),
                    font = Font.F17SB.textStyle,
                    color = colorResource(id = R.color.fz_dark_colour),
                    modifier = null
                )

                Spacer(modifier = Modifier.padding(top = 22.dp))

                HelpCenterSectionView(
                    sectionTitle = stringResource(id = R.string.help_page_live_desk),
                    sectionImage = painterResource(id = R.drawable.settings_whatsapp_icon),
                    modifier = null
                ) {

                }

                Spacer(modifier = Modifier.padding(top = 22.dp))

                HelpCenterSectionView(
                    sectionTitle = stringResource(id = R.string.help_page_feature),
                    sectionImage = painterResource(id = R.drawable.request_suggestion_icon),
                    modifier = null
                ) {

                }

                Spacer(modifier = Modifier.padding(top = 22.dp))

                HelpCenterSectionView(
                    sectionTitle = stringResource(id = R.string.help_page_bug),
                    sectionImage = painterResource(id = R.drawable.bug_report_icon),
                    modifier = null
                ) {

                }

                Spacer(modifier = Modifier.padding(top = 22.dp))

                HelpCenterSectionView(
                    sectionTitle = stringResource(id = R.string.help_page_call_center),
                    sectionImage = painterResource(id = R.drawable.settings_call_property),
                    modifier = null
                ) {

                }

            }

            Column(
                modifier = Modifier
                    .padding(
                        top = 42.dp,
                        bottom = 42.dp,
                        start = 22.dp,
                        end = 22.dp
                    )
                    .fillMaxWidth(),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {

                TextView(
                    text = "${R.string.help_page_version.localized()} ${BuildConfig.VERSION_NAME} ( ${BuildConfig.VERSION_CODE} )",
                    font = Font.F15SB.textStyle,
                    color = colorResource(id = R.color.fz_dark_colour),
                    modifier = null
                )

            }

        }

    }

}

Also, there are cases that I will need to open different popups in the same page for example 5 different popups. Do I have to assign different variables for each of the popups or is there a more generic way to handle this ?


Solution

  • You can use CompositionLocalProvider for this. This is a generic example.

    1. Create Composition local with type of viewmodel.This will be holding viewmodel instance

      val MyCompositionLocal = staticCompositionLocalOf<MyViewModel> {
           error("No  Viewmodel provided")
       }
      
    2. In fragment /activity, you should wrap composable view with CompositionLocalProvider and provide the viewmodel instance to the view tree.

      class MyFragment : Fragment() {
      
       private val viewModel by viewModels<MyViewModel>()
      
       override fun onCreateView(
           inflater: LayoutInflater,
           container: ViewGroup?,
           savedInstanceState: Bundle?
       ): View {
           return ComposeView(requireContext()).apply {
               setContent {
                   CompositionLocalProvider(MyCompositionLocal provides viewModel) {
                       MyComposeScreen()
                   }
               }
           }
       }
      }
      
    1. In any composables in activity/fragment tree, you get viewmodel instance using CompositionLocal.current

       @Composable
       fun MyComposeScreen() {
       val viewmodel =  MyCompositionLocal.current //This will give viewmodel instance. 
       }