How to construct MutableList based List in Kotlin? - android

I hope to construct MutableList a based List b, how can I do in Kotlin?
BTW, Code A is wrong
var a:MutableList<MSetting>
var b:List<MSetting>
Code A
var a=mutableListOf(b)

Kotlin has a few versions of the toMutableList() extension function that should cover you. Check out the documentation here.
Basically, on any Collection, Interable, typed Array or generic Array, you can call .toMutableList() and it will creat a new MutableList (backed by ArrayList).
So in your code, you can do this (I'm showing types, you can leave them off):
var b: List<MSetting> = listOf(...)
var a: MutableList<MSetting> = b.toMutableList()

It is a generic and needs type information e.g.
var a = mutableListOf<Int>()
I tested at https://try.kotlinlang.org/#/
EDIT: I didn't read closely enough, what you want should be a toMutableList method.
var b = listOf(2, 3);
var a = b.toMutableList()

it's very easy:
val listB = listA?.toMutableList() ?: mutableListOf() // list B = A if (A != null), other wise, B = new empty list

Related

Kotlin List<List<String>> when instantiating creates 1 empty element

Solving algorithm tasks and came to one interesting situation that before I did not pay attention to.
Here is example:
val testList1 = mutableListOf<String>()
testList1.add("f")
testList1.add("n")
Toast.makeText(this, testList1.size.toString(), Toast.LENGTH_SHORT).show()
In this code, my toast will return size 2. Which is ok and expected.
but let's take this example:
val testList2 = mutableListOf(mutableListOf<String>())
testList2.add(mutableListOf("sf", "fgs"))
testList2.add(mutableListOf("sw", "fgg"))
Toast.makeText(this, testList2.size.toString(), Toast.LENGTH_SHORT).show()
Here the toast shows size = 3 even though I added 2 elements (2 lists). So when instantiating it adds 1 emptyList as the first element.
Not a big problem to solve this, we can just:
var finalList = testList2.removeIf { it.isEmpty() }
But I am curious why this happens. Also is there any nice way to avoid it. Would like to know little bit more if possible
It is not strange that testList2 contains 3 objects. testList2 is constructed with an initial empty list.
val testList2 = mutableListOf(mutableListOf<String>())
// using
public fun <T> mutableListOf(vararg elements: T): MutableList<T> =
if (elements.size == 0) ArrayList() else ArrayList(ArrayAsCollection(elements, isVarargs = true))
Here, you can define an empty mutable list by these codes.
val testList: MutableList<MutableList<String>> = mutableListOf()
// or
val testList = mutableListOf<MutableList<String>>()
// using
public inline fun <T> mutableListOf(): MutableList<T> = ArrayList()
Whatever you pass to the mutableListOf function is the initial contents of the list it returns. Since you have nested a call of mutableListOf() inside the outer call to mutableListOf(), you are creating your list with an initial value of another MutableList.
If you want your list to start empty, don’t put anything inside the () when you call mutableListOf().
If you construct your list this way, you need to specify the type of the list, since it won’t have an argument to infer the type from.
Either
val testList2 = mutableListOf<MutableList<String>>()
or
val testList2: MutableList<MutableList<String>> = mutableListOf()

Kotlin - StateFlow not emitting updates to its collectors

I got a StateFlow of type UserStateModel (data class) in my app.
private val _userStateFlow: MutableStateFlow<UserStateModel?> = MutableStateFlow(UserStateModel())
val userStateFlow: StateFlow<UserStateModel?> = _userStateFlow
here is the UserStateModel
data class UserStateModel(
val uid: String? = null,
val username: String? = null,
val profileImageUrl: String? = null,
var isLoggedIn: Boolean = false,
val isPremiumUser: Boolean = false,
val posts: List<Post>? = listOf()
)
When I update the StateFlow with a new Username it emits the change to the collectors and the UI updates.
But when I change a property inside the posts: List? list it doesnt emit the changes.
When I change the size of the list it does, when I change the name property of the Post at index 0 it doesnt.
How can I detect changes to the child properties of the Data class?
Right now I use an ugly workaround, I add
val updateErrorWorkaround: Int = 0
to the UserStateModel data class and increase it by one so the collectors get notified
P.s I'm using MVVM + Clean Architecture and Jeptack Compose
EDIT
Thats my Post Model:
data class Post(
val id: Int,
val name: String,
val tags: MutableList<Tag>? = null
)
Here is how I update the MutableList:
val posts = userStateFlow.value?.posts
posts.get(index).tags?.add(myNewTag)
_userStateFlow.value = userStateFlow.value?.copy(posts = posts)
Those changes are not emitted to the collectors
StateFlow emits only if it detects changes to the value, it ignores replacing the value with the same data. To do this, it compares the previous value with the new one. For this reason, we shouldn't modify the data that we already provided to the StateFlow, because it won't be able to detect changes.
For example, we set value to a User(name=John). Then we mutate the same user object by modifying its name to James and we set the value to this "new" user object. StateFlow compares "new" User(name=James) with its stored value, which is now also User(name=James), so it doesn't see any changes.
In your example you created a copy of UserStateModel, but inside you re-use the same objects and you mutate them. In this case you added a new item to tags and this change affected old UserStateModel as well, so StateFlow doesn't detect the change.
To fix the problem, you need to copy all the data that was changed and do not mutate anything in-place. It is safer to make all the data immutable, so val and List - this way you are forced to make copies. I changed tags to val tags: List<Tag> = listOf(), then your code could look like the following:
val posts = userStateFlow.value?.posts!!.toMutableList()
posts[index] = posts[index].copy(tags = posts[index].tags + myNewTag)
userStateFlow.value = userStateFlow.value?.copy(posts = posts)
Here we create a copy of not only UserStateModel. We also copy posts list, the Post that we modify and we also copy the list of tags.
Alternatively, if this behavior of StateFlow is more annoying to you than helpful, you can use SharedFlow which doesn't compare values, but just emits.

Problem with filtering Mutablelist of type class

So what I'm trying to do is to write search logic. The problem is following filter does not work even tho I do have an element containing following letter. So what my question is why is it not returning the expected value and if I'm doing something wrong what is it.
the filter I'm trying to use:
model.data.filter { person -> person.employeeName.toLowerCase().contains("t")}.toMutableList()
where model is InfoModel type and InfoModel looks like this:
class InfoModel {
var status = ""
lateinit var data : MutableList<Data>
class Data {
var id = ""
#SerializedName("employee_name")
var employeeName = ""
#SerializedName("employee_salary")
var employeeSalary = ""
#SerializedName("employee_age")
var employeeAge = ""
#SerializedName("profile_image ")
var profileImage = "https://www.pngitem.com/pimgs/m/146-1468479_my-profile-icon-blank-profile-picture-circle-hd.png"
}
}
I'm guessing due to lack of context, but maybe you're doing something like this:
model.data.filter { person -> person.employeeName.toLowerCase().contains("t")}.toMutableList()
println(model.data) // Still prints original unfiltered list!
The first line of code creates a new MutableList and promptly throws it away, because you don't assign it to anything. So the original list pointed at by model.data is left unchanged.
Since data is a MutableList, you can modify it in place using retainAll:
model.data.retainAll { person -> person.employeeName.toLowerCase().contains("t") }
Alternatively, you could reassign the result of your original code back to model.data:
model.data = model.data.filter { person -> person.employeeName.toLowerCase().contains("t")}.toMutableList()
To me it looks like kind of code smell to have a MutableList assigned to a read-write var, because then it's mutable in two different ways. Why does it even have to be lateinit if it's mutable? You could instantiate with an empty list and fill it later.
In general var data: List should be preferred to val data: MutableList unless you are needing to optimize performance for huge lists. And var data: MutableList is just inviting troubles.

Cast Array<String?> to Array<String> in Kotlin?

I have an array of nulls that I initialize with Strings. Then I have to pass it to a function that requires Array<String> instead of Array<String?>. So how can I go around this issue?
val list_names = arrayOfNulls<String>(plant_list.size)
for(item in plant_list){ list_names.plus(item.name) }
val myListAdapter = MyListAdapter(activity!!,list_names,list_types,list_images) // list_names must be Array<String>
I also want to mention that changing it in the Adapter would only complicate things, so I would like to do it all from here.
If you are sure that no null-values will be in your nullable array, you can simply (unsafe) cast it and it will work, e.g.:
val myListAdapter = MyListAdapter(activity!!,list_names as Array<String>,list_types,list_images)
If you have null values in there it depends a bit. One approach is then to first filter out the null-values and pass the resulting array, e.g.:
list_names.filterNotNull().toTypedArray()
// or in case you have different types in there and want only want a single matching one:
list_names.filterIsInstance<String>().toTypedArray()
But if you can: try to omit holding that array of nullable type in the first place. Can't you just filter out null values and collect the non-nullable only? That would probably the easiest and nicest way to collect the names as Array<String>, e.g.:
val list_names = plant_list.mapNotNull { it.name }.toTypedArray()
To answer your direct question you can get a new Array without nulls using this:
val nonNullListNames = list_names.filterNotNull().toTypedArray()
But there are other issues with your code. There's no reason to create the array of nulls and add items to it. Every time you call list_names.plus(item.name) in your loop, you are creating a new Array that still has the original set of null values plus your new item(s).
Instead you can directly create a list of non-null items from the collection you're getting items from, and convert it to an Array:
val nonNullNamesArray = plant_list.map { it.name }.toTypedArray()
If your plant names are nullable, use this:
val nonNullNamesArray = plant_list.mapNotNull { it.name }.toTypedArray()
You cannot have a arrayOfNulls and convert a Nullable to a NonNull object.
What I recommend to you is:
val list_names = mutableListOf<String>()
plant_list.foreach {
list_names.add(it.name)
}
val myListAdapter = MyListAdapter(activity, list_names, list_types, list_images)

Create ArrayList<String> from resource string-array

I have declared ArrayList like,
var otherSeriesList = ArrayList<String>()
And trying to get data from resource by following,
otherSeriesList = ArrayList<String>(Arrays.asList(resources.getStringArray(R.array.get_other_series)))
But I am getting error. Please see the image-
How should I create ArrayList from resource string-array?
Simple do like this-
otherSeriesList = ArrayList<String>(Arrays.asList(*resources.getStringArray(R.array.get_other_series)))
* will pass the Array as vararg
Just use ArrayList(resources.getStringArray( ... ).toMutableList()) instead.
If you don't need to use ArrayList exactly in your code, then you can change type of property to MutableList<String> and call resources.getStringArray( ... ).toMutableList()
Or you can use spread operator on your array and create ArrayList via call arrayListOf(*context.resources.getStringArray())
You can to declare it as MutableList(). Also cast that StringArray to String before that.
var otherSeriesList: MutableList<String> = Arrays.asList(resources.getStringArray(R.array.get_other_series).toString())
Or you can do it like,
var otherSeriesList: MutableList<String> = resources.getStringArray(R.array.get_other_series).toMutableList()
you can create an extension function
private fun Array<String>.getAsArrayList(): ArrayList<String> {
val list = ArrayList<String>()
this.forEach { it->list.add(it) }
return list
}
and call it like this
val list = resources.getStringArray(R.array.array).getAsArrayList()
If you have an array, simply call toList() on it.
val arr = arrayOf(1, 2)
val list = arr.toList()
Alternatively, there’s also a toMutableList() extension available.
Note that you can declare lists in Kotlin like this:
val list = listOf<String>(1, 2)
To be sure to get an ArrayList, use arrayListOf().

Categories

Resources