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
Related
I am working on an integration of a bluetooth sdk,
It forces me to have a static arraylist where the sdk module is hosting the read data, this statement is in Java
public ArrayList<ReaderDevice> tagsList = new ArrayList<>();
In my Kotlin activity I have the static reference
lateinit var sharedObjects: SharedObjects
To get this list of my fragments I use
HomeActivity.sharedObjects.tagsList
What I need is "some way" no matter how "dirty" to be able to have a "listener"-"observer" to know from my fragments when a new element is added to take some action x
Try to create the observable like this
//fragment
viewmodel.setReadTagsList.onNext(HomeActivity.sharedObjects.tagsList)
//viewmodel
val setReadTagsList = PublishSubject.create<List<ReaderDevice>>()
private val _tags = BehaviorSubject.create<List<ReaderDevice>>()
setReadTagsList
.bind(_tags)
.disposedBy(disposeBag)
setReadTagsList
.withLatestFrom(_tags){_, o1 -> o1}
.map { "${it.size}" }
.bind(_errorMessage)
.disposedBy(disposeBag)
but only when I do the "onnext" can I get the size, otherwise it doesn't refresh, I guess it's because it "is copied" but it doesn't have the same reference, can I somehow put the static reference to my viewmodel propertys? I'm lost how to see this list
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.
I'm new to Kotlin and trying to create an alarm clock app. In this app I'm using LiveData and RecycleView. Right now I need to change the alarm status:
Here is my AlarmsRecyclerAdapter where i tried to create .onClickListener{}
override fun onBindViewHolder(holder: AlarmsRecyclerAdapter.AlarmItemHolder, position: Int) {
//mAlarmViewModel = ViewModelProviders.of( context as Fragment)[AlarmViewModel::class.java]
if (mAlarms != null) {
val current = mAlarms!!.get(position)
holder.view.edit_time_button.text = current.printTime()
holder.view.switch_alarm_enabled.isEnabled = current.enabled
holder.view.switch_alarm_enabled.setOnClickListener {
current.enabled = !current.enabled
// mAlarmViewModel.insert(current)
}
} else {
// Covers the case of data not being ready yet.
holder.view.edit_time_button.text = "no timer"
}
}
I also tried to get instance of ViewModel in the comment line, but it just throws errors like
java.lang.ClassCastException: android.app.Application cannot be cast to androidx.fragment.app.FragmentActivity
at com.xxx.alarm.AlarmsRecyclerAdapter.onBindViewHolder(AlarmsRecyclerAdapter.kt:58)
at com.xxx.alarm.AlarmsRecyclerAdapter.onBindViewHolder(AlarmsRecyclerAdapter.kt:33)
I need to change the alarms in the database, so how can I get an instance of ViewModel in the adapter class? Or is there better way to manage the data changing?
Not really sure about getting your ViewModel inside your RecyclerView, and not really sure if this would be considered best practice. But here is the way I am doing this, and have others seen doing it.
First you create your ViewModel in you Fragment.
Then you observe your AlarmData and when it changes you update the data in your RecyclerAdapter.
So in your Fragment you do something like this():
mAlarmViewModel = ViewModelProviders.of( context as Fragment)[AlarmViewModel::class.java]
mAlarmViewMode.getAlarms().observe(...
mAdapter.setData(newData)
and inside you Adapter you add the following:
setData(data:List) {
mAlarms= data;
notifyDataSetChanged();
}
this should keep your data updated.
Now for the changing of your data.
Try Setting the OnclickListener inside your ViewHolder, as this is going to increase the speed of your app.
to get your current value you could do this:
val current = mAlarms!!.get(getAdapterPosition())
Finally you shold add a Interface to your Adapter, something like this:
interface ItemSelectedListener {
fun onItemSelected(item:Any, v:View)
}
Set this interface from your Fragment and call it from the onClickListener.
Then you have all the data you need inside your Fragment and can modify it from there
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 :)
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
}