IS it possible for a recyclerview to not update when the activity is opened, but will update if reopened?
db = FirebaseFirestore.getInstance()
db.collection("Helpers")
.whereEqualTo("helperReady", true)
.whereEqualTo("state", "Online")
.addSnapshotListener(object : EventListener<QuerySnapshot> {
override fun onEvent(value: QuerySnapshot?, error: FirebaseFirestoreException?) {
if (error != null) {
Log.d("Firestore Error: ", error.message.toString())
return
}
newArrayList.clear()
for (dc: DocumentChange in value?.documentChanges!!) {
val currentUser = dc.document.toObject(HelperList::class.java)
if (dc.type == DocumentChange.Type.ADDED) {
if (auth.currentUser?.uid != currentUser?.userID) {
newArrayList.add(dc.document.toObject(HelperList::class.java))
newArrayList.sortByDescending {
it.rating
}
}
}
}
helperAdapter.notifyDataSetChanged()
}
})
Adapter:
class HelperAdapter(private val context: Context, private val helperList: ArrayList<HelperList>) : RecyclerView.Adapter<HelperAdapter.MyViewHolder>(){
private lateinit var mListener: onItemClickListener
interface onItemClickListener{
fun onItemClick(position: Int)
}
fun setOnItemClickListener(listener:onItemClickListener){
mListener = listener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val helperView = LayoutInflater.from(parent.context).inflate(R.layout.helpers_list_view, parent, false)
return MyViewHolder(helperView, mListener)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentHelper = helperList[position]
var delimiter = " "
var firstName = currentHelper.helperName!!.substringBefore(delimiter)
holder.helperName.text = firstName
holder.helperBio.text = currentHelper.helperBio
holder.helperRating.text = currentHelper.rating
Glide.with(context).load(currentHelper.profileImage).into(holder.helperImage)
}
override fun getItemCount(): Int {
return helperList.size
}
class MyViewHolder(itemView: View, listener: onItemClickListener) : RecyclerView.ViewHolder(itemView){
val helperImage: CircleImageView = itemView.findViewById(R.id.helper_image)
val helperName: TextView = itemView.findViewById(R.id.helper_name)
val helperBio: TextView = itemView.findViewById(R.id.helper_bio)
val helperRating: TextView = itemView.findViewById(R.id.helper_rating)
init{
itemView.setOnClickListener {
listener.onItemClick(adapterPosition)
}
}
}
}
Array List:
data class HelperList(var helperName: String ?= null, var helperBio: String?=null, var helperPay: String ?=null, var helperSkills: String ?=null,
var helperNumber: String ?=null, var profileImage: String ?= null, var rating: String ?= null,
val userID: String, var tokenID: String ?= null){
constructor():this ("","", "", "", "", "", "", "", "")
}
This code updates the list but everytime a data changes, the list goes completely blank and will only show the updated item, when reopened it shows the item with the rest of the list. I want it to stay the way it is and the update to only show if the activity is reopened. Any help is appreciated.
Edit: This is all on the assumption that newArrayList is the list used by the RecyclerView to display data.
Let's check your code.
You are updating the list as it is being changed.
If we look at your code, you are listening to data changes in firebase
.addSnapshotListener
This means that whenever data changes in the database, your code will trigger.
In your code, you are then doing this
newArrayList.clear()
which basically clears the data in the list.
After that, you check if this event is true if (dc.type == DocumentChange.Type.ADDED), so you only add a newly added item in the now empty list. After all this, you then triger helperAdapter.notifyDataSetChanged() so the recyclerView updates with only the now one item size list.
Not sure if it will be the perfect approach, but you can remove the call to newArrayList.clear(), and then your list will update in real time without clearing everything currently in it.
If you want to make it even better, you should separate the data updating from the recyclerView update. Ideally, you should create a function in your class to set the new list of items.
This way, you can even check before calling the method to set the new items if the list is empty, and only set new data if it is empty by calling rvAdapter.itemCount.
Code for the recyclerViewAdapter
private var dataList: MutableList<MyData> = mutableListOf()
//for setting all the data when list is empty.
fun setItems(newItemsList : List<MyData>){
dataList.clear()
dataList.addAll(newItemsList)
notifyDataSetChanged()
}
//for only adding items to an existing list.
fun addItems(newItemsList : List<MyData){
//here maybe do some logic if your data is sorted and you don't want to break it
val lastIndex = dataList.lastIndex
dataList.addAll(lastIndex,newItemsList)
notifyItemRangeInserted(lastIndex, newItemsList.size)
}
Then listen in the activity/fragment to data changes, and call the corresponding function.
Related
Data Class
data class productAddOnVariations (var name : String? =null, var id :String? =null, var price :String? =null)
Adapter
class CartAdapter(val product: List<productAddOnVariations>) : RecyclerView.Adapter<CartAdapter.customizationViewHolder>(){
inner class customizationViewHolder(itemView: View) :RecyclerView.ViewHolder(itemView) {
val radioButton = itemView.radioButton
val tvcustomPrice = itemView.tvCustomPrice
}
//its show the item count in recycleView
override fun getItemCount(): Int {
return product.size
}
override fun onBindViewHolder(holder: customizationViewHolder, position: Int) {
holder.radioButton.text = product[position].name
holder.tvcustomPrice.text =product[position].price
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): customizationViewHolder {
return customizationViewHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.customization_layout,parent,false))
}
}
BootSheet
var list1 = document.get("product_add_ons") as List<productAddOnVariations>
val userModel: productAddOnVariations = document.toObject(productAddOnVariations::class.java)!!
var hashMap = HashMap<String,String>()
hashMap.put(list1.toString(),userModel.toString()
for (list1 in userModel.toString()){
println("$key = $value")
}
cartAdapter = CartAdapter(list1)
rvChooseCustomiztion.layoutManager = LinearLayoutManager(requireContext())
rvChooseCustomiztion.adapter = cartAdapter
We have to read data from firebase and the structure is of array list as show in screenShot so how to print that time of data in recycleView where one side of it is key ="Food Name" and value =30 like this we have to print in recycleview.
It should be better to restructure your Firestore document by adding a third field called food name in order to specify "Double cheese", "Extra veggies", etc. there.
It will make it easier to fetch data from your DB.
I'm successfully passing data from MainActivity to my recyclerView via adapter, and my view with items is rendering correctly. However, I need to change one member of my item object on click (status), and i wrote a method for that (updateStatus), and it works great, it changes the value and save it to database.
But i cannot refresh my recyclerView, so it could render changed Status attribute. I need to go back on my phone, reenter, and then it renders it correctly. I have tried everything, from notifyDataSetChanged to restarting adapter, no luck. There is something missing and I can't find what.
Here is my MainActivity class
class MainActivity : AppCompatActivity() {
private var posiljkaDAO: PosiljkaDAO? = null
private var dostavnaKnjizicaDAO: DostavnaKnjizicaDAO? = null
private var allItems: ArrayList<DostavnaKnjizicaModel> = arrayListOf()
var adapter = RecycleViewAdapter(allItems)
private var eSifraPosiljke: EditText? = null
#RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_listview)
//get logo
supportActionBar!!.setDisplayShowHomeEnabled(true)
supportActionBar!!.setLogo(R.drawable.logo_bp)
supportActionBar!!.setDisplayUseLogoEnabled(true)
dostavnaKnjizicaDAO = DostavnaKnjizicaDAO(this)
dostavnaKnjizicaDAO?.closeDB()
getAllItems(this)
//connecting adapter and recyclerView
adapter = RecycleViewAdapter(allItems)
recycleView.adapter = adapter
recycleView.layoutManager = LinearLayoutManager(this)
recycleView.setHasFixedSize(true)
eSifraPosiljke = findViewById<EditText>(R.id.eSifraPosiljke)
posiljkaDAO = PosiljkaDAO(this)
}
//method that gets all items from database
private fun getAllItems(context: Context) {
var dostavenFromLOcal = dostavnaKnjizicaDAO?.getAllLocalDostavneKnjizice(context)
if (dostavenFromLOcal != null) {
allItems = dostavenFromLOcal
}
}
//method that changes status of an item
fun changeStatus(context: Context, IdDostavne: Int, statusDostavne: Int) {
dostavnaKnjizicaDAO = DostavnaKnjizicaDAO(context)
dostavnaKnjizicaDAO?.changeStatus(IdDostavne, statusDostavne)
getAllItems(context)
adapter.notifyDataSetChanged()
}
}
and my Adapter class
class RecycleViewAdapter(var dostavneKnjiziceBP: ArrayList<DostavnaKnjizicaModel>)
: RecyclerView.Adapter<RecycleViewAdapter.ViewHolder>() {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view){
val nazivPrimaoca: TextView = view.txtNazivPrimaoca
val brojPosiljke: TextView = view.txtBrojPosiljke
val statusDostave: TextView = view.txtStatusDostave
val imgMore: ImageView = view.img_more
val context: Context = view.context
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutView = LayoutInflater.from(parent.context).inflate(R.layout.urucenje_posiljke_layout, parent, false)
return ViewHolder(layoutView)
}
override fun getItemCount() = dostavneKnjiziceBP.size
#RequiresApi(Build.VERSION_CODES.KITKAT)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
//New variable to get all modeliPosiljakaBP and their position
var dosKnjizica = dostavneKnjiziceBP[position]
val mainActivity = MainActivity()
//Sending data to layout for display in specific field
if (dosKnjizica.naziv_primaoca != null) {
holder.brojPosiljke.text = "${dosKnjizica.id_dostavna_knjizica}, "
holder.nazivPrimaoca.text = "${dosKnjizica.naziv_primaoca}"
if (dosKnjizica.naziv_primaoca!!.length > 25) {
holder.nazivPrimaoca.text = "${dosKnjizica.naziv_primaoca!!.subSequence(0, 25)}..."
}
} else {
holder.brojPosiljke.text = "${dosKnjizica.id_dostavna_knjizica}"
holder.nazivPrimaoca.text = ""
}
holder.statusDostave.text = "${dosKnjizica.status_dostave_naziv}"
when (dosKnjizica.status_dostave) {
StatusDostaveEnum.Neurucena.value -> {
holder.statusDostave.setTextColor(Color.RED)
}
StatusDostaveEnum.Uruceno.value, StatusDostaveEnum.ZaRejon.value, StatusDostaveEnum.Nadoslano.value, StatusDostaveEnum.Izgubljeno.value -> {
holder.statusDostave.setTextColor(Color.GREEN)
}
StatusDostaveEnum.Obavjesteno.value, StatusDostaveEnum.ZaNarednuDostavu.value -> {
holder.statusDostave.setTextColor(Color.BLUE)
}
StatusDostaveEnum.Retour.value -> {
holder.statusDostave.setTextColor(Color.parseColor("#dda0dd"))
}
}
//Calling menu menu_pregled_drugih_vrsta_posiljke to display menu options on click on three dots
holder.imgMore.setOnClickListener {
val popupMenu = PopupMenu(holder.context, it, Gravity.START)
popupMenu.setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.uruci -> {
//calling new activity from second item in dropdown menu
holder.imgMore.context.startActivity(
Intent(holder.imgMore.context, MainActivityInfo::class.java).putExtra(
"Id", dosKnjizica.id_dostavna_knjizica.toString()
)
)
true
}
//here i am calling my changeStatus method from MainActivity
R.id.obavjesti -> {
mainActivity.changeStatus(holder.context, dosKnjizica.id_dostavna_knjizica!!, StatusDostaveEnum.Uruceno.value)
Toast.makeText(holder.context, "obavjesti", Toast.LENGTH_SHORT).show()
true
}
R.id.vrati -> {
Toast.makeText(holder.context, "vrati", Toast.LENGTH_SHORT).show()
true
}
else -> false
}
}
popupMenu.inflate(R.menu.menu_urucenje_posiljke)
popupMenu.show()
}
}
}
Your adapter doesn't have the updated data. Initially, you fetch all data from the database and create an adapter with it: adapter = RecycleViewAdapter(allItems). Afterwards, you are updating the database, calling getAllItems(Context) but you don't pass the data to the adapter.
Add the line adapter.dostavneKnjiziceBP = allItems to the changeStatus method like this:
//method that changes status of an item
fun changeStatus(context: Context, IdDostavne: Int, statusDostavne: Int) {
dostavnaKnjizicaDAO = DostavnaKnjizicaDAO(context)
dostavnaKnjizicaDAO?.changeStatus(IdDostavne, statusDostavne)
getAllItems(context)
adapter.dostavneKnjiziceBP = allItems
adapter.notifyDataSetChanged()
}
Save dostavneKnjiziceBP as a private var inside the adapter and create functions for assigning and updating that ArrayList from within the adapter, using notifyDataSetChanged() everytime a change is done.
class RecycleViewAdapter internal constructor(
context: Context
) : RecyclerView.Adapter<RecycleViewAdapter.ViewHolder>() {
private var items = ArrayList<DostavnaKnjizicaModel>()
// ...
internal fun setItems(items: ArrayList<DostavnaKnjizicaModel>) {
this.items = items
notifyDataSetChanged()
}
override fun getItemCount() = this.items.size
}
Also, try using adapter.notifyItemChanged(updateIndex); if you know the index of the updated item.
I am trying to update my nested RecyclerView list using submitList in a LiveData Observer.
The list is submitted, but the UI only updates when you touch the screen.
The issue arose when I added nested RecyclerViews in my master RecyclerView.
So I think it has something to do with it.
I want it to update after the submitList is called.
What am I doing wrong?
Outer ListAdapter:
class ReservationAdapter(
private val changeState: (visit: Visit, newState: Visit.State) -> MutableLiveData<Visit?>
) : ListAdapter<Reservation, ReservationViewHolder>(ReservationDiffCallback()) {
private val recycledViewPool = RecyclerView.RecycledViewPool()
/// Filters \\\
// Building
var filterBuildingId = ""
set(value) {
field = value
filteredVisits = visits.filter(Visit.filter(value, filterSearchString))
}
// Search string
var filterSearchString = ""
set(value) {
field = value
filteredVisits = visits.filter(Visit.filter(filterBuildingId, value))
}
/// Filtering chain \\\
// Visits come in and get filtered
var visits = arrayListOf<Visit>()
set(value) {
field = value
filteredVisits = value.filter(Visit.filter(filterBuildingId, filterSearchString))
}
// Filtered visits come in, a reservation map is made
var filteredVisits = listOf<Visit>()
set(value) {
field = value
reservationVisits = Reservation.extractMapFromVisits(value)
}
// Reservation map comes in, and submits a list of reservations
private var reservationVisits = hashMapOf<Reservation, ArrayList<Visit>>()
set(value) {
field = value
_reservationVisits.value = value
submitList(value.keys.toMutableList())
notifyDataSetChanged()
}
private val _reservationVisits = MutableLiveData<HashMap<Reservation, ArrayList<Visit>>>()
/**
* Inflate items
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReservationViewHolder {
return ReservationViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.list_reservation,
parent,
false
),
recycledViewPool,
changeState,
_reservationVisits
)
}
/**
* Bind items
*/
override fun onBindViewHolder(holder: ReservationViewHolder, position: Int) {
val reservation = getItem(position)
holder.bind(reservation, reservationVisits[reservation])
}
}
Outer ViewHolder:
class ReservationViewHolder(
private val view: View,
private val recycledViewPool: RecyclerView.RecycledViewPool,
changeState: (visit: Visit, newState: Visit.State) -> MutableLiveData<Visit?>,
reservationVisits: MutableLiveData<HashMap<Reservation, ArrayList<Visit>>>
) : RecyclerView.ViewHolder(view) {
private val adapter = VisitAdapter(changeState)
private var _reservation: Reservation? = null
init {
/*
THIS IS WHERE I SETS THE VALUE, BUT DOESN'T UPDATE THE UI.
*/
reservationVisits.observe(view.context as LifecycleOwner) {
if (_reservation != null) {
(view.context as MainActivity).runOnUiThread {
adapter.submitList(null)
adapter.notifyDataSetChanged()
adapter.submitList(it[_reservation!!]?.toMutableList())
adapter.notifyDataSetChanged()
view.refreshDrawableState()
}
}
}
}
fun bind(reservation: Reservation, visits: ArrayList<Visit>?): View = with(view) {
println("3: ${visits?.size}")
_reservation = reservation
// Initialize values
txtSubject.text = reservation.subject
txtTime.text = "TIME"
// Recycler view for visits
rcvVisits.apply {
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
layoutManager = LinearLayoutManager(context)
adapter = this#ReservationViewHolder.adapter
setRecycledViewPool(this#ReservationViewHolder.recycledViewPool)
}
// Add visits to adapter
adapter.submitList(visits)
// Return view
view
}
}
Inner ListAdapter:
class VisitAdapter(
private val changeState: (visit: Visit, newState: Visit.State) -> MutableLiveData<Visit?>
) : ListAdapter<Visit, VisitViewHolder>(VisitDiffCallback()) {
// Sorter
private val _visitSorter = compareBy<Visit>({ it.state }, { it.expectedArrival }, { it.visitor?.name })
// ListAdapter create
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VisitViewHolder {
return VisitViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.list_visitor,
parent,
false
),
changeState
)
}
// ListAdapter bind
override fun onBindViewHolder(holder: VisitViewHolder, position: Int) {
holder.bind(getItem(position))
}
// ListAdapter submit
override fun submitList(list: List<Visit>?) {
super.submitList(list?.sortedWith(_visitSorter) ?: listOf())
}
}
I checked your code these are what I found:
you created an instance of the adapter and an observer in view holder class.
view holder will instantiate multiple times, so you shouldn't instantiate the adapter in it. check this link
and if you want to pass data to your view holder, you should do it in the constructor, never observe for data change in view holder class.
so you can observe data in activity and pass data to your adapter.
and there is no need to call runOnUiThread in an observer. its already on main Thread (UI Thread).
The events are dispatched on the main thread. If LiveData already has data set, it will be delivered to the observer.
LiveData
I solved it.
The issue was with the com.mikepenz:aboutlibraries:7.0.4#aar library.
After I removed it and commented out any code that used it, it started working.
I am trying to access data once it is completely retrieved from database? initially I apply adapter to fragment. With in the Adapter I tried to retrieve data from firebase database. So here give problem it send the null arraylist. It should send back the arraylist when complete data is retrieved?
Adapter code :
class FirebaseAdapter(context: Context): RecyclerView.Adapter<FirebaseAdapter.Holder>() {
var dataList: ArrayList<DatabaseOperations.ImageInfo> = arrayListOf()
var context: Context? = null
init {
if (context == null)
this.context = context
dataList= DatabaseOperations().retriveInfo(context!!)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
var itemView: View = LayoutInflater.from(parent.context).inflate(R.layout.imagelist_row,parent)
var viewHolder: FirebaseAdapter.Holder = FirebaseAdapter.Holder(itemView)
return viewHolder
}
override fun getItemCount(): Int {
Log.e("itemCoutn",dataList.size.toString()) // it give output 0
return dataList.size
}
override fun onBindViewHolder(holder: Holder, position: Int) {
try {
//----------------get bitmap from image url-----------------
var downloadUri: String = dataList.get(position).downloadUri
Log.e("fire adapter",downloadUri.toString())
//------------------Assign Data to item here-----------------
holder.image_name.text = dataList.get(position).imageName
Glide.with(this!!.context!!)
.load(downloadUri)
.into(holder.row_image)
}
catch(e: Exception){
Log.e("Firebase Adapter","Error "+e.toString())
}
}
class Holder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
val row_image: ImageView
val image_name: TextView
init {
row_image = itemView!!.findViewById<ImageView>(R.id.row_image)
image_name = itemView!!.findViewById<TextView>(R.id.image_name)
}
}
}
Information retrieve code :
fun retriveInfo( context: Context): ArrayList<ImageInfo>{
var data = ArrayList<ImageInfo>()
if (mDatabaseRefrence == null)
mDatabaseRefrence = FirebaseDatabase.getInstance().getReference(getUid())
val menuListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
var dataSnap: DataSnapshot? = null
var it: Iterable<DataSnapshot> = dataSnapshot.children
it.forEach { dataSnapshot ->
data.add(ImageInfo(
dataSnapshot!!.child("imageName").toString(),
dataSnapshot!!.child("imageInfo").toString(),
dataSnapshot!!.child("downloadUri").toString()
))
}
FirebaseAdapter(context).notifyDataSetChanged()
Log.e("db size 0",data.size.toString())
}
override fun onCancelled(databaseError: DatabaseError) {
println("loadPost:onCancelled ${databaseError.toException()}")
}
}
mDatabaseRefrence!!.addValueEventListener(menuListener)
Log.e("db size",data.size.toString())
return data
}
You cannot return something now that hasn't been loaded yet. With other words, you cannot simply return the data list as a result of a function because the list it will always be empty due the asynchronous behaviour of this function. This means that by the time you are trying to return that result, the data hasn't finished loading yet from the database and that's why is not accessible.
Basically, you're trying to return a value synchronously from an API that's asynchronous. That's not a good idea. You should handle the APIs asynchronously as intended.
A quick solve for this problem would be to use the data list only inside the callback (inside the onDataChange() method). If you want to use it outside, I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.
Been using realm and it's awesome.
Came up against something. Wondering if I'm doing something wrong.
I have a RealmRecyclerViewAdapter that I'm using to show the results of a realm query. This works perfectly if I add or update records in the realm. I had to setHasFixedSize(false) on the recycler view to get it to update on the fly. Not sure if this is correct but it worked.
Anyway, that's not my issue.
I'm experimenting with filtering my data. I have the following query:
realm.where(Person::class.java).contains("name", nameFilter, Case.INSENSITIVE).findAllSorted("name")
I'm passing this RealmResults to my recycler view and it works great on add/update.
However, when I attempt a filter, it doesn't update automatically.
Am I right in saying that simply changing my filter (specified by nameFilter) isn't enough for the query to be re-run? This would be fair enough I suppose. Since I guess there's no trigger for realm to know I've changed the value of the string.
However, even if I recalculate my query, it doesn't seem to update in the Recycler View unless I explicitly call updateData on my adapter. I'm not sure if this is the best or most efficient way to do this. Is there a better way?
Complete Code:
Main Activity
class MainActivity : AppCompatActivity(), View.OnClickListener {
private val TAG: String = this::class.java.simpleName
private val realm: Realm = Realm.getInstance(RealmConfiguration.Builder().deleteRealmIfMigrationNeeded().build())
private var nameFilter = ""
private var allPersons: RealmResults<Person> = realm.where(Person::class.java).contains("name", nameFilter, Case.INSENSITIVE).findAllSorted("name")
private val adapter: PersonRecyclerViewAdapter = PersonRecyclerViewAdapter(allPersons)
private lateinit var disposable: Disposable
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
realm.executeTransaction({
// realm.deleteAll()
})
Log.i(TAG, "Deleted all objects from Realm")
buttonAddOrUpdatePerson.setOnClickListener(this)
setUpRecyclerView()
disposable = RxTextView.textChangeEvents(editTextNameFilter)
// .debounce(400, TimeUnit.MILLISECONDS) // default Scheduler is Computation
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith<DisposableObserver<TextViewTextChangeEvent>>(getSearchObserver())
}
private fun getSearchObserver(): DisposableObserver<TextViewTextChangeEvent> {
return object : DisposableObserver<TextViewTextChangeEvent>() {
override fun onComplete() {
Log.i(TAG,"--------- onComplete")
}
override fun onError(e: Throwable) {
Log.i(TAG, "--------- Woops on error!")
}
override fun onNext(onTextChangeEvent: TextViewTextChangeEvent) {
nameFilter = editTextNameFilter.text.toString()
allPersons = realm.where(Person::class.java).contains("name", nameFilter, Case.INSENSITIVE).findAllSorted("name")
// this is necessary or the recycler view doesn't update
adapter.updateData(allPersons)
Log.d(TAG, "Filter: $nameFilter")
}
}
}
override fun onDestroy() {
super.onDestroy()
realm.close()
}
override fun onClick(view: View?) {
if(view == null) return
when(view) {
buttonAddOrUpdatePerson -> handleAddOrUpdatePerson()
}
}
private fun handleAddOrUpdatePerson() {
val personToAdd = Person()
personToAdd.name = editTextName.text.toString()
personToAdd.email = editTextEmail.text.toString()
realm.executeTransactionAsync({
bgRealm -> bgRealm.copyToRealmOrUpdate(personToAdd)
})
}
private fun setUpRecyclerView() {
recyclerViewPersons.layoutManager = LinearLayoutManager(this)
recyclerViewPersons.adapter = adapter
recyclerViewPersons.setHasFixedSize(false)
recyclerViewPersons.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
}
}
PersonRecyclerViewAdapter
internal class PersonRecyclerViewAdapter(data: OrderedRealmCollection<Person>?, autoUpdate: Boolean = true) : RealmRecyclerViewAdapter<Person, PersonRecyclerViewAdapter.PersonViewHolder>(data, autoUpdate) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PersonViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.person_row, parent, false)
return PersonViewHolder(itemView)
}
override fun onBindViewHolder(holder: PersonViewHolder?, position: Int) {
if(holder == null || data == null) return
val personList = data ?: return
val person = personList[position]
holder.bind(person)
}
internal class PersonViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var textViewName: TextView = view.findViewById(R.id.textViewNameDisplay)
var textViewEmail: TextView = view.findViewById(R.id.textViewEmailDisplay)
internal fun bind(person: Person) {
textViewEmail.text = person.email
textViewName.text = person.name
}
}
}
Yeah, updateData() is the way to do it. Since you updated the query, the Results you want to show becomes a different object. updateData() has to be called to notify the adapter that the data source is changed.
However, you may lose the nice animation for the RecyclerView in this way since the whole view will be refreshed because of the data source is changed. There are some ways to work around this.
eg.: You can add one field isSelected to Person. Query the results by isSelected field and pass it to the adaptor:
allPersons = realm.where(Person::class.java).equalTo("isSelected", true).findAllSorted("name")
adapter = PersonRecyclerViewAdapter(allPersons)
When changing the query:
realm.executeTransactionAsync({
var allPersons = realm.where(Person::class.java).equalTo("isSelected", true).findAllSorted("name")
for (person in allPersons) person.isSelected = false; // Clear the list first
allPersons = realm.where(Person::class.java).contains("name", nameFilter, Case.INSENSITIVE).findAllSorted("name") // new query
for (person in allPersons) person.isSelected = true;
})
It depends on your use case, if the list to show is long, this approach might be slow, you could try to add all the filtered person to a RealmList and set the RealmList as the data source of the adapter. RealmList.clear() is a fast opration than iterating the whole results set to set the isSelected field.
If the filter will mostly cause the whole view gets refreshed, updateData() is simply good enough, just use it then.