Search code examples
androidkotlinandroid-jetpack-composeandroid-jetpack

How to Pass Click events from Parent XML view to inside Jetpack Compose


I have an existing app made with XML and Kotlin. When updating, I wanted to learn Jetpack Compose, so I added a HorizontalPager inside a parent XML screen using ComposeView.

In XML

    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/my_composable"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:visibility="visible"
        app:layout_constraintBottom_toTopOf="@+id/divider"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

Then in Kotlin I did

binding.value.myComposable.setContent {
            IntroPager()
        }

My Compose Function

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun IntroPager(){
    val pagerState = rememberPagerState(pageCount = {
        3
    })
    
    Box(modifier = Modifier.fillMaxSize()) {

        HorizontalPager(
            state = pagerState,
            modifier = Modifier.fillMaxSize()

        ) { page ->
            when (page){
                0-> Page1("")
                1-> Page2("")
                2-> Page3("")
                else -> Page1(" from else $page")
            }
        }

        Row(
            Modifier
                .wrapContentHeight()
                .fillMaxWidth()
                .align(Alignment.BottomCenter)
                .padding(bottom = 8.dp),
            horizontalArrangement = Arrangement.Center
        ) {
            repeat(pagerState.pageCount) { iteration ->
                val color =
                    if (pagerState.currentPage == iteration) Color(R.color.theme_color_secondary) else Color(R.color.dark_grey)
                Box(
                    modifier = Modifier
                        .padding(2.dp)
                        .clip(CircleShape)
                        .background(color)
                        .size(16.dp)
                )
            }
        }

    }

}

Now, based on some user interaction in the XML part of the screen, I want to pass a click event from an XML onClickListener to go to the next page in my HorizontalPager in Compose. Basically Passing XML clickEvent to Compose.

I could not find any documentation on how to achieve this.

I tried implementing interface callback, but was unsuccessful


Solution

  • The only working solution I could figure out was to use a shared ViewModel. Here is what I did:

    I created a ViewModel class with a function to increment the page index:

    class PagerViewModel : ViewModel() {
        val page = mutableIntStateOf(0)
    
        fun nextPage(){
            if (page.value<=2)
                page.value++
        }
    }
    

    I then linked this ViewModel in my parent Activity. On click events in XML, I called the pagerViewModel.nextPage() function.

    Then in my Compose code:

    @OptIn(ExperimentalFoundationApi::class)
    @Composable
    fun IntroPager(pagerViewModel: PagerViewModel){
        val pagerState = rememberPagerState(pageCount = {
            3
        })
        Box(modifier = Modifier.fillMaxSize()) {
    
            HorizontalPager(
                state = pagerState,
                modifier = Modifier.fillMaxSize()
    
            ) { page ->
                Log.d("IntroPager", "IntroPager: "+pagerViewModel.page.value)
                when (page){
                    0-> {
                        Page1("")
                    }
                    1-> {
                        Page2("")
                    }
                    2-> {
                        Page3("")
                    }
                    else -> Page1(" from else $page")
                }
            }
            Row(
                Modifier
                    .wrapContentHeight()
                    .fillMaxWidth()
                    .align(Alignment.BottomCenter)
                    .padding(bottom = 8.dp),
                horizontalArrangement = Arrangement.Center
            ) {
                repeat(pagerState.pageCount) { iteration ->
                    val color =
                        if (pagerState.currentPage == iteration) Color(R.color.theme_color_secondary) else Color(R.color.dark_grey)
                    Box(
                        modifier = Modifier
                            .padding(2.dp)
                            .clip(CircleShape)
                            .background(color)
                            .size(16.dp)
                    )
                }
            }
            LaunchedEffect(pagerViewModel.page.value) {
                pagerState.animateScrollToPage( pagerViewModel.page.value)
            }
            LaunchedEffect(pagerState){
                snapshotFlow { pagerState.currentPage }.collect { page ->
                    pagerViewModel.page.intValue = page
                }
            }
        }
    }
    

    This links the ViewModel page index to the HorizontalPager state. LaunchedEffect(pagerViewModel.page.value) {} Launches a coroutine whenever the value of pageIndex changes, it animates scrolling to that page.

    I wanted a straight forward way to connect click events between XML and Compose. But I couldn't find a simple solution, so I resorted to using the shared ViewModel to update state.