#Composable
fun getData() {
var wordData = arrayListOf<Word>()
db.get().addOnSuccessListener { documents ->
for (document in documents) {
wordData.add(document.toObject(Word::class.java))
}
}
LazyColumn {
items(wordData) { word ->
WordCard(word = word)
}
}
}
I wanted to use Lazy Column to show all of my words data, these are my WordCard codes.
#Composable
fun WordCard(word: Word) {
Text(text = word.word, color = Color.Black, modifier = Modifier.background(Color.Gray).padding(12.dp))
}
Not sure if this is a firebase issue, but I notice this.
var wordData = arrayListOf<Word>()
You are just adding elements to a standard collection structure, not something that compose can observe state changes for it to update your LazyColumn.
So please change it to this using SnapshotStateList and wrap it to a remember{…} so the list won't re-initialize on succeeding re-compositions
val wordData = remember { mutableStateListOf<Word>() }
Related
I have a LazyColumn with items and advertisements. When I scroll down and up (see video) the Advertisement Composable is recomposed (good that is how it should work in compose), meaning that a new advertisement is loaded. The grey block with the text advertisement is a placeholder we use when the Ad is not loaded yet.
Is it possible to keep the same advertisement in the LazyColumn? (the basic question here is: Can i have a Composable in a LazyColumn that will not be recomposed?)
I have tried a few things: adding keys to the items in the LazyColumn, remember the AdvertisementView (which is a AndroidView), but it's not working.
So my question is:
Is this even possible with a LazyColumn in Compose? And if so, how can I do it?
Thank you!
Edit: added some code:
LazyColumn() {
items(list, key = {it.unitId}) { item ->
when (item) {
is ListItem.NormalItem -> NormalItem( )
is ListItem.Advertisement -> AdvertisementAndroidView()
}
}
}
#Composable
fun AdvertisementAndroidView() {
val context = LocalContext.current
var isLoaded by remember { mutableStateOf(false) }
val bannerView by remember { mutableStateOf(createAdView(context, advertisementData) {
isLoaded = true })}
Box() {
if (!isLoaded) {
AdvertisementPlaceHolder()
} else {
AndroidView( factory = {bannerView} )
}
}
}
private fun createAdView(context: Context, data: AdvertisementData, isLoaded: () -> Unit): AdManagerAdView {
val view = AdManagerAdView(context)
...
view.loadAd(adRequest)
return view
}
For example, I load data into a List, it`s wrapped by MutableStateFlow, and I collect these as State in UI Component.
The trouble is, when I change an item in the MutableStateFlow<List>, such as modifying attribute, but don`t add or delete, the UI will not change.
So how can I change the UI when I modify an item of the MutableStateFlow?
These are codes:
ViewModel:
data class TestBean(val id: Int, var name: String)
class VM: ViewModel() {
val testList = MutableStateFlow<List<TestBean>>(emptyList())
fun createTestData() {
val result = mutableListOf<TestBean>()
(0 .. 10).forEach {
result.add(TestBean(it, it.toString()))
}
testList.value = result
}
fun changeTestData(index: Int) {
// first way to change data
testList.value[index].name = System.currentTimeMillis().toString()
// second way to change data
val p = testList.value[index]
p.name = System.currentTimeMillis().toString()
val tmplist = testList.value.toMutableList()
tmplist[index].name = p.name
testList.update { tmplist }
}
}
UI:
setContent {
LaunchedEffect(key1 = Unit) {
vm.createTestData()
}
Column {
vm.testList.collectAsState().value.forEachIndexed { index, it ->
Text(text = it.name, modifier = Modifier.padding(16.dp).clickable {
vm.changeTestData(index)
Log.d("TAG", "click: ${index}")
})
}
}
}
Both Flow and Compose mutable state cannot track changes made inside of containing objects.
But you can replace an object with an updated object. data class is a nice tool to be used, which will provide you all copy out of the box, but you should emit using var and only use val for your fields to avoid mistakes.
Check out Why is immutability important in functional programming?
testList.value[index] = testList.value[index].copy(name = System.currentTimeMillis().toString())
I want get Data(with an api) from internet and set the data in LazyColumn
So I use this
#Composable
fun LazyColumn() {
val list by remember { mutableStateOf(arrayListOf<HelloData>()) }
Thread {
for (index in 0..100) {
list.add(HelloData(JSONObject(URL("https://ovooa.com/API/sjtx/api?form=%E5%A5%B3%E5%A4%B4").readText()).getString("text")))
}
}.start()
LazyColumn(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(4.dp)) {
items(list) { item ->
Image(
painter = rememberImagePainter(item.url, builder = {
crossfade(true)
diskCachePolicy(CachePolicy.DISABLED)
memoryCachePolicy(CachePolicy.DISABLED)
}),
contentDescription = null,
modifier = Modifier.size(200.dp)
)
}
}
}
data class HelloData(val url: String)
It's not working.
I just want get the data from my api,then put them in an Image.
I can achieve it easily in traditional Android Development.
Use mutableStateListOf instead of mutableStateOf. mutableStateListOf detects changes to the list items whereas mutableStateOf will only detect changes to the list object.
I have a screen with Jetpack Compose in which I have a TextField for the user to write a text.
With this text I will make a query to obtain data. I want this query to be made when the user finishes typing.
Is there a way to know if the user takes 2 seconds without writing (for example) to launch this query?
To query after 2 seconds after user stop typing, I think you can use debounce operator (similar idea to the answer here Jetpack Compose and Room DB: Performance overhead of auto-saving user input?)
Here is an example to handle text change on TextField, then query to database and return the result to dbText
class VM : ViewModel() {
val text = MutableStateFlow("")
val dbText = text.debounce(2000)
.distinctUntilChanged()
.flatMapLatest {
queryFromDb(it)
}
private fun queryFromDb(query: String): Flow<String> {
Log.i("TAG", "query from db: " + query)
if (query.isEmpty()) {
return flowOf("Empty Result")
}
// TODO, do query from DB and return result
}
}
In Composable
Column {
val text by viewModel.text.collectAsState()
val dbText by viewModel.dbText.collectAsState("Empty Result")
TextField(value = text, onValueChange = { viewModel.text.value = it })
Text(text = dbText)
}
Another way is to avoid the viewmodel completely. Utilise the LaunchedEffect that will cancel/restart itself on every key (text) change. I find this to be way cleaner than to couple your debounce code to your viewmodel.
#Composable
private fun TextInput(
dispatch: (ViewModelEvent) : Unit,
modifier: Modifier = Modifier
) {
var someInputText by remember { mutableStateOf(TextFieldValue("")) }
TextField(
value = someInputText,
onValueChange = {
someInputText = it
},
)
LaunchedEffect(key1 = someInputText) {
// this check is optional if you want the value to emit from the start
if (someInputText.text.isBlank()) return#LaunchedEffect
delay(2000)
// print or emit to your viewmodel
dispatch(SomeViewModelEvent(someInputText.text))
}
}
I'm trying to update a LazyColumn items using a subscriber to a RxAndroid Flowable. The state variable I'm using for the image list is called simply "list"
This is my LazyColumn code:
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
) {
items(list) { image ->
Text(text = image.title ?: "Title")
}
}
If for example, I run this test coroutine, the list is updated and shows the correct amount of test images:
GlobalContext.run {
val testList = SnapshotStateList<Image>()
for (i in 1..100) {
testList.add(Image(i, null, null, null, null))
}
list = testList
}
But if I try the same method using my subscription to a Flowable, it updates the variable value but the recomposition is not triggered. This is my code:
val observer = remember {
disposable.add(
viewModel.imagesObservable().subscribe(
{ images ->
val snapList = SnapshotStateList<Image>()
images.forEach {
snapList.add(Image(it.id, it.albumId, it.title, it.url, it.thumbnailUrl))
}
list = snapList
},
{ Log.d("dasal", "Error: Can't load images") }
)
)
}
How do I handle a Flowable with a Composable?
Fixed it. I was using this declaration
var list = remember { mutableStateListOf<Image>() }
I changed it to this one instead
val list = remember { mutableStateOf(listOf<Image>()) }
Now I can use the list.value property to update/read the current value.