Jetpack compose doesn't recompose on mutableStateOf change - android

I wanted to build a very simple demo. A button which you can click, and it counts the clicks.
Code looks like this:
class MainActivity : ComponentActivity() {
private var clicks = mutableStateOf(0)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Surface(color = MaterialTheme.colors.background) {
NewsStory(clicks.value) { onClick() }
}
}
}
private fun onClick() {
clicks.value++
}
}
#Composable
fun NewsStory(clicks: Int, onClick: () -> Unit) {
Column(modifier = Modifier.padding(8.dp)) {
Button(onClick = onClick) {
Text("Clicked: $clicks")
}
}
}
From my understanding this should be recomposed everytime the button is clicked, as clicks is changed.
But it does not work, any ideas what I'm doing wrong here?
I'm on androidx.activity:activity-compose:1.3.0-beta01, kotlin 1.5.10 and compose version 1.0.0-beta08

You need to use the "remember" keyword for the recomposition to happen each time, as explained here: https://foso.github.io/Jetpack-Compose-Playground/general/state/
In short, your composable would look like this:
#Composable
fun NewsStory (){
val clickState = remember { mutableStateOf(0) }
Column (modifier = Modifier.padding(8.dp)) {
Button(
onClick = { clickState.value++ }) {
}
Text("Clicked: $clickState.value.toString()")
}
}

Related

How to recompose a composable after an event occured in the main Activity?

I created an event listener to catch when a physical button is pressed, and it works well.
But I would want to update a list used in a LazyColumn
class MainActivity : ComponentActivity() {
#OptIn(ExperimentalComposeUiApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Theme {
Surface(
modifier = Modifier .fillMaxSize(),
color = MaterialTheme.colors.background
) {
Column(modifier = Modifier.fillMaxSize()) {
Greeting("Android")
}
}
}
}
}
#SuppressLint("RestrictedApi")
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
// Handling event to get a text (type String)
// ......
//then updating my list
myList+=newValue
}
var myList: List<String> = mutableListOf()
#OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
#Composable
fun Greeting(name: String, paramBarcode: String) {
var mutableList by remember {mutableStateOf(myList)}
Button(onClick = {
myList+= "new item"
mutableList = myList
}) {
Text(text = "Add")
}
LazyColumn(Modifier.fillMaxSize() .padding(16.dp)
) {
stickyHeader {Row(Modifier.fillMaxSize() .background(Color.Green)
) {
TableCell(text = "Code", width = 264)
}
}
itemsIndexed(items = mutableList, itemContent = {
index, item ->
Row(Modifier.fillMaxSize(),
) {
TableCell(text = item, width = 256)
}
})
}
}
If I try to add or remove an element of the list from my composable, everything is fine, but I can't get the same behaviour from my event.
I also tried to pass the list as a parameter to my composable, but it didn't help at all.
Try using a SnapshotStateList instead of an ordinary list as mutable state.
So instead of this
var myList: List<String> = mutableListOf()
try this,
var myList = mutableStateListOf("Item1")
and instead of using your ordinary list setting it with a new one every time you add an item, you can simply add new elements to a SnapshotStateList.
I modified your code and any changes coming from outside Greeting and inside of it reflects to LazyColumn
#Composable
fun Greeting(
list: SnapshotStateList<String>
) {
Button(onClick = {
list.add("Item inside greeting")
}) {
Text(text = "Add")
}
LazyColumn(
Modifier
.fillMaxSize()
.padding(16.dp)
) { ... }
}
Usage
setContent {
Column {
Button(onClick = {
myList.add("New Item Outside greeting")
}) {}
Greeting(myList)
}
}
I've got a very good answer from #x.y
but then it lost value at recomposition.
Here is the updated code to handle my first problem while keeping the state of the list :
var MainList: SnapshotStateList<String> = SnapshotStateList()
class MainActivity : ComponentActivity() {
var myList: SnapshotStateList<String> = MainList
#OptIn(ExperimentalComposeUiApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Theme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Column(modifier = Modifier.fillMaxSize()) {
Greeting("Android", myList)
}
}
}
}
}
}
#SuppressLint("RestrictedApi")
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
MainList.add("$barcode")
}
}
#OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
#Composable
fun Greeting(name: String, paramBarcode: String, theList: SnapshotStateList<ScanItem>) {
var mutableList by remember { mutableStateOf(theList) }
Column() {
Button(onClick = {
MainList.add("ABC0000000000001")
}) {
Text(text = "Add item")
}
Row() {
Button(onClick = {
MainList.add("add00001")
}) {
Text(text = "Add")
}
Button(
onClick = { MainList.clear() }
) {
Text("Empty list")
}
}
}
}
And I can pass mutableList as the parameter in itemsIndexed, and it will keep the state on recompose.

Compose navigation lose state after pop screen (navigate for network success)

I am using compose navigation with single activity and no fragments.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MobileComposePlaygroundTheme {
Surface(color = MaterialTheme.colors.background) {
val navController = rememberNavController()
NavHost(navController, startDestination = "main") {
composable("main") { MainScreen(navController) }
composable("helloScreen/{data}") { HelloScreen() }
}
}
}
}
}
}
#Composable
private fun MainScreen(navController: NavHostController) {
val viewModel = viewModel()
val loginState by viewModel.loginState
LaunchedEffect(loginState) {
if(loginState is State.Success){
navController.navigate("helloScreen/data")
}
}
Column {
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { viewModel.login() }, //viewmodel will change loginState
modifier = Modifier.padding(8.dp)
) {
Text(text = "Go To HelloScreen")
}
}
}
#Composable
fun HelloScreen() {
Log.d("TAG", "HelloScreen")
Text("Hello Screen")
}
This post is hidden. You deleted this post 7 mins ago.
i have some problem
MainScreen(loginState)-> LaunchedEffect(loginState) -> HelloScreen -> back button -> MainScreen
when i pop HelloScreen
MainScreen will be recompose for
loginState, LaunchedEffect(netWorkState) will navigate(HelloScreen) again
how can i change code right of navigate
Use Disposable effect
DisposableEffect(loginState) {
if(loginState is State.Success){
navController.navigate("helloScreen/data")
}
onDispose {
loginState = State.Idle // Reset back your state here
}
}

Text is not updating on button in Jetpack Compose

I want to change the text that's appearing on the button each time I click it, so I have written the following code, but it's not working. Where am I going wrong?
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
var i=0;
Button(onClick = {i++ }) {
Text("Clicked $i times") //!!not updating here
}
}
}
}
Check how compose works with states and recomposition.
Use something like:
var i by remember { mutableStateOf(0) }
Button(onClick = {i++ }) {
Text("Clicked $i times")
}

swipe to refresh using accompanist

I'm using accompanist library for swipe to refresh.
And I adopt it sample code for testing, however, it didn't work.
I search for adopt it, but I couldn't find.
Is there anything wrong in my code?
I want to swipe when user needs to refresh
class MyViewModel : ViewModel() {
private val _isRefreshing = MutableStateFlow(false)
val isRefreshing: StateFlow<Boolean>
get() = _isRefreshing.asStateFlow()
fun refresh() {
// This doesn't handle multiple 'refreshing' tasks, don't use this
viewModelScope.launch {
// A fake 2 second 'refresh'
_isRefreshing.emit(true)
delay(2000)
_isRefreshing.emit(false)
}
}
}
#Composable
fun SwipeRefreshSample() {
val viewModel: MyViewModel = viewModel()
val isRefreshing by viewModel.isRefreshing.collectAsState()
SwipeRefresh(
state = rememberSwipeRefreshState(isRefreshing),
onRefresh = { viewModel.refresh() },
) {
LazyColumn {
items(30) { index ->
// TODO: list items
}
}
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
setContent {
TestTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
}
}
}
}
}
Your list doesn't take up the full screen width and you should include the state parameter:
SwipeRefresh(
state = rememberSwipeRefreshState(isRefreshing),
onRefresh = { viewModel.refresh() },
) {
LazyColumn(state = rememberLazyListState(), modifier = Modifier.fillMaxSize()) {
items(100) { index ->
Text(index.toString())
}
}
}
or with Column:
Column(modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())) {
repeat(100) { index ->
Text(index.toString())
}
}

Android compose onClick being called before click

I'm having issues whit the onClick on Jetpack compose, it performs the click as soon as I run the app and after returning to this activity the button stops working. Any insights?
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val context = LocalContext.current
linkButton("Go to text views", goTo(context, TextViewActivity::class.java))
}
}
}
#Composable
fun linkButton(msg: String, link: Unit) {
Button(onClick = {
link
}) {
Text(msg)
}
}
#Preview
#Composable
fun PreviewMessageCard() {
val context = LocalContext.current
linkButton(
msg = "Sample",
link = goTo(context, TextViewActivity::class.java)
)
}
private fun goTo(context: Context, clazz: Class<*>) {
context.startActivity(Intent(context, clazz))
}
You are actually calling the method at the moment you are composing the linkButton, not passing it as a callback to be called on click. And on click, it is just returning a Unit which causes the unexpected behavior.
To fix that, you should change the parameter type in your composable function to () -> Unit, which represents a function type.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val context = LocalContext.current
linkButton("Go to text views") {
goTo(context, TextViewActivity::class.java)
}
}
}
}
#Composable
fun LinkButton(msg: String, link: () -> Unit) {
Button(onClick = {
link()
}) {
Text(msg)
}
}

Categories

Resources