Hi guys, I am seeing very weird issue in `@Composa...
# compose-android
k
Hi guys, I am seeing very weird issue in
@Composable
function. I am collecting data in flow. When I tried the
@Composable
function inside the activity. It's working fine without any problem, but when I write outside the class it not working. I don't understand the problem. Code in the thread
1. This code is working fine
Copy code
package com.hub.myapplication

import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.hub.myapplication.databinding.ActivityMainBinding
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {

    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    private val viewModel by viewModels<MainActivityViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.CREATED) {
                viewModel.showBottomSheetContent.collect {
                    binding.bottomSheetComposeView.setContent {
                        ModalBottomSheetSample()
                    }
                }
            }
        }

        binding.buttonComposeView.setContent {
            ButtonViewContent()
        }
    }

    @Composable
    fun ButtonViewContent() {
        val scope = rememberCoroutineScope()
        Column(Modifier.fillMaxSize()) {
            Button(
                onClick = {
                    scope.launch {
                        viewModel.showBottomSheetContent.emit(true)
                    }
                }
            ) {
                Text(text = "Open Bottom Sheet")
            }
        }
    }

    @Composable
    @OptIn(ExperimentalMaterialApi::class)
    fun ModalBottomSheetSample() {
        val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden, skipHalfExpanded = true)
        val scope = rememberCoroutineScope()
        SideEffect {
            scope.launch {
                sheetState.show()
            }
        }
        ModalBottomSheetContent(sheetState)
    }

    @OptIn(ExperimentalMaterialApi::class)
    @Composable
    fun ModalBottomSheetContent(sheetState: ModalBottomSheetState) {
        ModalBottomSheetLayout(
            sheetState = sheetState,
            sheetContent = {
                LazyColumn {
                    items(5) {
                        ListItem(
                            icon = {
                                Icon(
                                    Icons.Default.Favorite,
                                    contentDescription = null
                                )
                            },
                            text = { Text("Item $it") },
                        )
                    }
                }
            }
        ) {}
    }
}
2. This code not works correctly
Copy code
package com.hub.myapplication

import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.hub.myapplication.databinding.ActivityMainBinding
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {

    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    private val viewModel by viewModels<MainActivityViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.CREATED) {
                viewModel.showBottomSheetContent.collect {
                    binding.bottomSheetComposeView.setContent {
                        ModalBottomSheetSample()
                    }
                }
            }
        }

        binding.buttonComposeView.setContent {
            ButtonViewContent()
        }
    }

    @Composable
    fun ButtonViewContent() {
        val scope = rememberCoroutineScope()
        Column(Modifier.fillMaxSize()) {
            Button(
                onClick = {
                    scope.launch {
                        viewModel.showBottomSheetContent.emit(true)
                    }
                }
            ) {
                Text(text = "Open Bottom Sheet")
            }
        }
    }
}

@Composable
@OptIn(ExperimentalMaterialApi::class)
fun ModalBottomSheetSample() {
    val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden, skipHalfExpanded = true)
    val scope = rememberCoroutineScope()
    SideEffect {
        scope.launch {
            sheetState.show()
        }
    }
    ModalBottomSheetContent(sheetState)
}

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ModalBottomSheetContent(sheetState: ModalBottomSheetState) {
    ModalBottomSheetLayout(
        sheetState = sheetState,
        sheetContent = {
            LazyColumn {
                items(5) {
                    ListItem(
                        icon = {
                            Icon(
                                Icons.Default.Favorite,
                                contentDescription = null
                            )
                        },
                        text = { Text("Item $it") },
                    )
                }
            }
        }
    ) {}
}
z
define “not correctly” - does it crash? Not show what you expect? Etc
General note though: you probably don’t want to launch a coroutine in a
SideEffect
- a new coroutine will be launched on every recomposition.
k
No it does not crash. It only shows the bottomsheet once and after it does not appear.
So what is the best way to show the bottomsheet?
z
I’m not 100% sure, but my guess is that the unstable
this
implicitly passed to the composables when they’re defined as members of your activity class are preventing some composable from skipping on repeated calls to setContent, so it’s working by accident because something is relying on recomposition to re-show the sheet. When you move the composables out of the class, they no longer capture
this
, and so they can skip.
k
Yeah it's a problem then. Do you have any resources for this to solve this kind of issue ?