Getting always default values of items from RecyclerViewAdapter - android

I have an activity that has a recyclerview. Each item of the recyclerview has 3 components: spinner, EditText and an ImageButton
In the activity, there's an "add users" button that should save all the info in the recycleViewer to DB. To give more context, there's also an "add user" button that adds another item to the recycleViewer.
The problem is that when I call the saveUsers function from the activity, who calls the newUserAdapter.getUsers(), that function always returns the list of items of the RecycleView empty (the editText and the Spinner) even if the user has modified the info on the recycleView
Here is my activity
class AddUser : AppCompatActivity() {
private lateinit var binding: ActivityAddUserBinding
private lateinit var newUserAdapter : AddUserRecyclerViewAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAddUserBinding.inflate(layoutInflater)
newUserAdapter = AddUserRecyclerViewAdapter()
val b = User("","")
newUserAdapter.addItem(b)
binding.recyclerViewUser.adapter = newUserAdapter
setContentView(binding.root)
}
fun addNewUserRow(view: View) {
Timber.i("AddUser addNewUserRow called")
val b = User("","")
newUserAdapter.addItem(b)
}
fun saveUsers(view: View) {
if(newUserAdapter.itemCount > 0)
{
var usr = newUserAdapter.getUsers()
//TODO: save usr to DataBase
}
else
{
Snackbar.make(view, R.string.delete_user, Snackbar.LENGTH_LONG).show()
}
}
}
This is my User data class:
data class User (
var name: String = String(),
var department: String = String()
)
And my RecycleViewAdapter:
class AddUserRecyclerViewAdapter : RecyclerView.Adapter<AddUserRecyclerViewAdapter.AddUserViewHolder>() {
private var allUsers = ArrayList<User>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddUserViewHolder {
Timber.i("User onCreateViewHolder")
val view = AddUserItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
val tempUser = AddUserViewHolder(view)
tempUser.deleteBtn.setOnClickListener {
Timber.i("NewUser setOnClickListener " + tempUser.bindingAdapterPosition)
if (this.itemCount > 1)
{
deleteItem(tempUser.bindingAdapterPosition)
}
else
{
//At least one item, we don't let delete the last one
Snackbar.make(it, R.string.delete_user, Snackbar.LENGTH_LONG).show()
}
}
return tempUser
}
override fun onBindViewHolder(holderUser: AddUserViewHolder, position: Int) {
Timber.i("NewUser onBindViewHolder $position")
val item = allUsers[position]
holderUser.nameEt.setText(item.name.toString())
}
private fun deleteItem(pos: Int) {
Timber.i("NewUser deleteItem $pos")
allUsers.removeAt(pos)
// call notifyDataSetChanged() to notify our adapter.
notifyItemRemoved(pos)
}
fun addItem(item: User) {
allUsers.add(item)
// call notifyDataSetChanged() to notify our adapter.
notifyItemInserted(allUsers.size -1)
}
fun getUsers(): ArrayList<User> {
return allUsers
}
override fun getItemCount(): Int {
Timber.i("NewUser getItemCount called" + allUsers.size)
return allUsers.size
}
inner class AddUserViewHolder(binding: AddUserItemBinding) :
RecyclerView.ViewHolder(binding.root) {
var nameEt: EditText = binding.etName
var deleteBtn : ImageButton = binding.btnDelete
var spinnerDepartment : Spinner = binding.spinnerDepartment
}
}
Edit
As it seems that someone downvoted for lack of information, let me say it in different words. This is the function in RecycleViewAdapter:
fun getUsers(): ArrayList<User> {
return allUsers
}
Who is called when a button is clicked from the activity:
fun saveUsers(view: View) {
if(newUserAdapter.itemCount > 0)
{
var usr = newUserAdapter.getUsers()
//TODO: save usr to DataBase
}
else
{
Snackbar.make(view, R.string.delete_user, Snackbar.LENGTH_LONG).show()
}
}
So the var usr is where I get a list of empty users, where I expected to get a list of users with the information filled in the RecycleView.

You should update the property of the user in the ArrayList when you are changing the text with an addTextChangedListener
override fun onBindViewHolder(holderUser: AddUserViewHolder, position: Int) {
Timber.i("NewUser onBindViewHolder $position")
val item = allUsers[position]
holderUser.nameEt.setText(item.name.toString())
holderUser.nameEt.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(p0: Editable?) { }
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { }
override fun onTextChanged(text: CharSequence?, p1: Int, p2: Int, p3: Int) {
// Update user instance in allUsers
allUsers[position].name = text.toString()
}
})
}
You may want also want to define an onItemSelectedListener on the Spinner to update the selected department as well.

Related

position of recyclerview item changes after opening the fragment again

I am using android kotlin. i am working on online shopping app.
in the shopping cart section. all things works fine.adding an item to cart or delete the item or adding quantity and undoing the delete .
when i click on undo button the item is placed in its own position after undoing the deleted item. but when i close the fragment and open it again all item position changes and the item that i clicked first will be first item and so to the end
this is my recycler adapter:
class BasketRecyclerAdapter(val list: MutableList<Model.BasketItem>, val listener: ButtonListener) :
RecyclerView.Adapter<BasketRecyclerAdapter.ViewHolder>() {
lateinit var context: Context
lateinit var localStore: LocalStore // i saved user phone and pass in a shared prefs using this class
inner class ViewHolder(val binding: BasketItemLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): BasketRecyclerAdapter.ViewHolder {
val binding: BasketItemLayoutBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.basket_item_layout,
parent,
false
)
localStore = LocalStore(parent.context)
context = parent.context
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: BasketRecyclerAdapter.ViewHolder, position: Int) {
val currentItem = list[position]
holder.binding.apply {
title.text = currentItem.title
numberPicker.progress = currentItem.quantity
color.setCardBackgroundColor(android.graphics.Color.parseColor(currentItem.color))
colorName.text = currentItem.color_name
price.text =
doubleToStringNoDecimal((currentItem.price * currentItem.quantity).toDouble())
Glide.with(context)
.asBitmap()
.load(currentItem.url)
.into(productImage)
holder.binding.apply {
numberPicker.doOnProgressChanged { numberPicker, progress, formUser ->
listener.onPickerProgressChanged(
numberPicker,
progress,
formUser,
position
)
price.text =
doubleToStringNoDecimal((currentItem.price * progress).toDouble())
}
delete.setOnClickListener {
listener.delete(holder.adapterPosition)
removeItem(holder.adapterPosition, holder)
}
}
}
}
override fun getItemCount(): Int = list.size
fun doubleToStringNoDecimal(d: Double): String? {
val formatter: DecimalFormat = NumberFormat.getInstance(Locale.US) as DecimalFormat
formatter.applyPattern("#,###,###,###")
return formatter.format(d)
}
fun removeItem(position: Int, viewHolder: RecyclerView.ViewHolder) {
val removedItem = list[position]
val removedPosition = position
list.removeAt(position)
notifyItemRemoved(position)
val snackbar = Snackbar.make(
viewHolder.itemView,
"این کالا از سبد خرید حذف شد",
Snackbar.LENGTH_LONG
)
snackbar.setAction("بازگرداندن") {
list.add(removedPosition, removedItem)
notifyItemInserted(removedPosition)
listener.SnackActionClicked(position)
}
snackbar.setActionTextColor(Color.YELLOW);
snackbar.show();
}
interface ButtonListener {
fun onPickerProgressChanged(
numberPicker: NumberPicker,
progress: Int,
fromUser: Boolean,
position: Int
)
fun delete(position: Int)
fun SnackActionClicked(position: Int)
}
}
adapter =
BasketRecyclerAdapter(
items as ArrayList<Model.BasketItem>,
object : BasketRecyclerAdapter.ButtonListener {
override fun onPickerProgressChanged(
numberPicker: NumberPicker,
progress: Int,
fromUser: Boolean,
position: Int
) {
val currentitem = items[position]
viewModel.AddToCart(
localStore.getMobile().toString(),
currentitem.category,
currentitem.title,
currentitem.url,
currentitem.price,
progress,
currentitem.color,
currentitem.color_name,
currentitem.product_id
)
updateWorkerData(
currentitem.title,
currentitem.color,
Model.BasketItem::quantity,
progress
)
viewModel.CalculateTotalPrice(items)
.observe(viewLifecycleOwner) {
binding.price.text = doubleToStringNoDecimal(it)
}
}
override fun delete(position: Int) {
val currentitem = items[position]
viewModel.DeleteItemInCart(
localStore.getMobile().toString(),
currentitem.title,
currentitem.color_name
)
itemList.remove(
Model.BasketItem(
currentitem.id,
localStore.getMobile().toString(),
currentitem.category,
currentitem.title,
currentitem.url,
currentitem.price,
currentitem.quantity,
currentitem.color,
currentitem.color_name,
currentitem.product_id
)
)
if (items.size == 1)
if (isLastVisible()) {
basketpic.visibility = View.VISIBLE
emptyBasketText.visibility = View.VISIBLE
} else {
basketpic.visibility = View.GONE
emptyBasketText.visibility = View.GONE
}
viewModel.CalculateTotalPrice(items)
.observe(viewLifecycleOwner) {
binding.price.text = doubleToStringNoDecimal(it)
}
}
override fun SnackActionClicked(position: Int) {
val currentitem = items[position]
viewModel.AddToCart(
localStore.getMobile().toString(),
currentitem.category,
currentitem.title,
currentitem.url,
currentitem.price,
currentitem.quantity,
currentitem.color,
currentitem.color_name,
currentitem.product_id
)
binding.apply {
basketpic.visibility = View.GONE
emptyBasketText.visibility = View.GONE
}
viewModel.CalculateTotalPrice(items)
.observe(viewLifecycleOwner) {
binding.price.text = doubleToStringNoDecimal(it)
}
}
})
any help will be appreciated
UPDATE
you can see that first one is white and the second is brown but when i delete and undodelete the brown then white and reopen the fragment their position changes-> see second picture
after delete and undo deleteitems(first brown then white) and reopening the fragment
The Problem was that when i Clicked on Delete button the corresponding row in the Mysql was deleted and when i Clicked on Undo button the items that i deleted them
Were added to the table with new id and beacause of this the position of item changed after the fragment reopened
so i set a Snackbar.Callback to snackbar and i said that when it closed on its own then you can delete the item from database(mysql)
this is the code:
val snackbar = Snackbar.make(
viewHolder.itemView,
"این کالا از سبد خرید حذف شد",
Snackbar.LENGTH_LONG
)
snackbar.setAction("بازگرداندن") {
list.add(removedPosition, removedItem)
listener.SnackActionClicked(position)
notifyItemInserted(removedPosition)
}
snackbar.setActionTextColor(Color.YELLOW);
snackbar.addCallback(object : Snackbar.Callback() {
override fun onDismissed(snackbar: Snackbar?, event: Int) {
super.onDismissed(snackbar, event)
if (event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT) {
// Snackbar closed on its own
// Delete item from mysql database
}
}
override fun onShown(snackbar: Snackbar?) {
super.onShown(snackbar)
}
});
snackbar.show();

problem populate spinner android kotlin with room database

I want to populate spinner with room database in kotlin but items in spinner are different. the number of items are true. exactly I want to make ArrayList of title in Category Class and show them in spinner in a fragment
#Entity(tableName = "cat_table")
class Category(val title: String,
val fulType: Int,
val SaleP: Long,
val BuyP: Long,
var created: Date = Date()
) {
#PrimaryKey(autoGenerate = true)
var id: Int = 0
}`
by this an want to show all title in ArrayList and set to spinner
private fun initData() {
val packageTypesAdapter = context?.let {ArrayAdapter<Any>(it,android.R.layout.simple_spinner_item)}
catViewModel!!.allCats.observe(viewLifecycleOwner, Observer { packageTypes ->
packageTypes?.forEach {packageTypesAdapter!!.add(it)}
})
spinner2.adapter = packageTypesAdapter
spinner2.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(adapterView: AdapterView<*>, view: View, i: Int, l: Long) {
Toast.makeText(context, "$packageTypesAdapter", Toast.LENGTH_SHORT).show()
//if (i == 0) {
// loadAllTodos()
//} else {
// val string: String = parent.getItemAtPosition(position).toString()
// loadFilteredTodos(string)
//}
}
override fun onNothingSelected(adapterView: AdapterView<*>) {
}
}
}`
if use this query
#get:Query("SELECT * FROM cat_table WHERE title ")
val allCatTitle: LiveData<List<Category>>
spinner nothing to show and below query are like this picture
#get:Query("SELECT * FROM cat_table ORDER BY created DESC")
val allCatByDate: LiveData<List<Category>>
Please check the photo
In case someone else bumps into the same issue. Here's how I did it a while ago (Kotlin).
Dao
#Query("SELECT deviceName FROM device_table WHERE deviceType = :deviceType")
fun getDevice(deviceType: String): LiveData<List<String>>
Repository
fun getDevice(deviceType: String): LiveData<List<String>> {
return deviceDao.getDevice(deviceType)
}
ViewModel
fun getDevice(deviceType: String): LiveData<List<String>> {
return repository.getDevice(deviceType)
}
And finally in my fragment:
private fun initSpinnerData() {
val spinner3 = view?.findViewById<Spinner>(R.id.spinnerRecord_devices)
if (spinner3 != null) {
val allDevices = context?.let {
ArrayAdapter<Any>(it, R.layout.spinner_text)
}
mDevicesViewModel.getDevice("Appliance")
.observe(viewLifecycleOwner, { devices ->
devices?.forEach {
allDevices?.add(it)
}
})
spinner3.adapter = allDevices
spinner3.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
Toast.makeText(requireContext(), "$allDevices", Toast.LENGTH_LONG).show()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
}
}

KOTLIN: Basic Async / Coroutines

I am doing a school project.
I have a list with Doses, so I need to fetch data en set text one by one.
Right now I'm getting:
kotlin.UninitializedPropertyAccessException: lateinit property medicine has not been initialized.
So I need to wait till the first item is fetched and set before continuing to next item.
can you help me?
class ClientDoseListAdapter(private val doses: List<Dose>) : RecyclerView.Adapter<ClientDoseListAdapter.ViewHolder>() {
private lateinit var medicine : Medicine
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.client_dose_listitem, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = doses[position]
runBlocking {
displayMedicine(item.medicine)
}
holder.med_name.text = medicine.name
holder.dose_amount.text = item.amount.toString()
}
private suspend fun displayMedicine(id: Int) {
fetchMedicine(id)
}
override fun getItemCount(): Int = doses.size
inner class ViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView),
LayoutContainer
private fun fetchMedicine(id: Int) {
service.getMedicine(id, "Bearer ${ClienOverzichtFragment.auth}")
.enqueue(object : Callback<List<Medicine>> {
override fun onResponse(call: Call<List<Medicine>>, response: Response<List<Medicine>>) {
if (response.code() == 200) {
val temp = response.body()!!
medicine = temp[0]
Log.v("SHIT", medicine.name)
} else {
Log.v("SHIT", response.code().toString())
//TODO
}
}
override fun onFailure(call: Call<List<Medicine>>, t: Throwable) {
Log.v("SHIT", "FAILED : "+t.message)
}
})
}
}
Move your service call out of the Recycler (best into a ViewModel, but can call from Activity or using any other pattern - the main thing, shouldn't be part of the Recycler) and pass the data, when it's received, into the Recycler.
Your ClientDoseListAdapter to accept medicine:
class ClientDoseListAdapter(private val doses: List<Dose>, private val medicine: Medicine)
In your activity, initiate and a call for medicine and observe it - when the data arrives, pass it to the adapter. Assuming you use a view model your code in Activity would look something like this:
viewModel.getMedicine().observe(
this,
Observer<Medicine> { medicine ->
//assuming doses come from somewhere else
adapter = ClientDoseListAdapter(doses, medicine, this)
clientDoseRecyclerView.adapter = adapter
}
)

Button Checklist all on Recyclerview does not work

I have recyclerview with checkbox and I want to checklist all the data using button. I have trying this tutorial, but when i click the button, the log is call the isSelectedAll function but can't make the checkbox checked. what wrong with my code?
this is my adapter code
var isSelectedAll = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListApproveDeatilViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.activity_list_approve_row, parent, false)
return ListApproveDeatilViewHolder(itemView)
}
private lateinit var mSelectedItemsIds: SparseBooleanArray
fun selectAll() {
Log.e("onClickSelectAll", "yes")
isSelectedAll = true
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: ListApproveDeatilViewHolder, position: Int) {
val approve = dataSet!![position]
holder.soal.text = approve.title
holder.kategori.text = approve.kategori
if (!isSelectedAll){
holder.checkBox.setChecked(false)
} else {
holder.checkBox.setChecked(true)
}
}
and this is my activity code
override fun onCreate(savedInstanceState: Bundle?) {
private var adapter: ListApproveDetailAdapter? = null
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list_approve)
ButterKnife.bind(this)
getData()
// this is my button onclick code
select.setOnClickListener(){
if (select.getText().toString().equals("Select all")){
Toast.makeText(this, "" + select.getText().toString(), Toast.LENGTH_SHORT).show()
adapter?.selectAll()
select.setText("Deselect all")
} else {
Toast.makeText(this, "" + select.getText().toString(), Toast.LENGTH_SHORT).show()
select.setText("Select all")
}
}
}
//this is for get my data for the recyclerview
fun getData() {
val created_by = intent.getStringExtra(ID_SA)
val tgl_supervisi = intent.getStringExtra(TGL_SURVEY)
val no_dlr = intent.getStringExtra(NO_DLR)
API.getListApproveDetail(created_by, tgl_supervisi, no_dlr).enqueue(object : Callback<ArrayList<ListApprove>> {
override fun onResponse(call: Call<ArrayList<ListApprove>>, response: Response<ArrayList<ListApprove>>) {
if (response.code() == 200) {
tempDatas = response.body()
Log.i("Data Index History", "" + tempDatas)
recyclerviewApprove?.setHasFixedSize(true)
recyclerviewApprove?.layoutManager = LinearLayoutManager(this#ListApproveActivity)
recyclerviewApprove?.adapter = ListApproveDetailAdapter(tempDatas)
adapter?.notifyDataSetChanged()
} else {
Toast.makeText(this#ListApproveActivity, "Error", Toast.LENGTH_LONG).show()
}
swipeRefreshLayout.isRefreshing = false
}
override fun onFailure(call: Call<ArrayList<ListApprove>>, t: Throwable) {
Toast.makeText(this#ListApproveActivity, "Error", Toast.LENGTH_SHORT).show()
swipeRefreshLayout.isRefreshing = false
}
})
}
thankyou for any help :)
I am posting the answer with implementation of demo project. I haven't modified your code but as per your requirement i have done this.
MainActivity class:
class MainActivity : AppCompatActivity() {
var selectAll: Boolean = false;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) as RecyclerView
val btnSelectAll = findViewById<Button>(R.id.btnSelectAll) as Button
//adding a layoutmanager
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayout.VERTICAL, false)
//crating an arraylist to store users using the data class user
val users = ArrayList<User>()
//adding some dummy data to the list
users.add(User("Piyush", "Ranchi"))
users.add(User("Mehul", "Chennai"))
users.add(User("Karan", "TamilNadu"))
users.add(User("Bela", "Kolkata"))
//creating our adapter
val adapter = CustomAdapter(users, selectAll)
//now adding the adapter to recyclerview
recyclerView.adapter = adapter
btnSelectAll.setOnClickListener {
if (!selectAll) {
selectAll = true
} else {
selectAll = false
}
adapter?.selectAllCheckBoxes(selectAll)
}
}
}
User class:
data class User(val name: String, val address: String)
Adapter class:
class CustomAdapter(val userList: ArrayList<User>, val selectAll: Boolean) :
RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
var selectAllA = selectAll;
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.list_layout, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: CustomAdapter.ViewHolder, position: Int) {
holder.textViewName.text = userList[position].name;
if (!selectAllA){
holder.checkBox.setChecked(false)
} else {
holder.checkBox.setChecked(true)
}
}
//this method is giving the size of the list
override fun getItemCount(): Int {
return userList.size
}
//the class is hodling the list view
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textViewName = itemView.findViewById(R.id.textViewUsername) as TextView
val checkBox = itemView.findViewById(R.id.checkbox) as CheckBox
}
fun selectAllCheckBoxes(selectAll: Boolean) {
selectAllA = selectAll
notifyDataSetChanged()
}
}
As i already mentioned in comments you are using two different adapter instance .
Now i see you have declared adapter globally .
Just modify your code as follows and make sure response.body() have data int it :
if (response.code() == 200) {
tempDatas = response.body()
Log.i("Data Index History", "" + tempDatas)
recyclerviewApprove?.setHasFixedSize(true)
recyclerviewApprove?.layoutManager = LinearLayoutManager(this#ListApproveActivity)
adapter = ListApproveDetailAdapter(tempDatas)
recyclerviewApprove?.adapter=adapter
} else {
Toast.makeText(this#ListApproveActivity, "Error", Toast.LENGTH_LONG).show()
}
Add one variable in model class.
like var isSelect : Boolean
In your selectAll() method update adpter list and notify adapter.
Edit:
in the adapter class.
if (approve.isSelect){
holder.checkBox.setChecked(true)
} else {
holder.checkBox.setChecked(false)
}
Hope this may help you.
OR
If you are using AndroidX then use should use one recyclerview features.
androidx.recyclerview.selection
A RecyclerView addon library providing support for item selection. The
library provides support for both touch and mouse driven selection.
Developers retain control over the visual representation, and the
policies controlling selection behavior (like which items are eligible
for selection, and how many items can be selected.)
Reference from here

RealmBaseAdapter with section headers

I'm trying to use the realm data store for Android and i'm trying to build an application that shows the user a list of options in a ListView, kind of like the users contact list. The first letter for each section (such as A, B, C, etc.) should be a header. Is there a way to achieve this with RealmBaseAdapter?
Currently I have it working with ArrayAdapter and I simply have an array with values populated but would like to pull data from Realm using its adapter if possible. I know in iOS this is fairly straight forward using the NSFetchedResultsController. How do we break up the RealmResults into sections?
RealmBaseAdapter doesn't work with ExpandableListAdapter (which I am assuming you are using for sections?), so right now your only choice is creating your own implementation. But a RealmResults is also a List so it should work seamlessly with an ArrayAdapter.
You can also see more details here: https://github.com/realm/realm-java/issues/978
This is what I use:
package com.poterion.android.library.adapters
import android.widget.BaseExpandableListAdapter
import io.realm.*
/**
* #author Jan Kubovy <jan#kubovy.eu>
*/
abstract class RealmExpandableListAdapter<out Group : Any, Item : RealmModel>(
private val itemGroupsProvider: (Item) -> Collection<Group?>,
private val groupsProvider: (Collection<Item>) -> List<Group?>,
private var adapterData: OrderedRealmCollection<Item>?) : BaseExpandableListAdapter() {
private val listener: RealmChangeListener<OrderedRealmCollection<Item>>?
protected val groups: List<Group?>
get() {
return adapterData?.takeIf { isDataValid }?.let(groupsProvider) ?: emptyList()
}
private val isDataValid: Boolean
get() = adapterData?.isValid == true
init {
if (adapterData?.isManaged == false)
throw IllegalStateException("Only use this adapter with managed list, for un-managed lists you can just use the BaseAdapter")
this.listener = RealmChangeListener { notifyDataSetChanged() }
adapterData?.takeIf { isDataValid }?.also { addListener(it) }
}
private fun addListener(data: OrderedRealmCollection<Item>) {
when (data) {
is RealmResults<Item> -> data.addChangeListener((listener as RealmChangeListener<RealmResults<Item>>))
is RealmList<Item> -> data.addChangeListener((listener as RealmChangeListener<RealmList<Item>>))
else -> throw IllegalArgumentException("RealmCollection not supported: " + data.javaClass)
}
}
private fun removeListener(data: OrderedRealmCollection<Item>) {
when (data) {
is RealmResults<Item> -> data.removeChangeListener((listener as RealmChangeListener<RealmResults<Item>>))
is RealmList<Item> -> data.removeChangeListener((listener as RealmChangeListener<RealmList<Item>>))
else -> throw IllegalArgumentException("RealmCollection not supported: " + data.javaClass)
}
}
override fun getGroupCount(): Int = groups.size
override fun getChildrenCount(groupPosition: Int): Int = adapterData?.takeIf { isDataValid }?.let { data ->
val g = groups[groupPosition]
data.filter { g == null || groups(it).contains(g) }.size
} ?: 0
override fun getGroup(groupPosition: Int): Group? = if (groups.size > groupPosition) groups[groupPosition] else null
override fun getChild(groupPosition: Int, childPosition: Int): Item? = children(groupPosition)
.takeIf { it.size > childPosition }?.get(childPosition)
override fun notifyDataSetChanged() {
super.notifyDataSetChanged()
}
private fun children(groupPosition: Int): List<Item> {
return getGroup(groupPosition)
?.let { g -> adapterData?.takeIf { isDataValid }?.filter { groups(it).contains(g) } } ?: emptyList()
}
}
And usage:
class PersonListAdapter(realm: Realm) :
RealmExpandableListAdapter<String, Person>(
itemGroupsProvider = { person -> arrayOf(person.group, null) },
groupsProvider = { people -> people.map { it.group } },
adapterData = realm.where(Person::class.java)
.findAllSortedAsync("lastName", Sort.ASCENDING, "firstName", Sort.ASCENDING)) {
override fun getGroupId(groupPosition: Int) = getGroup(groupPosition).id
override fun getChildId(groupPosition: Int, childPosition: Int) = getChild(groupPosition, childPosition).id
override fun hasStableIds() = true
override fun getGroupView(groupPosition: Int, isExpanded: Boolean, convertView: View?, parent: ViewGroup?): View {
// ... Item View here ...
}
override fun getChildView(groupPosition: Int, childPosition: Int, isLastChild: Boolean,
convertView: View?, parent: ViewGroup?): View {
// ... Group View here ...
}
override fun isChildSelectable(groupPosition: Int, childPosition: Int) = true
}

Categories

Resources