I'm trying to update a parameter of my Model. I have a MutableStateFlow with a list with some of my model created.
data class MyModel(
val id: Int,
val category: String,
var completed: Boolean
)
val listOfModel = listOf(
MyModel(
id = 0,
category = "shopping",
completed = true
), MyModel(
id = 1,
category = "web",
completed = false
)
)
var _modelStateFlow = MutableStateFlow(listOfModel)
var modelStateFlow = _modelStateFlow.asStateFlow()
What I want to do in my other class, is to update the "completed" parameter in the model. That's what I tried but I get the following error:
Type mismatch. Required: List<"MyModel"> Found: MyModel
_modelStateFlow.update { it[current.value!!].copy(completed = !modelStateFlow.value[current.value!!].completed) }
You can do it like this:
_modelStateFlow.update { list ->
list.mapIndexed { index, myModel ->
if(index == indexToUpdate) myModel.copy(completed = !myModel.completed)
else myModel
}
}
The reason you are getting that error is because, you need to return a new list inside update function which represents the new value of StateFlow. You can create that new list using map function. Update the model at the desired index keeping others as they are.
Related
My User object has a List<Post>, each Post has a List<Tag>.
Now I would like to add a Tag item to the List<Tag>.
User:
data class User(
val id: String,
val name: String,
val posts: List<Post> = listOf()
)
Post:
data class Post(
val id: Int,
val name: String
val tags: List<Tags> = listOf()
)
Now I would like to update my MutableStateFlow containing the User object:
private val _userStateFlow: MutableStateFlow<User?> = MutableStateFlow(User())
val userStateFlow: StateFlow<User?> = _userStateFlow
To find the correct Post (containing the List<Tag> I want to update) I have the id of it (passedPostId)
val postsList = userStateFlow.value?.posts!!.toMutableList()
val tagsList = postsList.find { it.id == passedPostId }?.tags?.toMutableList()
tagsList.add(Tag("test"))
Now I got an updated tagsList but I need an updated postsList to pass it to my MutableStateFlow.
I am not sure how to do it.
This is how I update my MutableStateFlow:
_userStateFlow.update { it?.copy(posts = newPosts)
If I have the index of the post I want to update everything works fine, but In my Case I only can call .find because all i have is the id of the post.
val posts = userStateFlow.value?.posts!!.toMutableList()
posts[index] = posts[index].copy(tags = posts[index].tags.plus(Tag("test")
userStateFlow.value = userStateFlow.value?.copy(posts = posts)
You can create a function to add a tag to your immutable Post object - may as well stick it inside the class:
data class Post(
val id: Int,
val name: String
val tags: List<Tag> = listOf()
) {
// copy this object, replacing the tag list with a copy that has the new one appended
fun addTag(newTag: Tag) = copy(tags = tags + newTag)
}
Then you can do:
// alternative to putting it in the User class - whatever feels better
fun User.addTag(postId: Int, tag: Tag) =
// copy this User, including copying its list of Posts
// but any with a matching ID get the tag added
copy(posts = posts.map { post ->
if (post.id == postId) post.addTag(tag) else post
})
and update with
userStateFlow.value = userStateFlow.value!!.addTag(passedPostId, Tag("test"))
Try it here if you like
I fixed it by simply getting the index of the Post so I could use the code that worked before:
val postPosition = postsList.indexOfFirst {
it.id == passedPostId
}
postsList[postPosition] = postsList[postPosition].copy(tags = tagsList)
_userStateFlow.update { it?.copy(posts = postsList)
I feel pretty stupid to not have thought of that in the first place.
My project is an expense tracker where I show a list of Dates under which I have a list of expenses that happened on those dates. I have nested RecyclerViews. Parent RecyclerView is a list of unique dates of all expenses. Child RecyclerView is list of expenses (viewed, of course, under unique dates).
My ViewModel has a list of LiveData of ExpenseEntity. The ViewModel has to have a list of LiveData of Date which contains unique dates. I get my list of ExpenseEntity from a Room database.
My main fragments observes the LiveData of ExpenseEntities because then is when I need to update my parent and child recyclerviews.
I cannot figure out how to use Transformations.map to have a live transforming list of unique dates. How should I make sure the LiveData of Dates is always updated once LiveData of ExpenseEntity is updated?
MainActivityViewModel.kt
class MainActivityViewModel(private val expenseDao: ExpenseDao) : ViewModel() {
val allExpenses : LiveData<List<ExpenseEntity>> = expenseDao.fetchAllExpenses().asLiveData()
val uniqueDates : LiveData<List<Date>> = Transformations.map(allExpenses) {
it.map { expense ->
expense.date!!
}.distinct()
}
...
}
ExpensesFragment.kt
val factory = MainActivityViewModelFactory((activity?.application as SimpleExpenseTrackerApp).db.expenseDao())
expensesViewModel = ViewModelProvider(this, factory).get(MainActivityViewModel::class.java)
binding.rvParentExpenseDates.layoutManager = LinearLayoutManager(requireContext())
expensesViewModel.allExpenses.observe(viewLifecycleOwner){ expensesList ->
if (expensesList.isNotEmpty()){
binding.rvParentExpenseDates.adapter = expensesViewModel.uniqueDates.value?.let {
ParentDatesAdapter(it, expensesList) { expenseId ->
Toast.makeText(requireContext(), "Clicked expense with id: $expenseId", Toast.LENGTH_LONG).show()
}
}
binding.rvParentExpenseDates.visibility = View.VISIBLE
} else {
binding.rvParentExpenseDates.visibility = View.GONE
}
}
ExpenseEntity.kt
#Entity(tableName = "expense-table")
data class ExpenseEntity(
#PrimaryKey(autoGenerate = true)
val id: Int = 0,
#ColumnInfo(name = "date-time")
val dateTime : Date?,
val date : Date?,
#ColumnInfo(name = "account-type")
val accountType : String = "",
val category : String = "",
val amount : Double = 0.0,
val currency : String = "",
val note : String = ""
)
Per the documentation:
These methods permit functional composition and delegation of LiveData
instances. The transformations are calculated lazily, and will run
only when the returned LiveData is observed. Lifecycle behavior is
propagated from the input source LiveData to the returned one.
The issue here is that you never observe the transformed LiveData (uniqueDates) -- you only inspect the value, so the transformation is never applied.
One option, if you need both together, is to map into a joined view:
class MainActivityViewModel(private val expenseDao: ExpenseDao) : ViewModel() {
val allExpenses : LiveData<List<ExpenseEntity>> =
expenseDao.fetchAllExpenses().asLiveData()
val allAndUniqueDatedExpenses: LiveData<Pair<List<ExpenseEntity>, List<Date>> =
Transformations.map(allExpenses) { expenses ->
expenses to expenses.mapNotNull { it.date }.distinct()
}
}
Then simply observe this joined value:
expensesViewModel.allAndUniqueDatedExpenses.observe(this) { (expenses, dates) ->
binding.rvParentExpenseDates.adapter =
ParentDatesAdapter(dates, expenses) { expenseId ->
Toast.makeText(...).show()
}
}
However, I would argue here you don't really need another LiveData transformation. Simply do the transformation inline:
expensesViewModel.allExpenses.observe(this) { expenses ->
val dates = expenses.mapNotNull { it.date }.distinct()
binding.rvParentExpenseDates.adapter =
ParentDatesAdapter(dates, expenses) { expenseId ->
Toast.makeText(...).show()
}
}
I want to remove all duplicate objects of one type from a list.
For Eg.
val models: MutableList<Model> = MutableList<DrawableModel>
models.add(Student)
models.add(Student)
models.add(Teacher)
models.add(Teacher)
models.add(Teacher)
Expected Output:
Student
Student
Teacher
I want only one instance of a teacher on the list.
I tried with
models.distinctBy{ it is Teacher}
But it is applying distinct to the entire list not to a particular object and returns.
Student
Teacher
You can try something like this:
object Kotlin
{
#JvmStatic
fun main(args: Array<String>)
{
val teacher = Teacher(0, "T1");
val student = Student(1, "S1")
val models = mutableListOf(teacher, teacher, student, student, student)
// creating new list which is connection of two lists. First -> only Students. Second -> Distinct Teacher
val newModels = models.filterIsInstance<Teacher>().distinct() + models.filterIsInstance<Student>()
println(models) // [Teacher(ID=0, name=T1), Teacher(ID=0, name=T1), Student(ID=1, name=S1), Student(ID=1, name=S1)]
println(newModels) // [Teacher(ID=0, name=T1), Student(ID=1, name=S1), Student(ID=1, name=S1)]
}
// classes like in the question.
open class Model(val id: Int)
data class Teacher(val ID: Int, val name: String) : Model(ID)
data class Student(val ID: Int, val name: String) : Model(ID)
So basically it is the main part:
val newModels = models.filterIsInstance<Teacher>().distinct() + models.filterIsInstance<Student>()
You can set a Boolean so it skips the first one it finds.
var first = true
models.removeAll { if (it is Teacher) (!first).also { first = false } else false }
If you want a new list, use filterNot instead of removeAll.
distinctBy doesn't work because it's converting the objects to distinct keys. Since you're key type is Boolean, there can only be two items in the list.
Try this:
var list = arrayListOf<Any>()
list.add(Person("JAVA",20))
list.add(Person("JAVA",20))
list.add(Student("SQL",24))
list.add(Student("SQL",24))
list.add(Student("SQL",24))
var filterList = arrayListOf<Any>()
var studentCount = 0
// filtering student object only
list.forEach {
if (it is Student && studentCount == 0) {
studentCount++
filterList.add(it)
} else if (it is Person) filterList.add(it)
}
filterList.stream().forEach { println(it) }// output : [JAVA,20],[JAVA,20],[SQL,24]
I have a model named Item. the model has linkingObjects to model ItemModifier. The Question is, why do I have to query for it to return a result ?
open class Item() : RealmObject() {
#PrimaryKey
var id: String = ""
#LinkingObjects("item")
val itemModifiers: RealmResults<ItemModifier>? = null
}
var item = Item()
item.id = UUID.randomUUID().toString()
realm.copyToRealmOrUpdate(itemModifier)
var itemModifier = ItemModifier()
itemModifier.id = UUID.randomUUID().toString()
itemModifier.item = item
realm.copyToRealmOrUpdate(itemModifier)
for (itemModifier in item.itemModifiers) { // this returns nullOrEmpty.
Log.e("test", itemModifier.id)
}
queriedItem = Realm.getDefaultInstance().where(Item::class.java).equalTo("id", item.id).findFirst()!!
for (itemModifier in queriedItem.itemModifiers) { // this return itemModifier.
Log.e("test", itemModifier.id)
}
Firstly, I assume the first realm.copyToRealmOrUpdate(itemModifier) is a typo and should be realm.copyToRealmOrUpdate(item).
Having done this the item you created (an unmanaged object) has now been copied into the realm, but the reference you hold is still to the unmanaged object. This is why a look at its linking objects field gives you an empty list. Whereas, as you have shown, retrieving the managed object via query gives you the result you expect.
Note that according to the docs (here), the copyToRealmOrUpdate method returns a reference to the managed object, so you could use that immediately and you should get the correct result. E.g.:
var item = Item()
item.id = UUID.randomUUID().toString()
val managedItem = realm.copyToRealmOrUpdate(item)
var itemModifier = ItemModifier()
itemModifier.id = UUID.randomUUID().toString()
itemModifier.item = managedItem
realm.copyToRealmOrUpdate(itemModifier)
for (itemModifier in managedItem.itemModifiers) { // this should now work.
Log.e("test", itemModifier.id)
}
I have a mutableList of Cars (Cars is a data class).
I'm searching a way to find the Car with a specific id in my mutable list.
How can i achieve that in Kotlin?
Car.kt
data class Car(
val createdAt: String = "",
val updatedAt: String = "",
val id: String = "",
val number: String = ""
)
In my CarsFragment.kt:
var cars: MutableList<Car>
// extract Car with id = "89Ddzedzedze8998" ?
Use firstOrNull or find to get the result or null in case no car available with the given id.
fun getCarById(carId: Int) {
val myCar: Car? = carsList.firstOrNull { it.id == carId }
// or
val myCar: Car? = carsList.find { it.id == carId }
}
Now you can easily check if you get actual value or null, and move further accordingly.
You can do using find in kotlin
val idToCheck = "89Ddzedzedze8998"
cars.find { it.id == idToCheck }
So you want something like that
val idToCheck = "89Ddzedzedze8998"
cars.first {car->
car.id == idToCheck
}
update
as mention by a_local_nobody it is worth mentioning that first will throw an NoSuchElementException but firstOrNull will return null