https://kotlinlang.org logo
Title
m

Manuel Lorenzo

08/23/2022, 1:41 PM
hi everybody, I’ve got a problem with a LazyColumn of elements that have a favourite button: basically when I tap the favourite button, the item that is being favourited (a document in my case) is changed in the underlying data structure in the VM, but the view isn’t updated, so I never see any change in the button state 🧵
c

Chris Fillmore

08/23/2022, 4:04 PM
Can you please put your code blocks in the thread? That’s why people are reacting with 🧵
m

Manuel Lorenzo

08/23/2022, 4:10 PM
of course, sorry!
class MainViewModel(private val ioDispatcher: CoroutineDispatcher = <http://Dispatchers.IO|Dispatchers.IO>) : ViewModel() {
    var documentList = emptyList<PDFDocument>().toMutableStateList()
    ....
    fun toggleFavoriteDocument(pdfDocument: PDFDocument) {
        documentList.find {
            it == pdfDocument
        }?.let {
            it.favorite = !it.favorite
        }
    }
}
The composables are:
@Composable
fun DocumentRow(
    document: PDFDocument,
    onDocumentClicked: (String, Boolean) -> Unit,
    onFavoriteValueChange: (Uri) -> Unit
) {
HeartIcon(
                isFavorite = document.favorite,
                onValueChanged = { onFavoriteValueChange(document.uri) }
            )
}

@Composable
fun HeartIcon(
    isFavorite: Boolean,
    color: Color = Color(0xffE91E63),
    onValueChanged: (Boolean) -> Unit
) {
    IconToggleButton(
        checked = isFavorite,
        onCheckedChange = {
            onValueChanged()
        }
    ) {
        Icon(
            tint = color,
            imageVector = if (isFavorite) {
                Icons.Filled.Favorite
            } else {
                Icons.Default.FavoriteBorder
            },
            contentDescription = null
        )
    }
}
am I doing something wrong? because when I call the
toggleFavouriteDocument
in the
ViewModel
, I see it’s marked or unmarked as favourite but there is no recomposition at all anywhere (edited)
c

Chris Fillmore

08/23/2022, 4:11 PM
It’s no problem. 👍 Sorry I can’t offer any contribution to your specific problem; good luck getting it resolved.
o

Oleksandr Balan

08/23/2022, 4:18 PM
Does your
favorite
property in
PDFDocument
baked by
State
?
m

Manuel Lorenzo

08/23/2022, 4:19 PM
what do you mean? although I guess if I don’t know what you mean, that means that it is NOT backed by
State
o

Oleksandr Balan

08/23/2022, 4:19 PM
Could you show us your
PDFDocument
class?
m

Manuel Lorenzo

08/23/2022, 4:21 PM
@Parcelize 
data class PDFDocument(var uri: String, var favorite: Boolean): Parcelable()
o

Oleksandr Balan

08/23/2022, 4:28 PM
Having a mutable properties (var in this case) inside the mutable container (MutableList) is generally a bad idea. Compose simply could not track this changes. It tracks only changes in the
State
properties. So I guess something like this should work:
@Parcelize
data class PDFDocument(val uri: String, val initialFavorite: Boolean): Parcelable {
    var favorite by mutableStateOf(initialFavorite)
}
However I would recommend to make your models fully immutable:
@Parcelize
data class PDFDocument(val uri: String, val favorite: Boolean): Parcelable
And create a new ones (for example with a
.copy()
) when the state changes:
fun toggleFavoriteDocument(pdfDocument: PDFDocument) {
    documentList.replaceAll { if (it == pdfDocument) it.copy(favorite = !it.favorite) else it }
}
a

Albert Chang

08/23/2022, 4:35 PM
Use regular class for a mutable class and data class for a immutable class. Do not use data class for a mutable class.
m

Manuel Lorenzo

08/23/2022, 4:56 PM
thanks! unfortunately it’s not working yet
I have this:
fun toggleFavoriteDocument(pdfDocument: PDFDocument) {
        documentList.find {
            it == pdfDocument
        }?.let {
            it.copy(fav = !it.fav)
        }.also {
            _documentListLiveData.postValue(documentList)
        }
    }
and when I set a breakpoint in the last
postValue
to see the content of the
documentList
the documents are never
favorite = true
nor
fav = true
doing this works, but the icon is never changed:
fun toggleFavoriteDocument(pdfDocument: PDFDocument) {
        documentList.forEachIndexed { index, document ->
            pdfDocument.takeIf {
                document == pdfDocument
            }?.let {
                documentList[index] = document.copy(fav = !document.favorite)
            }
        }.also {
            _documentListLiveData.postValue(documentList)
        }
    }
perhaps there’s something wrong in the way I hoisted the state in
DocumentRow
and
HeartIcon
?
o

Oleksandr Balan

08/23/2022, 5:14 PM
Oh, you are using
LiveData
, in this case something like this should work:
fun toggleFavoriteDocument(pdfDocument: PDFDocument) {
    val newDocuments = _documentListLiveData.value?.map {
        if (it == pdfDocument) {
            it.copy(favorite = !it.favorite)
        } else {
            it
        }
    }
    _documentListLiveData.postValue(newDocuments)
}
Why do you have a
documentList
property then?
m

Manuel Lorenzo

08/23/2022, 5:20 PM
Yes, a mutable list
I have tried however with both LiveData and mutable state list
I’ll make it work, thanks a lot everybody!!