How to implement undo swipe and reinsert recently deleted item KOTLIN - kotlin

I have a to do list app and I have created a snackbar that shows up when a user swipes to delete an item. What I want to do is have the undo snackbar reinsert the item that was just deleted.
Here's my code that handles the swipe to delete and show snackbar
override fun onViewSwiped(position: Int) {
deleteTask(list[position].ID)
list.removeAt(position)
notifyItemRemoved(position)
updateNotesPositionInDb()
val snackbar = Snackbar.make((context as Activity).findViewById(R.id.mainLayout), "task deleted", Snackbar.LENGTH_SHORT)
snackbar.setAction("Undo") {
}
snackbar.show()
Keep in mind that this code is used in an Adapter class.

You can save the deleted item and add it back to the list with undo:
The problem is that you already deleted the task in your database, so I think that you should give that deleteTask some delay and only delete the task if the undo is not clicked during that delay.
override fun onViewSwiped(position: Int) {
val list = mutableListOf<String>()
deleteTask(list[position].ID)
val removedItem = list.removeAt(position)
notifyItemRemoved(position)
updateNotesPositionInDb()
val snackbar = Snackbar.make(
(context as Activity).findViewById(R.id.mainLayout),
"task deleted",
Snackbar.LENGTH_SHORT
)
snackbar.setAction("Undo") {
undoDeleteTask(removedItem.ID)
list.add(position, removedItem)
notifyItemInserted(position)
}
snackbar.show()
}

Related

Can you change the color of a textview in a recyclerview adapter after a certain condition is met in Main Activity?

I have a basic function that displays the elapsed time every time the button is pressed. I cannot get the logic in MainActivity to transfer to the recyclerview adapter. I simply want the text output color to change to red after the time passes 5 seconds. I have tried to research how to do this for the past week and I cannot find the exact answer. I'm hoping someone can help.
I have tried it with and without the boolean in the data class. I wasn't sure if that was required.
Here is my code:
Main Activity:`
class MainActivity : AppCompatActivity() {
var startTime = SystemClock.elapsedRealtime()
var displaySeconds = 0
private lateinit var binding: ActivityMainBinding
private val secondsList = generateSecondsList()
private val secondsAdapter = Adapter(secondsList)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
recyclerView.adapter = secondsAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(false)
binding.button.setOnClickListener {
getDuration()
addSecondsToRecyclerView()
}
}
fun getDuration(): Int {
val endTime = SystemClock.elapsedRealtime()
val elapsedMilliSeconds: Long = endTime - startTime
val elapsedSeconds = elapsedMilliSeconds / 1000.0
displaySeconds = elapsedSeconds.toInt()
return displaySeconds
}
private fun generateSecondsList(): ArrayList<Seconds> {
return ArrayList()
}
fun addSecondsToRecyclerView() {
val addSeconds =
Seconds(getDuration(), true)
secondsList.add(addSeconds)
secondsAdapter.notifyItemInserted(secondsList.size - 1)
}
}
Adapter:
var adapterSeconds = MainActivity().getDuration()
class Adapter(
private val rvDisplay: MutableList<Seconds>
) : RecyclerView.Adapter<Adapter.AdapterViewHolder>() {
class AdapterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView1: TextView = itemView.tv_seconds
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AdapterViewHolder {
val myItemView = LayoutInflater.from(parent.context).inflate(
R.layout.rv_item,
parent, false
)
return AdapterViewHolder(myItemView)
}
override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
val currentDisplay = rvDisplay[position]
currentDisplay.isRed = adapterSeconds > 5
holder.itemView.apply {
val redColor = ContextCompat.getColor(context, R.color.red).toString()
val blackColor = ContextCompat.getColor(context, R.color.black).toString()
if (currentDisplay.isRed) {
holder.textView1.setTextColor(redColor.toInt())
holder.textView1.text = currentDisplay.rvSeconds.toString()
} else {
holder.textView1.setTextColor(blackColor.toInt())
holder.textView1.text = currentDisplay.rvSeconds.toString()
}
}
}
override fun getItemCount() = rvDisplay.size
}
Data Class:
data class Seconds(
var rvSeconds: Int,
var isRed: Boolean
)
when you call secondsList.add(addSeconds) then the data that is already inside secondsList should be updated too.
you could do something like
private var secondsList = generateSecondsList() // make this var
fun addSecondsToRecyclerView() {
val addSeconds =
Seconds(getDuration(), true)
secondsList.add(addSeconds)
if ( /* TODO check if time has passed */) {
secondsList = secondsList.map { it.isRed = true }
secondsAdapter.rvDisplay = secondsList // TODO also make rvDisplay a var
secondsAdapter.notifyDatasetChanged() // also need to tell rv to redraw the all views
} else {
secondsAdapter.notifyItemInserted(secondsList.size - 1)
}
}
that might work, but to be honest it looks bad... There is already a lot of logic inside Activity. Read about MVVM architecture and LiveData, there should be another class called ViewModel that would keep track of time and the data. Activity should be as simple as possible, because it has lifecycle, so if you rotate the screen, all your state will be lost.
Your code isn't really working because of this:
var adapterSeconds = MainActivity().getDuration()
override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
...
currentDisplay.isRed = adapterSeconds > 5
...
}
You're only setting adapterSeconds right there, so it never updates as time passes. I assume you want to know the moment 5 seconds has elapsed, and then update the RecyclerView at that moment - in that case you'll need some kind of timer task that will fire after 5 seconds, and can tell the adapter to display things as red. Let's deal with that first:
class Adapter( private val rvDisplay: MutableList ) : RecyclerView.Adapter<Adapter.AdapterViewHolder>() {
private var displayRed = false
set(value) {
field = value
// Refresh the display - the ItemChanged methods mean something about the items
// has changed, rather than a structural change in the list
// But you can use notifyDataSetChanged if you want (better to be specific though)
notifyItemRangeChanged(0, itemCount)
}
override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
if (displayRed) {
// show things as red - you shouldn't need to store that state in the items
// themselves, it's not about them - it's an overall display state, right?
} else {
// display as not red
}
}
So with that setter function, every time you update displayRed it'll refresh the display, which calls onBindViewHolder, which checks displayRed to see how to style things. It's better to put all this internal refreshing stuff inside the adapter - just pass it data and events, let it worry about what needs to happen internally and to the RecyclerView it's managing, y'know?
Now we have a thing we can set to control how the list looks, you just need a timer to change it. Lots of ways to do this - a CountdownTimer, a coroutine, but let's keep things simple for this example and just post a task to the thread's Looper. We can do that through any View instead of creating a Handler:
// in MainActivity
recyclerView.postDelayed({ secondsAdapter.displayRed = true }, 5000)
That's it! Using any view, post a delayed function that tells the adapter to display as red.
It might be more helpful to store that runnable as an object:
private val showRedTask = Runnable { secondsAdapter.displayRed = true }
...
recyclerView.postDelayed(showRedTask, 5000)
because then you can easily cancel it
recyclerView.removeCallbacks(showRedTask)
Hopefully that's enough for you to put some logic together to get what you want. Set displayRed = false to reset the styling, use removeCallbacks to cancel any running task, and postDelayed to start a new countdown. Not the only way to do it, but it's pretty neat!
I finally figured it out using a companion object in Main Activity with a boolean set to false. If the time exceeded 5 seconds, then it set to true.
The adapter was able to recognize the companion object and change the color of seconds to red if they exceeded 5.

RecyclerView and notifyDataSetChanged LongClick mismatch

I'm having a weird problem with notifyDataSetChanged() in my Recycler Adapter. If I keep 5 items in an array the code works fine and I can check the checkbox at the item I LongClick, but when I add 5 items or more to the array other checkboxes get checked in my list.
I am using a boolean to toggle between VISIBLE and GONE on the checkboxes when the user LongClicks as well.
Here is my code:
class RecyclerAdapter(private val listActivity: ListActivity) : RecyclerView.Adapter<RecyclerAdapter.Holder>() {
lateinit var binding: ActivityListItemRowBinding
var checkboxesVisibility = false
val dummyArrayWorks = arrayOf("000", "111", "222", "333", "444")
val dummyArrayFails = arrayOf("000", "111", "222", "333", "444", "555")
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
binding = ActivityListItemRowBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return Holder(binding)
}
override fun getItemCount(): Int = dummyArrayFails.size
#SuppressLint("NotifyDataSetChanged")
override fun onBindViewHolder(holder: Holder, position: Int) {
val item = dummyArrayFails[position]
holder.binding.checkbox.visibility = if (checkboxesVisibility) VISIBLE else GONE
holder.bindItem(item)
holder.itemView.setOnLongClickListener {
if (!checkboxesVisibility) {
checkboxesVisibility = true
holder.binding.checkbox.isChecked = true
notifyDataSetChanged()
true
} else {
false
}
}
holder.itemView.setOnClickListener {
if (!checkboxesVisibility) {
//Some other unrelated code
} else {
holder.binding.checkbox.isChecked = !holder.binding.checkbox.isChecked
notifyDataSetChanged()
}
}
}
class Holder(internal val binding: ActivityListItemRowBinding) : RecyclerView.ViewHolder(binding.root) {
var item = String()
fun bindItem(item: String) {
this.item = item
binding.itemPlaceHolder.text = item
}
}
}
I should add that when I remove the toggle for the checkboxes, and just show the checkboxes on first load, the clicks match the checkmarks without a problem.
Does anybody have any idea of what is going on? All help will be much appreciated!
The problem is you're holding your checked state in the ViewHolder itself - you're toggling its checkbox on and off depending on clicks, right?
The way a RecyclerView works is that instead of having a ViewHolder for every single item (like a ListView does), it only creates a handful of them - enough for what's on screen and a few more for scrolling - and recycles those, using them to display different items.
That's what onBindViewHolder is about - when it needs to display the item at position, it hands you a ViewHolder from its pool and says here you go, use that to display this item's details. This is where you do things like setting text, changing images, and setting things like checkbox state to reflect that particular item.
What you're doing is you're not storing the item's state anywhere, you're just setting the checkbox on the view holder. So if you check it, every item that happens to be displayed in that reusable holder object will have its box ticked. That's why you're seeing it pop up on other items - that checked state has nothing to do with the items themselves, just which view holder they all happen to use because of their position in the list.
So instead, you need to keep their checked state somewhere - it could be as simple as a boolean array that matches the length of your item list. Then you just set and get from that when binding your data (displaying it). Working with what you've got:
// all default to false
val itemChecked = BooleanArray(items.size)
override fun onBindViewHolder(holder: Holder, position: Int) {
...
// when displaying the data, refer to the checked state we're holding
holder.binding.checkbox.checked = itemChecked[position]
...
holder.itemView.setOnLongClickListener {
...
// when checking the box, update our checked state
// since we're calling notifyDataSetChanged, the item will be redisplayed
// and onBindViewHolder will be called again (which sets the checkbox)
itemChecked[position] = true
// notifyItemChanged(position) is better here btw, just refreshes this one
notifyDataSetChanged()
...
}
}

Can't add a suspended function to a setOnClickListener in my AlertDialog

The add_button in the songToAddDialog() method won't accept my suspended method positiveButtonClick() into its setOnClickListener. I have been looking to this for hours and I do not know what to do.
// Check the GenreKey of the current playlist to use later to create the Song
suspend fun playlistGenreCheck(id: Long): Long {
val playlist = dataSource.getPlaylist(id)
val playlistGenre = playlist.playlistGenre
return playlistGenre
}
// When you press the add button in the AlertDialog it will add the song to the database and closes the AlertDialog afterwards
suspend fun positiveButtonClick(builder: AlertDialog){
val song = Song(title = builder.dialogTitleEt.toString(), artist = builder.dialogArtistEt.toString(), playlist = arguments.playlistKey, key = builder.dialogKeyEt.toString(), genre = playlistGenreCheck(arguments.playlistKey))
songsModel.addSong(song)
builder.dismiss()
}
// When you press the cancel button the AlertDialog will bet dismissed
fun negativeButtonClick(builder: AlertDialog){
builder.dismiss()
}
// This creates the AlertDialog and adds the two functions above to the buttons
fun songToAddDialog(){
val mDialogView = LayoutInflater.from(requireContext()).inflate(R.layout.add_song_dialog, null)
val mBuilder = AlertDialog.Builder(requireContext()).setView(mDialogView).setTitle("Add a Song")
val mAlertDialog = mBuilder.show()
mDialogView.add_button.setOnClickListener{positiveButtonClick(mAlertDialog)}
mDialogView.cancel_button.setOnClickListener{negativeButtonClick(mAlertDialog)}
}
// Makes the add-button inside the songslistview observable
songsModel.addButton.observe(viewLifecycleOwner, androidx.lifecycle.Observer{
if (it) {
songToAddDialog()
}
})
The Suspend function can only be called from a CoroutineScope. If you have the lifecycle dependency then use:
mDialogView.add_button.setOnClickListener{
lifecyclescope.launch{
positiveButtonClick(mAlertDialog)
}
}
If you don't have lifecycle dependencies then calling CoroutineScope like this should also work:
mDialogView.add_button.setOnClickListener{
CoroutineScope(Dispatchers.IO).launch{
positiveButtonClick(mAlertDialog)
}
}
Do tell if you still have some problem regarding this :)

function call in onActivityCreated() triggers Observer every time the app restarts

in my fragment I am observing a live data in a function and in that observer, some sharedPreferences are changed. The function is then called inside onActivityCreated() .The problem is whenever I restart my app the onActivityCreated() gets called which in turn calls that function which in turn observes the live data and thus changes the value of sharedPreference which I don't want.
code to my fragment is attached.
package com.example.expensemanager.ui
import android.app.AlertDialog
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.expensemanager.R
import com.github.mikephil.charting.data.PieData
import com.github.mikephil.charting.data.PieDataSet
import com.github.mikephil.charting.data.PieEntry
import kotlinx.android.synthetic.main.fragment_transaction_list.*
import kotlinx.android.synthetic.main.set_balance_details.view.*
import org.eazegraph.lib.models.PieModel
class TransactionListFragment : Fragment() {
//declaring the view model
private lateinit var viewModel: TransactionListViewModel
var cashAmount:Float = 0F
var bankAmount:Float = 0F
override fun onCreate(savedInstanceState: Bundle?){
super.onCreate(savedInstanceState)
//setHasOptionsMenu(true)
//(activity as AppCompatActivity?)!!.setSupportActionBar(addAppBar)
viewModel = ViewModelProvider(this)
.get(TransactionListViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_transaction_list, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
//recycler view for showing all the transactions
with(transaction_list){
layoutManager = LinearLayoutManager(activity)
adapter = TransactionAdapter {
findNavController().navigate(
TransactionListFragmentDirections.actionTransactionListFragmentToTransactionDetailFragment(
it
)
)
}
}
//code for the floating action button in the main screen
add_transaction.setOnClickListener{
findNavController().navigate(
//here id is passed 0 because the transaction is being added the first time
TransactionListFragmentDirections.actionTransactionListFragmentToTransactionDetailFragment(
0
)
)
}
addAppBar.setOnMenuItemClickListener { menuItem ->
when(menuItem.itemId){
R.id.calendar_button -> {
findNavController().navigate(TransactionListFragmentDirections.actionTransactionListFragmentToCalanderViewFragment())
true
}
R.id.monthly_cards -> {
findNavController().navigate(TransactionListFragmentDirections.actionTransactionListFragmentToMonthlyCardsFragment())
true
}
else -> false
}
}
see_all_transactions.setOnClickListener {
findNavController().navigate(TransactionListFragmentDirections.actionTransactionListFragmentToAllTransactionsFragment())
}
//submitting the new list of upcoming Transactions after getting it from the db
viewModel.upcomingTransactions.observe(viewLifecycleOwner, Observer {
(transaction_list.adapter as TransactionAdapter).submitList(it)
})
val sharedPreferences: SharedPreferences = this.requireActivity().getSharedPreferences("OnboardingDetails", Context.MODE_PRIVATE)
val monthlyBudget = sharedPreferences.getFloat("monthlyBudget",0F)
var totalBalance = monthlyBudget*12
net_balance.text = totalBalance.toString()
Log.d("netbalance",totalBalance.toString())
//the net balance (yearly) is calculated wrt the transactions already done
viewModel.sumOfTransactions.observe(viewLifecycleOwner, Observer {
if (it != null) {
totalBalance += it
net_balance.text = totalBalance.toString()
}
})
val budgetPreferences: SharedPreferences =
this.requireActivity().getSharedPreferences("Balance_details", Context.MODE_PRIVATE)
val editor: SharedPreferences.Editor = budgetPreferences.edit()
//setting pie chart initially to 0
setPieChart(budgetPreferences,editor)
//observing the cash details and the bank details to update the text view and the pie chart
observeBalance(budgetPreferences,editor)
//GraphCardView code
//button for setting the balance details
set_balance_details.setOnClickListener {
setBalanceDetails(budgetPreferences,editor)
}
}
//dialog box for setting the balance details
private fun setBalanceDetails(budgetPreferences: SharedPreferences,editor: SharedPreferences.Editor) {
val dialog = LayoutInflater.from(requireContext()).inflate(
R.layout.set_balance_details,
null
)
//AlertDialogBuilder
val mBuilder = AlertDialog.Builder(requireContext())
.setView(dialog)
//show dialog
val mAlertDialog = mBuilder.show()
dialog.save_details.setOnClickListener {
cashAmount = dialog.cash_amount.editText?.text.toString().toFloat()
bankAmount = dialog.bank_amount.editText?.text.toString().toFloat()
//saving the cashAmount and bankAmount to shared preferences for future use
editor.putFloat("cashAmount", cashAmount).apply()
editor.putFloat("bankAmount", bankAmount).apply()
//setting the pie chart with new values
setPieChart(budgetPreferences, editor)
mAlertDialog.dismiss()
}
dialog.cancel_details.setOnClickListener { mAlertDialog.dismiss() }
mAlertDialog.show()
}
private fun observeBalance(budgetPreferences: SharedPreferences,editor: SharedPreferences.Editor) {
//getting the cashAmount and bankAmount and updating the views with live data
var cashAmount = budgetPreferences.getFloat("cashAmount", 0F)
var bankAmount = budgetPreferences.getFloat("bankAmount", 0F)
viewModel.cashAmount.observe(viewLifecycleOwner, Observer {
if (it != null) {
cashAmount += it
cash.text = "CASH : ${cashAmount}"
Log.d("observeCash",cashAmount.toString())
editor.putFloat("cashAmount",cashAmount).apply()//find solution to this
setPieChart(budgetPreferences,editor)
}
})
viewModel.bankAmount.observe(viewLifecycleOwner, Observer {
if (it != null) {
bankAmount+=it
bank.text = "BANK : ${bankAmount}"
Log.d("observeBank",bankAmount.toString())
editor.putFloat("cashAmount",cashAmount).apply()
setPieChart(budgetPreferences,editor)
}
})
setPieChart(budgetPreferences,editor)
}
//https://www.geeksforgeeks.org/how-to-add-a-pie-chart-into-an-android-application/ use this for reference
private fun setPieChart(budgetPreferences: SharedPreferences,editor: SharedPreferences.Editor) {
val cashAmount = budgetPreferences.getFloat("cashAmount", 0f)
val bankAmount = budgetPreferences.getFloat("bankAmount", 0f)
Log.d("pieCank",cashAmount.toString())
Log.d("pieBank",bankAmount.toString())
cash.text = "CASH : ${cashAmount}"
bank.text = "BANK : ${bankAmount}"
val pieEntries = arrayListOf<PieEntry>()
pieEntries.add(PieEntry(cashAmount))
pieEntries.add(PieEntry(bankAmount))
pieChart.animateXY(1000, 1000)
// setup PieChart Entries Colors
val pieDataSet = PieDataSet(pieEntries, "This is Pie Chart Label")
pieDataSet.setColors(
ContextCompat.getColor(requireActivity(), R.color.blue1),
ContextCompat.getColor(requireActivity(), R.color.blue2)
)
val pieData = PieData(pieDataSet)
// setip text in pieChart centre
//piechart.setHoleColor(R.color.teal_700)
pieChart.setHoleColor(getColorWithAlpha(Color.BLACK, 0.0f))
// hide the piechart entries tags
pieChart.legend.isEnabled = false
// now hide the description of piechart
pieChart.description.isEnabled = false
pieChart.description.text = "Expanses"
pieChart.holeRadius = 40f
// this enabled the values on each pieEntry
pieData.setDrawValues(true)
pieChart.data = pieData
}
fun getColorWithAlpha(color: Int, ratio: Float): Int {
var newColor = 0
val alpha = Math.round(Color.alpha(color) * ratio)
val r = Color.red(color)
val g = Color.green(color)
val b = Color.blue(color)
newColor = Color.argb(alpha, r, g, b)
return newColor
}
}
As seen when app restarts , viewModel.cashAmount gets triggered giving undesired outputs.
What can i do to avoid this .
Activities can get recreated a lot, like when you rotate the screen, or if it's in the background and the system destroys it to free up some memory. Right now, every time that happens your code doesn't know whether it's getting the current value, or a brand new one, but one of those should perform a calculation, and the other should just update the display.
The problem is your calculation logic is tied in with the UI state - it's being told what to display, and also deciding if that counts as a new user action or not. And it has no way of knowing that. Your logic needs to go something like
things observe LiveData values -> update to display new values when they come in
user clicks a button -> do calculation with the value they've entered
calculation result returns -> LiveData gets updated with new value
LiveData value changes -> things update to show the new value
that way a calculation happens specifically in response to a user action, like through a button click. LiveData observers only reflect the current state, so it doesn't matter if they see the same value lots of times, they're just redrawing a pie chart or whatever.
You can use a LiveData to watch for a stream of values, but the thing about UI components is that sometimes they're there to see them, and sometimes they're not. And LiveData is specifically made to push updates to active observers, but not inactive ones - and always provide the most recent value to a new observer, or one that becomes active.
So in that case it works more like "here's the current situation", and that fits better with displaying things, where it doesn't matter if you repeat yourself. That's why you can't do this kind of "handle everything exactly one time" thing in your UI - unless you're literally responding to a UI event like a button click

How to update data in nested child RecyclerView without losing animations/initialising a new adapter?

I have a ParentData class structured as follows:
class ParentData(
//parent data fields
var childList: List<ChildData>
I am populating a parent RecylerView with a list of ParentData and then populating each nested child RecyclerView with the embedded List<ChildData> in onBindViewHolder of the parent RecylerView Adapter (ListAdapter) like so:
val mAdapter = ChildAdapter()
binding.childRecyclerView.apply {
layoutManager = LinearLayoutManager(this.context)
adapter = mAdapter
}
mAdapter.submitList(item.childList)
//item from getItem(position)
I observe a LiveData<List<ParentData>>, so every time the embedded ChildData changes, I submit the new List<ParentData> to my parent recycler, which in turn calls OnBindViewHolder and submits the new `childList' and updates the inner child RecyclerView.
The issue is val mAdapter = ChildAdapter() is called every time the data is updated, resetting the entire child RecyclerView. This results in no item add animations being seen and scroll position being reset.
Is there any way to avoid initialising a new Adapter every time or some other way I can avoid resetting the child RecyclerViews?
I know this is an old question but I have worked on a project that is exactly like that and this answer might help others.
Basically we have a single RecyclerView approach where the main interface has a Parent RecyclerView that receives a list of Pair<ViewType, BaseViewBinder>.
class ParentAdapter: RecyclerView.Adapter<BaseViewHolder>() {
private var mList: List<Pair<Int, BaseViewBinder>> = listOf() //Int is the ViewType
fun updateData(mList: List<Pair<Int, BaseViewBinder>>) {
if (this.mList!= mList) {
this.mList = mList
this.notifySetDataChanged()
...
We observe a MediatorLiveData that feeds a new list to the Parent RecyclerView every time there is new data.
The problem is that this.notifySetDataChanged() will update everything in the parent recyclerview. So the solution for the issue where child RecyclerViews "reset" and scroll back to beggining when a new List is received was solved by sending one extra variable to the ParentAdapter "updateData" function informing which view type in the list have changed, so we can "notifyItemChanged" only that specific index of the list, therefore not refreshing the entire recyclerview ui.
fun updateData(mList: List<Pair<Int, BaseViewBinder>>, sectionUpdated: Int) {
if (this.mList!= mList) {
this.mList = mList
when(sectionUpdated){
SECTION_A -> this.notifyItemChanged(mList.indexOfFirst { it.first == VIEW_TYPE_A })
SECTION_B -> this.notifyItemChanged(mList.indexOfFirst { it.first == VIEW_TYPE_B })
SECTION_C -> this.notifyItemChanged(mList.indexOfFirst { it.first == VIEW_TYPE_C })
}
So basically you need to edit whatever function is generating your LiveData, check for differences in the new data, and return the list and an Int specifying that that specific child changed.
Ex:
private var mListA: List<X>
fun returnSomeLiveData(): LiveData<Pair<Int, List<X>>> {
var result = MutableLiveData<Pair<Int, List<X>>>()
if (mListA != someLiveData.value) { //could be DiffUtils
mListA = someLiveData.value
result.postValue(Pair(0, mListA))
}
return result
}
companion object {
val SECTION_A = 0
val SECTION_B = 1
val SECTION_C = 2
}