ahmed
ahmed

Reputation: 527

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

Upvotes: 1

Views: 611

Answers (2)

ahmed
ahmed

Reputation: 527

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.

Upvotes: 1

Faruk
Faruk

Reputation: 1491

Define pagerState at the beginning of setContent and send it as a parameter to IntroPager.

var pagerState = rememberPagerState(pageCount = { 3 })
fun IntroPager(pagerState: PagerState) 

use this for page switching

val scope = rememberCoroutineScope()
scope.launch {
   pagerState.scrollToPage(value)
}

You must add the xml clickListener after setContent. Otherwise you will get this error.

@Composable invocations can only happen from the context of a @Composable

Upvotes: 0

Related Questions