I recently started a new project on android studio. I switched to Kotlin and this language is giving me such a hard time!
I've set up a RecyclerView in my app and it's working fine. My adapter takes an ArrayList as an argument and displays all the data.
I have two functions here that create the ArrayList for my adapter: cardMaker() and getEventsInfo(). These two functions return an ArrayList with Cards elements.
When I call my adapter with an ArrayList created by getEventsInfo then all the events are showing fine.
But when I use cardMaker(), there are no events showing up!
I really can't understand what is going on here and it drives me crazy! ^^
If this language is really sequential by default, how is this behavior possible? I fell like I am missing something important here.
private fun cardMaker(): ArrayList<Card?> {
var newCards: ArrayList<Card?> = arrayListOf()
newCards.add(Card("UserCard", R.mipmap.logo_zenith_round, userData.firstName, userData.lastName))
val infoCards = getEventsInfo()
newCards.addAll(infoCards)
return newCards
}
private fun getEventsInfo(): ArrayList<Card?> {
var infoCards: ArrayList<Card?> = arrayListOf()
db.collection("Events")
.get()
.addOnSuccessListener { result ->
for (document in result) {
val eventsInfo = Card("EventCard",
R.mipmap.logo_zenith_round,
"${document.get("Name")}",
"${document.get("Date")}")
infoCards.add(eventsInfo)
}
}
.addOnFailureListener { exception ->
Log.w(TAG, "Error getting documents.", exception)
}
return infoCards
}
cards = cardMaker()
// RecyclerView
linearLayoutManager =
androidx.recyclerview.widget.LinearLayoutManager(this)
recyclerView = findViewById(R.id.recycler_view)
recyclerView.layoutManager = linearLayoutManager
adapter = RecyclerAdapter(cards)
recyclerView.adapter = adapter
Well, it was not related to Kotlin specificity.
As EpicPandaForce said: "Firebase fetches things asynchronously"
So I made a class instance of the variable cards: ArrayList and added adapter.notifyDataSetChanged() in my getEventsInfo() method inside the OnSuccessListener.
It is now working perfectly.
Thank you!
Related
Using kotlin.
I want to make childRecyclerView in parentRecyclerView.
Maybe I think this problem is from recyclerView adapter.
Data is getting from Firebase. (Cloud Firestore)
I completed 'parent RecyclerView'.
And I also wrote adapting code for childRecyclerView at parentRecyclerView's adapter.
In parentRecyclerView, I asked to receive childRecyclerView's Firebase Data
And I checked through the log that this data was well received.
But my app show only parentRecyclerView's content.
This code is adapting code for ChildRecyclerView in ParentRecyclerView's adapter.
And I write this code at ParentRecyclerView's bind() function. This fun is also contain showing ParentRecyclerView's content(Like Glide for parentRecyclerView's ImageView).
val db = FirebaseFirestore.getInstance()
val queryText2: Query = db.collection("spiceTable")
var queryText3: Query
val storageRef2 = Firebase.storage.reference.child("scentnote/spiceimage")
var j: Int = 1
var detailSpiceList = mutableListOf<SpiceDetailValue>()
queryText2.get().addOnSuccessListener { documents ->
//Loop in parentRecyclerView's content
.addOnSuccessListener { documentx ->
//Getting ChildRecyclerView's content from Firebase
}.addOnCompleteListener {
nAdapter = context?.let { NoteSubRecyclerviewAdapter(it, detailSpiceList) }
fragment2NoteParentItemBinding?.fragment2NoteChildList?.adapter = nAdapter
val gridLayoutManager = GridLayoutManager(context, 4)
fragment2NoteParentItemBinding?.fragment2NoteChildList?.layoutManager = gridLayoutManager
}
detailSpiceList = mutableListOf<SpiceDetailValue>()
From ".addOnCompleteListener", To "~gridLayoutManager}" code is same with adapting ParentRecyclerView at my Fragment. (in code, fragment2Note)
I try to Log at end of addOnSuccessListener and start of addOnCompleteListener.
And addOnCompleteListener's Log is shown faster than addOnSuccessListener's Log.
I don't know why this code work like this.
And also i don't know why this adapter can't work.
At Logcat,
No adapter attached; skipping layout
this log is shown several time but i don't know their reason.
Please help...
And I'm Sorry because I'm not good at English
Please do like this
Declare adapter at the first stage with empty data.
val db = FirebaseFirestore.getInstance()
val queryText2: Query = db.collection("spiceTable")
var queryText3: Query
val storageRef2 = Firebase.storage.reference.child("scentnote/spiceimage")
var j: Int = 1
var detailSpiceList = mutableListOf<SpiceDetailValue>()
nAdapter = context?.let { NoteSubRecyclerviewAdapter(it, detailSpiceList) }
fragment2NoteParentItemBinding?.fragment2NoteChildList?.adapter = nAdapter
val gridLayoutManager = GridLayoutManager(context, 4)
fragment2NoteParentItemBinding?.fragment2NoteChildList?.layoutManager = gridLayoutManager
queryText2.get().addOnSuccessListener { documents ->
//Loop in parentRecyclerView's content
.addOnSuccessListener { documentx ->
//Getting ChildRecyclerView's content from Firebase
// After getting data, you asssign data to adapter
adapter.setItem(detailSpiceList); // You need to create function to attach data to adapter. (it is easy and common way)
}.addOnCompleteListener {
}
In my Fragment for my Android app, I'm using Firebase Realtime Database and Moshi to save and load the data I get from my RecyclerView.
These are the functions I use for this task:
private fun saveData() {
val moshi = Moshi.Builder().add(BigDecimalAdapter).add(KotlinJsonAdapterFactory()).build()
val listMyData = Types.newParameterizedType(List::class.java, ItemCard::class.java)
val jsonAdapter: JsonAdapter<ArrayList<ItemCard>> = moshi.adapter(listMyData)
val json = jsonAdapter.toJson(dataList)
userInfo.child("jsonData").setValue(json)
}
private fun loadData(json: String) = lifecycleScope.launch(Dispatchers.IO) {
if (json != "") {
val type: Type = object : TypeToken<List<ItemCard>>() {}.type
val moshi = Moshi.Builder().add(BigDecimalAdapter).add(KotlinJsonAdapterFactory()).build()
val jsonAdapter: JsonAdapter<ArrayList<ItemCard>> = moshi.adapter(type)
dataList = jsonAdapter.fromJson(json)!!
if (dataList == null) {
dataList = arrayListOf<ItemCard>()
}
}
}
private fun buildRecyclerView() {
recyclerView = rootView.findViewById(R.id.main_recycler_view)
recyclerView.setHasFixedSize(true)
recyclerViewLayoutManager = LinearLayoutManager(this#Main.requireContext())
adapter = MainAdapter(dataList, this)
recyclerView.layoutManager = recyclerViewLayoutManager
recyclerView.adapter = adapter
}
In my onViewCreated, I having this for loading the data and building the RecyclerView:
userInfo.addValueEventListener(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
for (postSnapshot in dataSnapshot.children) {
when (postSnapshot.key) {
"jsonData" -> {
loadData(postSnapshot.value.toString())
buildRecyclerView()
}
}
}
}
override fun onCancelled(error: DatabaseError) {}
})
Everything works as I want, however, there's a delay/lag when I go to this specific fragment. There are a total of three fragments in my app. The other two work smoothly with no delay/lag, but when I click on the button or slide the screen to go to this fragment, there's a delay in the change of the UI.
What can I do to make the performance better? Where should I put my addValueEventListener? I only want it to get triggered when the fragment is first created and when the jsonData child gets changed. I believe in my onViewCreated the listener is being triggered multiple times. Is there anything else I can add to my code or modify to make the performance better when saving and loading the RecyclerView data?
when you create a listerner in onViewCreated run then make sure that you remove listener when fragment is not attach.Realtime event listerner return a string that you can easily = to your pojo class like:
ItemCard message = messageSnapshot.getValue(ItemCard.class);
this way is to saving manual converting the list effort.Last important thing that if recyclerview is initialize then don't initialize when data change only notifyDataSetChange.when you adding data in the list then make sure that the
list.clear();
otherwise you data is duplicate because on addValueEventListener return the whole data.
I am trying to update ArrayList which is inside in MutableLiveData.
whenever I change the content in ArrayList, ListAdapter is not updating.
ViewModel
private var _tileNumberList:MutableLiveData<ArrayList<TileNumber>> = MutableLiveData()
var tempTileNumberList: ArrayList<TileNumber> = ArrayList()
val tileNumberList:LiveData<ArrayList<TileNumber>>
get() = _tileNumberList
init {
for (i in 1..9){
for (j in 1..9){
//tileNumberList.add(TileNumber(0,i,j))
tempTileNumberList.add(TileNumber(0,i,j))
}
}
fun onStart() {
tempTileNumberList[count].number = 8
_tileNumberList.value = tempTileNumberList
count++
}
whenever I call onStart method list values changes but ListAdapter not updating with latest values
MainActivity
backTrackingViewModel.tileNumberList.observe(this, Observer {
it?.let {
//adapter.submitList(null)
adapter.submitList( it)
}
Toast.makeText(
applicationContext,
"Hey",
Toast.LENGTH_SHORT
).show()
})
when i set adapter.submitList(null) then my ListAdapter is updating
TileNumber
data class TileNumber(var number:Int,val row:Int,val column:Int) {
}
I tried this link references but didn't work
ListAdapter not updating item in RecyclerView
https://stackoverflow.com/a/50062174/6925888 (this is not a good solution)
DiffUtil ItemCallback areContentsTheSame() always returns true after updating an item on the ListAdapter
I think you should initialize tileNumberList list like this
var tileNumberList: LiveData<ArrayList<TileNumber>> = _tileNumberList
And then observe it in MainActivity just like you're doing it
I have two possible options why this code doesn't work:
Items are changed in place - i.e. you change the items inside the list. Since the list is passed to adapter, the adapter's list is changed as well.
Solution:
_tileNumberList.value = tempTileNumberList.toList()
You haven't overridden equals() and hashCode() for TileNumber class.
According to ListAdapter.SubmitList() documentation, until you pass the list with the same reference you will never update your list. To make it work you can simply invoke:
adapter.submitList(it.toList())
Replace this:
fun onStart() {
tempTileNumberList[count].number = 8
_tileNumberList.value = tempTileNumberList
count++
}
with this:
fun onStart() {
val tileNumber = tempTileNumberList[count].copy()
tileNumber.number = 8
tempTileNumberList.set(count, tileNumber)
_tileNumberList.value = tempTileNumberList
count++
}
Here I am using Kotlin data class copy() method.
hi I'm getting information from web with jsoup and coroutine and I want to show data in recyclerview
All the information is well received but the RecyclerView does not show anything and the view is not updated
fun myCoroutine(): ArrayList<DataModel> {
val listx = arrayListOf<DataModel>()
GlobalScope.launch { // launch new coroutine in background and continue
Log.d("asdasdasd", "start")
var doc: Document = Jsoup.connect("http://5743.zanjan.medu.ir").timeout(0).maxBodySize(0).ignoreHttpErrors(true).sslSocketFactory(setTrustAllCerts()).get()
val table: Elements = doc.select("table[class=\"table table-striped table-hover\"]")
for (myTable in table) {
val rows: Elements = myTable.select("tr")
for (i in 1 until rows.size) {
val row: Element = rows.get(i)
val cols: Elements = row.select("td")
val href: Elements = row.select("a")
val strhref: String = href.attr("href")
listx.add(DataModel(cols.get(2).text(),strhref))
Log.d("asdasf",cols.get(2).text())
}
}
}
return listx
}
private fun getData() {
itemsData = ArrayList()
itemsData = myCoroutine()
adapter.notifyDataSetChanged()
adapter = RVAdapter(itemsData)
}
and this is oncreate
var itemsData = ArrayList<DataModel>()
adapter = RVAdapter(itemsData)
val llm = LinearLayoutManager(this)
itemsrv.setHasFixedSize(true)
itemsrv.layoutManager = llm
getData()
itemsrv.adapter = adapter
This code has numerous bugs (getData, for instance, never sets the adapter onto the RecyclerView), but the biggest issue is that you're not actually waiting for listx to be populated - you're returning it immediately before it's populated. You need to either move the population of the adapter to the coroutine and run that part on the UI thread dispatcher, or use a callback, or dispatch it to the UI thread. Launching a coroutine and returning immediately doesn't make the data get populated when something tries to use it.
I have a spinner. Into that spinner I added one of my firestore collections. I thought but calling the collection I could load any data into my spinner, but when I added a second document to my collection it stopped working.
This is what I have right now:
db.collection("KitList").get().addOnSuccessListener { snapshot ->
for (document in snapshot.documents) {
val data = document.data
val skipRope = data["rope"] as String
spinnerArray.add(skipRope)
val kettle = data["kettle"] as ArrayList<String>
for (item in kettle) {
val kettleWeight = "kettle $item"
spinnerArray.add(kettleWeight)
}
}
}
I tried db.collection("KitList").document("documentname")get().addOnSuccessListener { snapshot -> but it didn't work because my snapshot.documents got an error.
Could anyone help me out here? Just want to know how I can can call multiple documents. Thanks :D
1st, create your custom data model (I will call it DataClass).
Then, in addOnSuccessListener put this code:
snapshot.documents.mapTo(spinnerArray) { it.toObject(DataClass::class.java)}
Of course, replace DataClass with your own :)