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

Viewed 38

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 ?

2 Answers

You can pass a Unit as a parameter.

@Composable
fun HelpCenterPageView(
    modifier: Modifier = Modifier, 
    onClick: () -> Unit, 
)

and

modifier = Modifier.clickable {
    onClick() 
}

and then

HelpCenterPageView(
    modifier = Modifier.zIndex(1f),
){
    popupManager.expand() 
} 

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. 
     }
    
Related