How to update my Recyclerview using kotlin android? - android

I have an actitvity with Recyclerview which display data. Now I want to update my RecyclerView once got a new data.For now each time I close and reopen my app the new data will be displayed. but i want it without close to update my view.
I have tried this,
but nothing will work,
fun setupViewPager(viewPager: ViewPager, it: List<TransactionEntity>, incoming: TransactionAdapterDirection, mainActivity: MainActivity) {
val cc: Context = mainActivity.applicationContext
if(adapter.count < 2) {
if (incoming.equals(OUTGOING)) {
val gson = Gson()
val gson1 = GsonBuilder().create()
val model = it
val IT = gson.toJson(model)
val pref = cc.applicationContext.getSharedPreferences("MyPrefSend", 0)
val editor = pref.edit()
editor.putString("NEWIT_SEND", IT)
editor.apply()
adapter.addFragment(SendingFragment(),"SEND")
adapter.notifyDataSetChanged()
} else if (incoming.equals(INCOMING)) {
val gson = Gson()
val gson1 = GsonBuilder().create()
val model = it
val IT = gson.toJson(model)
val pref = cc.applicationContext.getSharedPreferences("MyPrefRec", 0)
val editor = pref.edit()
editor.putString("NEWIT_REC", IT)
editor.apply()
adapter.addFragment(ReceiveFragment(), "RECEIVE")
adapter.notifyDataSetChanged()
}
viewPager.adapter = adapter
}
}
class ViewPagerAdapter(manager: FragmentManager) : FragmentStatePagerAdapter(manager)
{
private val mFragmentList: ArrayList<Fragment> = ArrayList<Fragment>()
private val mFragmentTitleList: ArrayList<String> = ArrayList<String>()
override fun getCount(): Int {
return mFragmentList.size
}
override fun getItem(position: Int): Fragment? {
var fragment: Fragment? = null
if (position == 0) {
fragment = SendingFragment()
} else if (position == 1) {
fragment = ReceiveFragment()
}
return fragment
}
fun addFragment(fragment: Fragment, title: String) {
mFragmentList.add(fragment)
mFragmentTitleList.add(title)
}
override fun getPageTitle(position: Int): CharSequence? {
return mFragmentTitleList[position]
}
override fun getItemPosition(fragItem: Any): Int {
var position = 0
if (fragItem is ReceiveFragment) {
position = 0
} else if (fragItem is SendingFragment) {
position = 1
}
return if (position >= 0) position else PagerAdapter.POSITION_NONE
}
}
**Framgnet.kt**
class ReceiveFragment: Fragment()
{
private var linearLayoutManager: LinearLayoutManager? = null
fun fromJson(jsonString: String, type: Type): Any {
return Gson().fromJson(jsonString, type)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val pref = context!!.getSharedPreferences("MyPrefRec", 0)
val mFragIT = pref.getString("NEWIT_REC", "")
val NewIT = fromJson(mFragIT,
object : TypeToken<List<TransactionEntity>>() {
}.type) as List<TransactionEntity>
val activity = activity as MainActivity
val myAppDatabaseData = activity.getAppDatabaseData()
val myNetwrk = activity.getNwtwrkData()
// Inflate the layout for this fragment
val rootView = inflater.inflate(R.layout.receive_fragment, container, false)
val recyclerView = rootView.findViewById<RecyclerView>(R.id.transaction_recycler_in) as RecyclerView
linearLayoutManager = LinearLayoutManager(activity, LinearLayout.VERTICAL, false)
recyclerView.layoutManager = linearLayoutManager
recyclerView.adapter = TransactionRecyclerAdapter(NewIT,myAppDatabaseData,TransactionAdapterDirection.INCOMING,myNetwrk)
recyclerView.setHasFixedSize(true)
return rootView
}
}
I have tried this, mAdapter.notifyDataSetChanged();

If you want to update data from activity/fragment than you can make one function inside adapter as below:
public void update(ArrayList<String> modelList){
//string arraylist is for example pass your data
// replace your adapter data with argument data
mAdapter.notifyDataSetChanged();
}
For kotlin:
fun update(modelList:ArrayList<String){
myList = modelList
myAdapter!!.notifyDataSetChanged()
}
Call this function from activity/fragment as below :
mAdapter.update(response.getList());

You can do it with a function in your ItemAdapter:
/**
* Refresh the whole data set of items.
*/
fun refreshDataset() {
mDataset = parseItems(mAppCtx)
notifyDataSetChanged()
}
and then, any time you receive a new set of data, you can call such function directly.

If you want to do in more Kotlin style do like this:
1) Create an abstract class for Adapter:
abstract class BaseRecyclerAdapter<Type, ViewHolder : BaseViewHolder<Type>>(list: List<Type> = mutableListOf()) : RecyclerView.Adapter<ViewHolder>() {
var items: MutableList<Type> = list.toMutableList()
set(value) {
field = value
notifyDataSetChanged()
}
override fun getItemCount() = items.size
enter code here
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(items[position]) }
2) Create an abstract class for ViewHolder:
abstract class BaseViewHolder<in T>(override val containerView: View): RecyclerView.ViewHolder(containerView), LayoutContainer {
abstract fun bind(item: T) }
3) How to use:
class MyActivity : AppCompatActivity() { private var adapter: MyAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
//This is your data which you set first time
val initData = mutableListOf<String>()
adapter = MyAdapter(initData)
myRecyclerView.layoutManager = adapter
myRecyclerView.layoutManager = LinearLayoutManager(this)
}
// Call this function when you need to update adapter
private fun notifyAdapter(list: MutableList<String>){
adapter.items = list
}}
Create MyAdapter:
private class MyAdapter(list: MutableList<String>) : BaseRecyclerAdapter<String, ViewHolder>(list) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder.newInstance(parent)
}
Create ViewHolder for MyAdapter:
private class ViewHolder(containerView: View) : BaseViewHolder<String>(containerView) {
companion object {
fun newInstance(parent: ViewGroup) = ViewHolder(parent.inflate(R.layout.item))
}
override fun bind(item: String) {
title.text = item
}}
Create item for ViewHolder:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
If you have any questions please ask

You should try LiveData
LiveData is a data holder class that can be observed within a given
lifecycle. This means that an Observer can be added in a pair with a
LifecycleOwner, and this observer will be notified about modifications
of the wrapped data only if the paired LifecycleOwner is in active
state.
Documentation: https://developer.android.com/reference/android/arch/lifecycle/LiveData

Related

recyclerview Drag and drop to different View

I have a recyclerview in my project, where a user will be able to drag and drop it to a different view(card view).
I am following mvvm pattern and using dataBinding. I use the longclick function from within the activity,i created an interface in adapter class for this.
inorder to use the drag and drop i need to get the adapter position of the item in activity, which i am unable to get.
here is the adapter class
class StatementAdapter(
private val context: Context,
private val statementList: ArrayList<Statement>
) :
RecyclerView.Adapter<StatementAdapter.StatementViewHolder>() {
private var mListener: OnItemLongClickListener? = null
interface OnItemLongClickListener {
fun onItemLongClick(statement: Statement)
}
fun setOnItemLongClickListener(listener: OnItemLongClickListener) {
mListener = listener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StatementViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val statementBinding: StatementBinding =
DataBindingUtil.inflate(layoutInflater, R.layout.statement_list, parent, false)
return StatementViewHolder(statementBinding, mListener)
}
override fun getItemCount(): Int {
return statementList.size
}
override fun onBindViewHolder(holder: StatementViewHolder, position: Int) {
val statementViewModel = statementList[position]
holder.bind(statementViewModel)
}
class StatementViewHolder(
private val statementBinding: StatementBinding,
private val listener: OnItemLongClickListener?
) : RecyclerView.ViewHolder(statementBinding.root) {
fun bind(statementViewModel: Statement) {
this.statementBinding.statementModel = statementViewModel
itemView.setOnLongClickListener {
listener?.onItemLongClick(
statementViewModel
)
true
}
statementBinding.executePendingBindings()
}
}
}
Here is the activity
class TrueOrFalseActivity : AppCompatActivity(), StatementAdapter.OnItemLongClickListener {
private lateinit var trueOrFalseBinding: ActivityTrueOrFalseBinding
private var rvStatement: RecyclerView? = null
private var statementAdapter: StatementAdapter? = null
private val dragMessage = "Added"
private val inBucket = "in bucket"
private val offBucket = "not in bucket"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
trueOrFalseBinding = ActivityTrueOrFalseBinding.inflate(layoutInflater)
setContentView(trueOrFalseBinding.root)
rvStatement = findViewById(R.id.rvStatement)
val statementViewModel = ViewModelProvider(this).get(StatementViewModel::class.java)
statementViewModel.generateStatement()
statementViewModel.newMStatementList.observe(this) {
statementAdapter = StatementAdapter(this#TrueOrFalseActivity, it)
rvStatement!!.layoutManager = LinearLayoutManager(this#TrueOrFalseActivity)
rvStatement!!.adapter = statementAdapter
statementAdapter?.setOnItemLongClickListener(this)
}
}
override fun onItemLongClick(statement: Statement) {
val item = ClipData.Item(dragMessage)
val dragData = ClipData(
dragMessage,
arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN),
item
)
val myShadow = MyDragShadowBuilder(this)
// Here where i need to get the item position
}
}
class MyDragShadowBuilder(trueOrFalseActivity: TrueOrFalseActivity) {
}
I am following the following documentation
https://developer.android.com/guide/topics/ui/drag-drop.html#AboutDragging
in this, the "view" is the parameter, but in my case it is arecyclerview item.
how do i solve this?

List is not updated after notifyDataSetChanged what should I do?

I am fetching JSON data from API and passing it in recycler view but if I want to fetch new data and display it in recycler view then I have to clear the list and then add new data in that list and notify the adapter that the data is changed but it is not updated what should I do?
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var recipeViewModel: RecipeViewModel
private lateinit var mainBinding: ActivityMainBinding
private lateinit var recipeAdapter: RecipeAdapter
private lateinit var recipeItemList: ArrayList<Hit>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
recipeViewModel =
ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory
.getInstance(application)
)[RecipeViewModel::class.java]
recipeItemList = arrayListOf()
mainBinding.recyclerView.layoutManager = LinearLayoutManager(this)
mainBinding.recyclerView.hasFixedSize()
recipeAdapter = RecipeAdapter(this)
mainBinding.recyclerView.adapter = recipeAdapter
recipeViewModel.recipeLiveData.observe(this, Observer { recipeItems ->
recipeItemList.addAll(recipeItems.hits)
recipeAdapter.updateRecipes(recipeItemList)
Log.d("RESPONSE", recipeItems.toString())
Log.d("List size", recipeAdapter.itemCount.toString())
})
searchRecipeName()
}
private fun searchRecipeName() {
mainBinding.searchRecipeFabBtn.setOnClickListener {
val view = layoutInflater.inflate(R.layout.recipe_search_layout, null)
val searchRecipeET = view.findViewById<EditText>(R.id.searchRecipeET)
val searchRecipeBtn = view.findViewById<Button>(R.id.searchRecipeBtn)
val bottomSheetDialog = BottomSheetDialog(this)
bottomSheetDialog.apply {
this.setContentView(view)
this.show()
}
searchRecipeBtn.setOnClickListener {
val recipeName = searchRecipeET.text.toString()
searchRecipeName(recipeName, searchRecipeET, bottomSheetDialog)
}
}
}
private fun searchRecipeName(
recipeName: String,
searchRecipeET: EditText,
bottomSheetDialog: BottomSheetDialog
) {
if (recipeName.isEmpty()) {
searchRecipeET.error = "Please enter recipe name"
} else {
recipeViewModel.getRecipes(recipeName)
bottomSheetDialog.dismiss()
}
}
}
RecipeAdapter.kt
class RecipeAdapter(val context: Context) : RecyclerView.Adapter<RecipeAdapter.RecipeViewHolder>() {
private val recipesList: ArrayList<Hit> = arrayListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
val layoutInflater = LayoutInflater.from(context)
val view = layoutInflater.inflate(R.layout.recipe_items_layout, null, false)
return RecipeViewHolder(view)
}
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
val currentItem = recipesList[position]
holder.recipeImageView.load(currentItem.recipe.image)
holder.recipeNameText.text = currentItem.recipe.label
}
override fun getItemCount(): Int {
return recipesList.size
}
class RecipeViewHolder(itemView: View) :RecyclerView.ViewHolder(itemView) {
val recipeImageView: ImageView = itemView.findViewById(R.id.recipeImageView)
val recipeNameText: TextView = itemView.findViewById(R.id.recipeNameText)
}
fun updateRecipes(newRecipesList: ArrayList<Hit>){
recipesList.clear()
Log.d("RECIPE SIZE", "${recipesList.size}")
recipesList.addAll(newRecipesList)
notifyDataSetChanged()
}
}
This may be helpful.
Be careful of this :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
recipeViewModel =
ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory
.getInstance(application)
)[RecipeViewModel::class.java]
recipeItemList = arrayListOf()
mainBinding.recyclerView.layoutManager = LinearLayoutManager(this)
mainBinding.recyclerView.hasFixedSize()
recipeAdapter = RecipeAdapter(this)
mainBinding.recyclerView.adapter = recipeAdapter
recipeViewModel.recipeLiveData.observe(this, Observer { recipeItems ->
// You're adding items here but never clear the list
// list will be bigger every time you'll be notified
// recipeItemList.addAll(recipeItems.hits
// recipeAdapter.updateRecipes(recipeItemList)
// Do this instead
recipeItemList = recipeItems.hits
recipeAdapter.updateRecipes(recipeItemList)
Log.d("RESPONSE", recipeItems.toString())
Log.d("List size", recipeAdapter.itemCount.toString())
})
searchRecipeName()
}
Also, here: It's a little better to do this (https://stackoverflow.com/a/10298038/4221943)
fun updateRecipes(newRecipesList: ArrayList<Hit>){
recipesList = newRecipesList
Log.d("RECIPE SIZE", "${recipesList.size}")
notifyDataSetChanged()
}
BTW it will always be more efficient to use the more specific change events if you can. Rely on notifyDataSetChanged() as a last resort. It is also good practice to use notifyItemInserted(mItems.size() - 1) for "easier" solution.
You could convert the RecyclerView.Adapter into a ListAdapter:
class RecipeAdapter(val context: Context) : ListAdapter<Hit, RecipeAdapter.RecipeViewHolder>(RecipeDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
val layoutInflater = LayoutInflater.from(context)
val view = layoutInflater.inflate(R.layout.recipe_items_layout, null, false)
return RecipeViewHolder(view)
}
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
val currentItem = getItem(position)
holder.recipeImageView.load(currentItem.recipe.image)
holder.recipeNameText.text = currentItem.recipe.label
}
class RecipeViewHolder(itemView: View) :RecyclerView.ViewHolder(itemView) {
val recipeImageView: ImageView = itemView.findViewById(R.id.recipeImageView)
val recipeNameText: TextView = itemView.findViewById(R.id.recipeNameText)
}
}
class RecipeDiffCallback : DiffUtil.ItemCallback<Hit>() {
// Change this condition based on the attribute of `Hit` that will change
override fun areItemsTheSame(oldItem: Hit, newItem: Hit): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Hit, newItem: Hit): Boolean = oldItem == newItem
}
Then update its content with the submitList method.
Every item not satisfying the RecipeDiffCallback conditions will be automatically updated:
recipeViewModel.recipeLiveData.observe(this, Observer { recipeItems ->
recipeAdapter.submitList(recipeItems.hits)
})

RecyclerView Adapter with ViewDataBinding

I am creating a GenericAdapter for handling single row|item layouts. Everything working fine only view-binding is not updating data..
I want to get RecyclerView.ViewHolder binding in callback ,I know I can bind it in adapter using BR.item and executePending
I want viewDataBinding context in a Callback
holder.binding.name.text = mutableList[pos]
Above line in TestActivity not working properly
GenericAdapter.kt
class GenericAdapter<T,VB:ViewDataBinding>(
var items:MutableList<T>,
#LayoutRes val resLayoutID:Int,
val onBind:(holder:GenericViewHolder<T,VB>,pos:Int) -> Unit
): RecyclerView.Adapter<GenericViewHolder<T,VB>>() {
lateinit var mItemBinding:VB
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GenericViewHolder<T,VB> {
val layoutInflater = LayoutInflater.from(parent.context)
mItemBinding = DataBindingUtil.inflate(layoutInflater, resLayoutID, parent, false)
return GenericViewHolder(mItemBinding)
}
override fun onBindViewHolder(holder: GenericViewHolder<T,VB>, position: Int) {
onBind(holder,position)
}
override fun getItemCount(): Int = items.size
}
GenericViewHolder.kt
class GenericViewHolder<T,VB: ViewDataBinding>(val binding: VB)
:RecyclerView.ViewHolder(binding.root){
val mItemBinding:VB = binding
}
TestActivity.kt
class TestActivity:AppCompatActivity() {
lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
recyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(true)
populateList()
}
private fun populateList(){
val mutableList = mutableListOf<String>("apple","mango","tutti fruti","apricot",
"apple","mango","tutti fruti","apricot",
"apple","mango","tutti fruti","apricot",
"apple","mango","tutti fruti","apricot")
val mAdapter = GenericAdapter<String,ItemCountryBinding>(mutableList,R.layout.item_country){ holder,pos ->
//val nameTv = holder.itemView.findViewById<TextView>(R.id.name)
//nameTv.text = mutableList[pos]
holder.binding.name.text = mutableList[pos]
}
recyclerView.adapter = mAdapter
}
}
Where as below code working fine
val nameTv = holder.itemView.findViewById<TextView>(R.id.name)
nameTv.text = mutableList[pos]
I would recommend do onBind inside view holder.
I don't see reason why you need this callback.
You already passed mutableList to the adapter.
For example here is my ViewHolder
class NewsViewHolder(private val binding: ListItemNewsBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(news: RedditPost, itemClick: (RedditPost, View) -> Unit) {
with(binding) {
root.transitionName = news.thumbnail
root.setOnClickListener { itemClick.invoke(news, binding.root) }
title.text = news.title
image.setImageUrl(news.thumbnail)
author.text = news.author
xHoursAgo.text = news.created_utc
numComments.text = news.numComments
}
}
}
And
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (isLoadingMore && position == (itemCount - 1)) return
(holder as? NewsViewHolder)?.bind(news[position], itemClick)
}

How to sort encrypted ArrayList and show this in a RecyclerView

My target is to sort in alphabetical order data from an ArrayList with encrypted data and put correct order into a RecyclerView.
I'm able to show decrypted data into RecyclerView, but in the same order witch I use to add item into the ArrayList (see code in adapter).
I tried to sort with sortby{}without success (see code in activity). The result is an partial order and onItemClick not return correct position.
My activity that extend adapter:
class HomeActivity : AppCompatActivity(),DocumentAdapter.OnItemClickListener {
companion object{
lateinit var dbHandler: DBHandler
}
private var documentslist = ArrayList<PassItem>()
private lateinit var adapter : RecyclerView.Adapter<*>
private lateinit var rv : RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
dbHandler = DBHandler(this, null, null,1)
viewDocuments()
val fab = findViewById<FloatingActionButton>(R.id.fabAdd)
fab.setOnClickListener {
val i = Intent(this, AddDocument :: class.java)
startActivity(i)
}
}
#SuppressLint("WrongConstant")
private fun viewDocuments(){
documentslist = dbHandler.getDocuments(this)
//documentslist.sortBy { it.PassItemApp.toString() } ---> this sort encrypted data then not in alphabetic order and onItemClick return uncorrect position
adapter = DocumentAdapter(this,documentslist,this)
rv = findViewById(R.id.recyclerView)
rv.layoutManager = LinearLayoutManager(this, LinearLayout.VERTICAL,false)
rv.adapter = adapter
}
override fun onItemClick(position: Int) {
val intent = Intent(this#HomeActivity,DocActivity::class.java)
intent.putExtra("clickedPosition", position)
startActivity(intent)
}
override fun onResume() {
viewDocuments()
super.onResume()
}
and my adapter:
class DocumentAdapter(val mCtx: Context, private val documents: ArrayList<PassItem>, val listener: OnItemClickListener) : RecyclerView.Adapter<DocumentAdapter.ViewHolder>(){
interface OnItemClickListener{
fun onItemClick(position : Int)
}
inner class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView), View.OnClickListener{
val txtDocApp : TextView = itemView.findViewById(R.id.txtDocApp)
init {
itemView.setOnClickListener(this)
}
override fun onClick(v: View?) {
val position : Int = adapterPosition
if (position != RecyclerView.NO_POSITION){
listener.onItemClick(position)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DocumentAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.item_pass_layout,parent,false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val document : PassItem = documents[position]
//Decryption of item showed in Recyclerview
val initialisationVector = document.PassItemIvApp
val bytesIV = Base64.decode(initialisationVector, Base64.NO_WRAP)
val oisIV = ObjectInputStream(ByteArrayInputStream(bytesIV))
val initializationVectorIV = oisIV.readObject() as ByteArray
val encryptedText = document.PassItemApp
val bytesText = Base64.decode(encryptedText, Base64.NO_WRAP)
val oisText = ObjectInputStream(ByteArrayInputStream(bytesText))
val textByteArray = oisText.readObject() as ByteArray
val key = document.PassItemKeyApp
val keyText = Base64.decode(key, Base64.NO_WRAP)
val oisKey = ObjectInputStream(ByteArrayInputStream(keyText))
val keyToDecrypt = oisKey.readObject() as SecretKey
val aesDecrypt = Decrypted(keyToDecrypt,initializationVectorIV)
val decryptedByteArray = aesDecrypt.decrypt(textByteArray)
val textAfterDecryption = decryptedByteArray.toString(charset("UTF_8"))
//Data in Recyclerview
holder.txtDocApp.text = textAfterDecryption
}
override fun getItemCount(): Int {
return documents.size
}
}

DiffUtil issues : old and new list are the same

Context :
MainActivity launches, create a list of ItemObject, and call ListFragment. When the user clicks on button (which is in MainActivity), it modify the list, then call the listFragment to update it.
Issues found :
In the update function on listFragment, newList and oldList are the same.
Here is my code.
MainActivity
class MainActivity : AppCompatActivity() {
private val viewModel = MainViewModel()
private var button : FloatingActionButton? = null
private val listFragment = ListFragment.newInstance()
var list = ArrayList<ItemObject>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
binding.viewModel = viewModel
button = binding.button
button?.setOnClickListener { modifyList() }
createItemsList()
}
/**
* This creates a list of 10 ItemObject, that will contains `i` as `randomInt` and "TEST $i" as `title`, then call
* setListFragment().
*/
private fun createItemsList() {
val itemsList = ArrayList<ItemObject>()
var i = 0
while (i < 10) {
itemsList.add(ItemObject().apply { this.randomInt = i ; this.title = "TEST $i" })
i++
}
list = itemsList
setListFragment()
}
/**
* Set listFragment inside content.
*/
private fun setListFragment() {
supportFragmentManager.beginTransaction().replace(R.id.content, listFragment).commit()
}
/**
* Triggered when the user clicks on the FloatingActionButton. Will modify each even item, add 2 to its `randomInt`
* and set its `title` to "MODIFIED $randomInt".
*/
private fun modifyList() {
list.forEach {
if (it.randomInt % 2 == 0) {
it.randomInt += 2
it.title = "MODIFIED ${it.randomInt}"
}
}
if (listFragment.isAdded) {
listFragment.updateList(list)
}
}
inner class MainViewModel
}
And ListFragment :
class ListFragment : Fragment() {
private val viewModel = ListViewModel()
private val listAdapter = ListAdapter()
private var listRv : RecyclerView? = null
private var list = ArrayList<ItemObject>()
override fun onCreateView(inflater: LayoutInflater, parent: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = DataBindingUtil.inflate<FragmentListBinding>(inflater, R.layout.fragment_list, parent, false)
binding.viewModel = viewModel
listRv = binding.listRv
list = (activity as MainActivity).list
setList()
return (binding.root)
}
/**
* Sets up the RecyclerView and set the list inside it.
*/
private fun setList() {
listRv?.layoutManager = LinearLayoutManager(context)
listRv?.adapter = listAdapter
listRv?.post { listAdapter.setData(list) }
}
/**
* Triggered by MainActivity when the user clicks on the button and the list is modified. Will call update() method
* from adapter.
*/
fun updateList(newList : ArrayList<ItemObject>) {
listAdapter.update(newList)
}
companion object {
fun newInstance() : ListFragment = ListFragment()
}
inner class ListViewModel
inner class ItemDiff : DiffUtil.Callback() {
private var old = ArrayList<ItemObject>()
private var new = ArrayList<ItemObject>()
fun setLists(old : ArrayList<ItemObject>, new : ArrayList<ItemObject>) {
this.old = old
this.new = new
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
println("ARE ITEMS ${old[oldItemPosition].title} THE SAME ? ${(old[oldItemPosition] == new[newItemPosition])}")
return (old[oldItemPosition] == new[newItemPosition])
}
override fun getOldListSize(): Int = old.size
override fun getNewListSize(): Int = new.size
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
println("ARE ITEMS ${old[oldItemPosition].title} CONTENTS THE SAME ? ${(old[oldItemPosition].title == new[newItemPosition].title
&& old[oldItemPosition].randomInt == new[newItemPosition].randomInt)}")
return (old[oldItemPosition].title == new[newItemPosition].title
&& old[oldItemPosition].randomInt == new[newItemPosition].randomInt)
}
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
val oldItem = old[oldItemPosition]
val newItem = new[newItemPosition]
val bundle = Bundle()
if (oldItem.title != newItem.title) {
println("SHOULD ADD NEW STRING ${newItem.title}")
bundle.putString("title", newItem.title)
}
if (oldItem.randomInt != newItem.randomInt) {
println("SHOULD ADD NEW INT ${newItem.randomInt}")
bundle.putInt("randomInt", newItem.randomInt)
}
return (bundle)
}
}
inner class ListAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var items = ArrayList<ItemObject>()
fun setData(list : ArrayList<ItemObject>) {
items = list
notifyDataSetChanged()
}
/**
* #param new
* Triggered when the list is modified in the parent activity. Uses DiffUtil to update the list.
*/
fun update(new : ArrayList<ItemObject>) {
println("///// IN UPDATE ; WILL PRINT OLD AND NEW LIST /////")
items.forEach { println("OLD ITEM ${it.title}") }
new.forEach { println("NEW ITEM ${it.title}") }
println("///// PRINT END /////")
val diffCallback = ItemDiff()
diffCallback.setLists(old = items, new = new)
val diffResult = DiffUtil.calculateDiff(diffCallback)
diffResult.dispatchUpdatesTo(this)
items = new
}
override fun onCreateViewHolder(parent: ViewGroup, position: Int): RecyclerView.ViewHolder {
return (ItemViewHolder(DataBindingUtil.inflate<ItemBinding>(LayoutInflater.from(parent.context),
R.layout.item, parent, false).apply {
viewModel = ItemViewModel()
}))
}
override fun getItemCount(): Int = items.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as ItemViewHolder).setData(items[position])
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: MutableList<Any>) {
println("IN ON BIND VIEWHOLDER ; PAYLOAD SIZE = ${payloads.size}")
if (payloads.isEmpty())
super.onBindViewHolder(holder, position, payloads)
else {
val bundle = payloads[0] as Bundle
if (bundle.size() != 0) {
val name = bundle.getString("name")
val randomInt = bundle.getInt("randomInt")
if (name != null)
(holder as ItemViewHolder).setName(name)
(holder as ItemViewHolder).setRandomInt(randomInt)
}
}
}
inner class ItemViewHolder(private val binding : ItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun setData(item : ItemObject) {
binding.viewModel?.setData(item)
}
fun setRandomInt(newInt : Int) {
binding.viewModel?.setRandomInt(newInt)
}
fun setName(newName : String) {
binding.viewModel?.setTitle(newName)
}
}
inner class ItemViewModel {
val title = ObservableField<String>("")
val randomInt = ObservableField<String>("")
fun setData(item : ItemObject) {
setRandomInt(item.randomInt)
setTitle(item.title)
}
fun setRandomInt(newInt : Int) {
randomInt.set(newInt.toString())
}
fun setTitle(newName : String) {
title.set(newName)
}
}
}
}
Here is my ItemObject class :
class ItemObject {
var title = ""
var randomInt = 0
}
What is strange is that i NEVER modify my list inside ListFragment. So how is that possible that when the list is updated, my debug println show me that old and new lists are the same ?

Categories

Resources