Odai A. Ali
Odai A. Ali

Reputation: 1215

How to force jetpack compose to recompose?

Say that, I'm building a custom compose layout and populating that list as below

val list = remember { dataList.toMutableStateList()}
MyCustomLayout{
    
   list.forEach { item ->
        key(item){
             listItemCompose( data = item,
                              onChange = { index1,index2 -> Collections.swap(list, index1,index2)})
   }
}

This code is working fine and the screen gets recomposed whenever onChange lambda function is called, but when it comes to any small change in any item's property, it does not recompose, to elaborate that let's change the above lambda functions to do the following

{index1,index2 -> list[index1].propertyName = true} 

Having that lambda changing list item's property won't trigger the screen to recompose. I don't know whether this is a bug in jetpack compose or I'm just following the wrong approach to tackle this issue and I would like to know the right way to do it from Android Developers Team. That's what makes me ask if there is a way to force-recomposing the whole screen.

Upvotes: 35

Views: 41273

Answers (6)

Raul Lucaciu
Raul Lucaciu

Reputation: 162

You can use:

currentCompositionScope.invalidate()

But use with care.

compose version: 1.7.5

Upvotes: 1

evcostt
evcostt

Reputation: 774

You can force recompose using key:

val text by remember { mutableStateOf("foo") }

key(text) {
    YourComposableFun(
        onClick = {
            text = "bar"
        }
    ) {

    }
}

In this example the text is keyed. When its value changes in the onClick, everything inside the key function recomposes.

Upvotes: 48

ahvroyal
ahvroyal

Reputation: 11

For enabling compose to track changes to your list's element data type properties, you could use mutableStateOf(initialValue) function to define your property in the data type definition.

as an example:

class Task(
    val id: Int,
    val label: String,
    initialChecked: Boolean = false
) { 
    var checked by mutableStateOf(initialChecked)
}

now, compose will trigger recomposition whenever checked proeprty is mutated.

Upvotes: 0

Eric
Eric

Reputation: 4395

If you ever need to force a recompose then create an empty LaunchedEffect with a MutableState<Boolean> value key and toggle the mutable state.

class FooViewHolder {
    private val recomposeToggleState: MutableState<Boolean> = mutableStateOf(false)

    @Composable
    fun View() {
        Log.i("Foo View Holder", "Compose / Recompose")

        Box(modifier = Modifier
            .fillMaxSize()
            .background(Color.Green)
        )

        LaunchedEffect(recomposeToggleState.value) {}
    }

    fun manualRecompose() {
        recomposeToggleState.value = !recomposeToggleState.value
    }
}    

Then in Activity for example

val viewHolder = FooViewHolder()
    
override fun onCreate(savedInstanceState: Bundle?) {
    setContent {
        viewHolder.View()
    }
}

override fun onResume() {
    viewHolder.manualRecompose()
}

Upvotes: 5

Thracian
Thracian

Reputation: 66929

With SnapshotStateList to trigger recomposition you need delete, insert or update existing item with a new item with the property you set as using a data class is best option since it comes out with a copy function

list[index1] = list[index1].copy(propertyName = true)

will trigger recomposition since you set a new item at index1 with property = true

https://stackoverflow.com/a/74506067/5457853

https://stackoverflow.com/a/74700668/5457853

Upvotes: 1

Francesc
Francesc

Reputation: 29280

You can't force a composable function to recompose, this is all handled by the compose framework itself, there are optimizations to determine when something has changed that would invalidate the composable and to trigger a recomposition, of only those elements that are affected by the change.

The problem with your approach is that you are not using immutable classes to represent your state. If your state changes, instead of mutating some deep variable in your state class you should create a new instance of your state class (using Kotin's data class), that way (by virtue of using the equals in the class that gets autogenerated) the composable will be notified of a state change and trigger a recomposition.

Compose works best when you use UDF (Unidirectional Data Flow) and immutable classes to represent the state.

This is no different than, say, using a LiveData<List<Foo>> from the view system and mutating the Foos in the list, the observable for this LiveData would not be notified, you would have to assign a new list to the LiveData object. The same principle applies to compose state.

Upvotes: 20

Related Questions