Populate a list in Kotlin with a for loop - android

It's been a while that I just started to learn how to develop in Kotlin.
There is this thing that I am working on, I am trying to parse a list into another type of list. Basically they are the same thing but with different names. But when I try to populate the new list with the data that I get from the list given as parameter in the function the list only gets populated with the first object.
Here is my function:
fun convertRoomClass(course: List<Course>) : List<Courses> {
lateinit var list : List<Courses>
course.forEach {
val id = it.pathID
val name = it.pathName
val desc = it.pathDescription
val crs : Courses = Courses(id, name!!, desc!!)
list = listOf(crs)
}
return list
}

The error in your code is that you are making a list in every iteration of the loop. You should make the list first and then add every item from the loop to it!
fun convertRoomClass(courses: List<Course>) : List<AnotherCourseClass> {
val newList = mutableListOf<AnotherCourseClass>()
courses.forEach {
newList += AnotherCourseClass(it.pathID, it.pathName, it.pathDescription)
}
return newList
}
A better solution is to use the map function
fun convertRoomClass(courses: List<Course>) = courses.map {
AnotherCourseClass(it.pathID, it. pathName, it.pathDescription)
}

You might be looking for Kotlin Map
Example:
course.map { Courses(it.pathID, it.pathName,it.pathDescription) }

You're getting the list with only on object, cause the function listOf(crs) returns a list of all objects that are passed as a parameters. Saying the same thing in Java you're doing something like this:
for (course: Courses) {
Course course = new Course(...);
List<Course> list = new ArrayList<>();
list.add(course);
return list;
}
As you can see the it created new list with a single object per iteration.
What you're trying to achieve, can be done with operator map{...} which simply transforms every object in the initial list using code passed inside map and returns list of transformed objects
course.map{ Courses(...) }
Also, I've noticed that you're using the !! operator when creating a Courses object. Probably because the Course can have nullable name, while Courses can't. I'm considering this as a bad practice, cause in this case you're saying
Please throw an Exception if the name is null.
I think that a much better approach is to provide an alternative, like:
val name = course.name ?: "default", saying
Please use name or "default" if the name is null.
or skip objects without name, or any other approach that suits your situation.

You could use MutableList instead of List. That enable you to append new element at the end of your list instead of replace the entire list by doing : list = listOf(crs)
So replace the type of your var lateinit var list : List<Courses> by lateinit var list : MutableList<Courses> then replace list = listOf(crs) by list.add(crs)
Hope it helps and have fun :)

Related

How to change visibility of a TextView if a MutableList is empty? (Android/Kotlin)

I'm working on a simple calorie counter app using two fragments and a ViewModel. I'm a beginner and this is a modification of an app I just created for a course (this app is not a homework assignment). It uses ViewModel and has a fragment that collects user input and a fragment that displays the input as a MutableList of MutableLiveData. I would like for the list screen to initially be empty except for a TextView with instructions, and I'd like the instructions to disappear once an entry has been added to the list. My class instructor told me to use an if-else statement in the fragment with the list to achieve this, but it's not working. He didn't tell me exactly where to put it. I tried a bunch of different spots but none of them worked. I don't get errors - just no change to the visibility of the TextView.
Here is the code for the ViewModel with the list:
val entryList: MutableLiveData<MutableList<Entry>>
get() = _entryList
init {
_entry = MutableLiveData<Entry>()
_entryList.value = mutableListOf()
}
fun addEntry(entryInfo: Entry){
_entry.value = entryInfo
_entryList.value?.add(_entry.value!!)
}
}
And this is the code for the observer in the list fragment:
Observer { entryList ->
val entryListView: View = inflater.inflate(R.layout.fragment_entry_list, null, false)
if (entryList.isNullOrEmpty()) {
entryListView.instructions_text_view.visibility = View.VISIBLE
} else {
entryListView.instructions_text_view.visibility = View.GONE
}
entryList.forEach {entry ->
val view: View = inflater.inflate(R.layout.entry_list_item, null, false)
view.date_entry_text_view.text = String.format(getString(R.string.date), entry.date)
view.calories_entry_text_view.text =
view.line_divider
binding.entryList.addView(view)
}
Thanks for any help.
I guess you are expecting your observer to get notified of the event when you are adding entryInfo to your event list (_entryList.value?.add(_entry.value!!).
But this won't happen as you are just adding an element to the same mutable list, and as the list reference hasn't changed, live data won't emit any update.
To solve this, you have two options.
Create a new boolean live data which controls when to show and hide the info text. Set its initial value to false, and update it to true in addEntry() function.
Instead of updating the same mutable list, create of copy of it, add the element and set the entryList.value equal to this new list. This way your observer will be notified of the new list.
Additionally, its generally not a good practice to expose mutable data unless there is no alternative. Here you are exposing a mutable list of Entry and that too in the form of a mutable live data. Ideally, your should be exposing LiveData<List<Entry>>.
This is one possible implementation of all the points that I mentioned:
private val _entryList = MutableLiveData(listOf<Entry>()) // Create a private mutable live data holding an empty entry list, to avoid the initial null value.
val entryList: LiveData<List<Entry>> = _entryList // Expose an immutable version of _entryList
fun addEntry(entryInfo: Entry) {
_entryList.value = entryList.value!! + entryInfo
}
I haven't used the _entry live data here, but you can implement it the same way.
set your viewModel to observe on entry added.
I think you have gotten your visibility toggle in the your if else blocks wrong.
if (entryList.isNullOrEmpty()) {
entryListView.instructions_text_view.visibility = View.GONE // OR View.INVISIBLE
} else {
entryListView.instructions_text_view.visibility = View.VISIBLE
}
Your Observer should get notified of changes to entryList when _entryList has changed. Make sure you are calling addEntry() function to trigger the notification.

How to initialize adapter with pojo class instead of list?

Hello I am learning the new android jetpack with kotlin. While using recyclerview with binding we need to initialize the adapter with an empty list , most of the time I have seen people passing list to adapter. But how to do the same with a nested pojo class instead of a list ?
Here is the the data sample
{"results":[{"gender":"female","name":{"title":"Ms","first":"Ines","last":"Hernandez"},"location":{"street":{"number":1495,"name":"Calle de Alcalá"},"city":"Málaga","state":"Melilla","country":"Spain","postcode":86664,"coordinates":{"latitude":"-17.4716","longitude":"106.6687"},"timezone":{"offset":"-5:00","description":"Eastern Time (US & Canada), Bogota, Lima"}},"email":"ines.hernandez#example.com","login":{"uuid":"66c3248d-d257-45b1-bd9c-3ebcba6e7b7a","username":"smallduck939","password":"maxxxx","salt":"kxEtQgmY","md5":"14e95cbbda70d692f74c5073a21b6a1e","sha1":"bf12732af070dd38e6418b31260ba10f74f14e94","sha256":"7f1b6be17c0bf339d6153c7c4379fd0d8564bf0cfa2f9e6ae9caef2a037f7e0a"},"dob":{"date":"1982-04-07T17:51:39.317Z","age":39},"registered":{"date":"2016-02-20T17:10:42.682Z","age":5},"phone":"964-947-469","cell":"677-793-075","id":{"name":"DNI","value":"96170049-S"},"picture":{"large":"https://randomuser.me/api/portraits/women/66.jpg","medium":"https://randomuser.me/api/portraits/med/women/66.jpg","thumbnail":"https://randomuser.me/api/portraits/thumb/women/66.jpg"},"nat":"ES"}]}
Here are my data classes
data class UserResponse(val results:ArrayList<User>?)
and the subsequent ones so you can see I have started the starting point with "results" key.
Now while declaring the adapter I need to pass a list like
private val listAdapter = UserAdapter(arrayListOf())
and then declare something like
//from another project
private val ListDataObserver = Observer<List<Animal>> {list-> list?.let { binding.animalList.visibility=View.VISIBLE
listAdapter.updateanimalList(it)}}
But you can see in the existing data the data is of type Single<> ie starting with a key, so how do I initialize the recyclerview in this case
private val listAdapter = UserAdapter(arrayListOf())
as the data class is not of type list ? Should I directly parse from here ?
[{"gender":"female","name":{"title":"Ms","first":"Ines","last":"Hernande
Please help me with this as I am not able to initialize the recyclerview and use the observable subsequently.
Thanks :)
You have to pass the same list to the adapter object which you are getting from the observer, see below code
private val listAdapter = UserAdapter(arrayListOf<Animal>())
private val ListDataObserver = Observer< UserResponse> {userRes-> userRes?.let { binding.animalList.visibility=View.VISIBLE listAdapter.updateanimalList(it.result)}}
NOTE: Animal is the list that you are getting from the observer in the UserResponse object

Removing elements from a list that is used by liveData does not remove any

Android 4.1.2
Kotlin 1.4.21
I have the following live data that I add to, but when it comes to removing it doesn't remove any elements.
val selectedLiveData by lazy { MutableLiveData<List<Core>>() }
I don't want to trigger the observers so I am not assigning the value as I just want to remove a single element from the liveData list and only trigger when adding.
None of the following work
selectedLiveData.value?.toMutableList()?.apply {
removeAt(0)
}
selectedLiveData.value?.toMutableList()?.apply {
removeFirst()
}
selectedLiveData.value?.toMutableList()?.apply {
remove(Core)
}
I am adding my elements like this and then assigning the value so the observers to this live data get updated:
selectedLiveData.value = selectedLiveData.value?.toMutableList()?.apply {
add(core)
}
What you wanted is
val selectedLiveData = MutableLiveData<List<Core>>(emptyList())
Then
selectedLiveData.value = selectedLiveData.value.toMutableList().apply {
removeAt(0)
}.toList()
So what are you doing exactly:
You create a MutableLiveData with a List of objects. As we know in Kotlin List is immutable, so it's readonly.
If you want to add / remove items from a list, you should use MutableList.
If we look the documentation of toMutableList which you are using:
/**
* Returns a new [MutableList] filled with all elements of this collection.
*/
public fun <T> Collection<T>.toMutableList(): MutableList<T> {
return ArrayList(this)
}
So every time you try to remove an item via:
selectedLiveData.value?.toMutableList()
you actually perform that operation on a new MutableList not the original one.
If you want to add / remove I suggest you to use MutableList in your MutableLiveData so you can create something similar to this:
private val selectedLiveData = MutableLiveData<MutableList<Int>>()
// Init
selectedLiveData.value = mutableListOf(100, 200)
// Add items
selectedLiveData.value?.add(2)
selectedLiveData.value?.add(10)
selectedLiveData.value?.add(50)
// Remove item
selectedLiveData.value?.remove(2)
selectedLiveData.postValue(selectedLiveData.value.toMutableList().apply {
removeAt(0)
}.toList())

Remove a value from ArrayList<Custom> without running a loop

I have a list of CustomObject and I wish to remove a value from the list with same id(property of CustomObject).
Now this can be done by using loop. But that seems a very unnecessary and inefficient code to me. Is there any other way to get specific object with id?
If you don't want to use the removeIf method, you can do something like this:
yourList.apply { removeAt( first { it.id == id } ) }
If your project's min sdk version is 24 and up then you can use removeIf
yourList.removeIf(model -> model.id == yourid);
If you check the code inside removeIf, actually it also iterate
throughout the list to remove matching object.
Beside this, if you are using Kotlin or want to, Then you can filter the list with your id and then remove those from your list.
val yourList = arrayListOf<Model>()
val removeList = yourList.filter { model ->
model.id == yourid
}
yourList.removeAll(removeList)

How to read a series of items under a child node in Firebase using FirebaseAdapter

As the title states, I'm trying to read a bunch of nodes under a nested node in Firebase, and display the information using FirebaseAdapter. I'm using the parseSnapshot method to try and grab the information I need but I think I'm misunderstanding exactly how to go about getting the information. The user section of the database is structured like so:
I want only the information under UserInfo, and so I currently have the following code setup to intialize a recyclerview adapter (which gets the information)
private fun setupRequiredRecyclerView() {
val requiredItems = private_items_recycler
val context = this
val userDataRef = mDatabaseReference.child("Users/${prefs.UID}/UserInfo")
val mAdapter = RequiredItemsAdapter(User::class.java, R.layout.privaterecyclerview_item_row, RequiredProfileItemsViewHolder::class.java, userDataRef, context)
//load data into adapter
requiredItems.adapter = mAdapter
//add divider between items
requiredItems.addItemDecoration(Utilities.createDivider(this))
}
But the data snapshot I get back in the parseSnapshot method only seems to contain "dateJoined" and no other nodes, I'm guessing there's something wrong with my reference, but I don't know how to structure it - going up to "Users/UID" gets me everything but it also gets me UserInfoComplete, which I don't want (and as far as I know, there's no way to ignore that data in parseSnapshot, as FirebaseAdapter grabs every child node)
Does anyone out there know how exactly I need to structure my database reference to only get the UserInfo data?
(If necessary, this is my current parseSnapshot method):
override fun parseSnapshot(snapshot: DataSnapshot?): User {
lateinit var user : User
Log.i("Snapshot Data", snapshot!!.value.toString())
var dateJoined = snapshot!!.value
var dateOfBirth = snapshot.child("dateOfBirth").value
var gender = snapshot.child("Gender").value
var location = snapshot.child("Location").value
var phoneNumber = snapshot.child("phoneNumber").value
Log.i("Snapshot Data", snapshot!!.value.toString())
user.dateJoined = dateJoined as Long
return user
}

Categories

Resources