DiffUtil issues : old and new list are the same - android

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 ?

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?

show progressbar in recyclerview's onBindViewHolder

I want to show progressBar from activity (in SubitemAdapter.kt) when elements in recyclerView are being loaded (about 150 to populate) but now this progressBar not shows at all. Here is my code:
CurrencyListFragment.kt
class CurrencyListFragment : Fragment(), MainContract.View {
companion object {
private val TAG = CurrencyListFragment::class.qualifiedName
}
private val restModel: RestModel = RestModel()
private val handler: Handler = Handler(Looper.getMainLooper())
private lateinit var mainPresenter: MainPresenter
private lateinit var itemAdapter: ItemAdapter
private lateinit var _layoutManager: LinearLayoutManager
private lateinit var onChangeFragment: OnChangeFragment
private lateinit var currentDate: String
private var isLoading: Boolean = false
private var apiResponseList: MutableList<ApiResponse> = arrayListOf()
private var listSize: Int = 0
override fun onAttach(context: Context) {
super.onAttach(context)
try {
if (activity is OnChangeFragment) onChangeFragment = activity as OnChangeFragment
} catch (error: ClassCastException) {
error.message?.let { Log.e(TAG, it) }
}
}
// #formatter:off
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.currency_list_fragment, container, false)
}
// #formatter:on
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_layoutManager = LinearLayoutManager(activity)
mainPresenter = MainPresenter(this, restModel, SharedPreferencesModel(activity as Activity))
currentDate = mainPresenter.convertCurrentDate()
if (mainPresenter.checkIfSuchDateExistsinSp(currentDate)) {
Log.i(TAG, "Date $currentDate already exists in SharedPreferences")
mainPresenter.processDateWithoutMakingACall(currentDate)
} else {
mainPresenter.makeACall(currentDate)
Log.i(TAG, "Date $currentDate does not exist in SharedPreferences. Retrofit call made")
}
mainPresenter.saveNumberOfMinusDaysIntoSp(0)
addScrollerListener()
}
override fun showProgressBarOnLoadingCurrencies() {
progress_bar.visibility = View.VISIBLE
}
override fun hideProgressBarOnFinishedLoadingCurrencies() {
progress_bar.visibility = View.GONE
}
override fun setRecyclerViewStateToLoading() {
if (apiResponseList.size > 0) {
apiResponseList.add(ApiResponse("", "", listOf(Currency("", 0f)), true))
itemAdapter.notifyItemInserted(apiResponseList.size - 1)
}
}
override fun removeRecyclerViewStetOfLoading() {
if (apiResponseList.size > 1) {
apiResponseList.removeAt(apiResponseList.size - 1)
listSize = apiResponseList.size
itemAdapter.notifyItemRemoved(listSize)
}
isLoading = false
}
override fun getApiResponseList(): List<ApiResponse> {
return apiResponseList
}
override fun showLogAboutExistingDateInSp(date: String) {
Log.i(TAG, "Date $date already exists in SharedPreferences (new element)")
}
override fun showLogAboutNotExistingDateInSp(date: String) {
Log.i(TAG, "Date $date does not exist in SharedPreferences. Retrofit call made (new element)")
}
override fun assignResponseToRecyclerview(apiResponse: ApiResponse?) {
rv_item.apply {
layoutManager = _layoutManager
apiResponseList.add(apiResponse!!)
itemAdapter = activity?.let { ItemAdapter(apiResponseList, it) }!!
adapter = itemAdapter
}
}
private fun addScrollerListener() {
rv_item.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(rvItem: RecyclerView, newState: Int) {
super.onScrollStateChanged(rvItem, newState)
mainPresenter.processRvitemOnScroll(isLoading, rvItem, newState)
}
})
}
private fun loadMore() {
setRecyclerViewStateToLoading()
var numberOfDays = mainPresenter.getNumberOfMinusDays()
numberOfDays++
mainPresenter.saveNumberOfMinusDaysIntoSp(numberOfDays)
val dateMinusXDays = mainPresenter.currentDateMinusXDaysToStr(numberOfDays)
val nextLimit = listSize + 1
for (i in listSize until nextLimit) {
if (mainPresenter.checkIfSuchDateExistsinSp(dateMinusXDays)) {
Log.i(TAG, "Date $dateMinusXDays already exists in SharedPreferences (new element)")
handler.postDelayed({
mainPresenter.processDateWithoutMakingACall(dateMinusXDays)
}, 2000)
} else {
Log.i(TAG, "Date $dateMinusXDays does not exist in SharedPreferences. Retrofit call made (new element)")
mainPresenter.makeACall(dateMinusXDays)
}
}
itemAdapter.notifyDataSetChanged()
}
override fun notifyChangedItemAdapter() {
itemAdapter.notifyDataSetChanged()
}
override fun onDestroy() {
super.onDestroy()
restModel.cancelJob()
}
}
ItemAdapter.kt
class ItemAdapter(private var items: MutableList<ApiResponse>, private val activity: Activity) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
private const val VIEW_TYPE_DATA = 0
private const val VIEW_TYPE_PROGRESS = 1
}
override fun onCreateViewHolder(parent: ViewGroup, p1: Int): RecyclerView.ViewHolder {
return when (p1) {
VIEW_TYPE_DATA -> {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false)
DataViewHolder(view, activity)
}
VIEW_TYPE_PROGRESS -> {
val view = LayoutInflater.from(parent.context).inflate(R.layout.progress_bar_layout, parent, false)
ProgressViewHolder(view)
}
else -> throw IllegalArgumentException("Different View type")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is DataViewHolder)
holder.bind(items[position])
}
override fun getItemCount() = items.size
override fun getItemViewType(position: Int): Int {
val viewtype = items[position]
return when (viewtype.isLoading) {//if data is load, returns PROGRESSBAR viewtype.
true -> VIEW_TYPE_PROGRESS
false -> VIEW_TYPE_DATA
}
}
class DataViewHolder(view: View, activity: Activity) : RecyclerView.ViewHolder(view) {
private var isRvSubitemVisible = false
private val tvDate = view.tv_date
private val rvSubitem = view.rv_subitem
private val activity = activity
fun bind(apiResponse: ApiResponse) {
tvDate.text = String.format(itemView.context.getString(R.string.day_x), apiResponse.date)
tvDate.setOnClickListener {
if (isRvSubitemVisible) {
rvSubitem.visibility = View.GONE
isRvSubitemVisible = false
} else {
rvSubitem.visibility = View.VISIBLE
isRvSubitemVisible = true
}
}
rvSubitem.apply {
layoutManager = LinearLayoutManager(itemView.context)
adapter = SubitemAdapter(apiResponse.rates, apiResponse.date, activity)
}
}
}
inner class ProgressViewHolder(view: View) : RecyclerView.ViewHolder(view)
}
SubitemAdapter.kt
class SubitemAdapter(private val subitems: List<Currency>, private val day: String, private val activity: Activity) : RecyclerView.Adapter<SubitemAdapter.SubitemViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, p1: Int): SubitemViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.subitem, parent, false)
return SubitemViewHolder(view, day, activity)
}
override fun onBindViewHolder(holder: SubitemViewHolder, position: Int) {
if(position < subitems.size - 1) {
activity.progress_bar.visibility = View.VISIBLE
}
else
activity.progress_bar.visibility = View.GONE
holder.bind(subitems[position], position)
}
override fun getItemCount() = subitems.size
class SubitemViewHolder(view: View, day: String, activity: Activity) : RecyclerView.ViewHolder(view) {
private val subitemRootView = view.subitem_root
private val tvCurrencyName = view.tv_currency_name
private val tvCurrencyValue = view.tv_currency_value
private val day = day
private val activity = activity
fun bind(currency: Currency, position: Int) {
subitemRootView.setOnClickListener { v ->
activity as OnChangeFragment
activity.changeFragment(SpecificCurrencyFragment(), ChangeFragmentData(hashMapOf(currency.currencyName to currency.currencyValue.toString()), day))
}
tvCurrencyName.text = currency.currencyName
tvCurrencyValue.text = currency.currencyValue.toString()
}
}
}
Here is I think everything to help me. But if you need something else more just aks.
Any help will bve really appreciated. Thank you in advance!
You are already passing a List<Currency> which are the items to be loaded inside your recyclerview. You should not be checking this inside onBindViewHolder since you already have those items when you passed them as an argument to the recyclerview adapter
Instead, when you are passing the List<Currency> to your adapter, you must update the progressbar at that time from activity itself. You can edit your question and add code for your Activity if this didn't help you, I'll try to answer you :)

Update status from viewholder on click android kotlin

I have a shopping cart (Recycle View).
When you click on a product, it should change its state(change the background background and the status in the ROOM)
I thought to solve this problem like this:
When you click on the product update the product status in the ROOM and update the list of products and depending on the status change the color, but the problem is that when in the itemadapter I can not call viewlifecycleowner. It doesn't see it(
If I write this logic in a fragment, then I can't call this function from ViewHolder.
Please help
class OrderItemAdapter() : RecyclerView.Adapter<OrderItemAdapter.ViewHolder>() {
private var mListProduct: MutableList<Product?>? = null
private var mViewModel: OrderViewModel? = null
constructor(viewModel: OrderViewModel, listProduct: MutableList<Product?>?) : this() {
mListProduct = listProduct
mViewModel = viewModel
}
override fun getItemCount(): Int {
return if (mListProduct == null) 0 else mListProduct!!.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(position)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return ViewHolder(inflater.inflate(R.layout.frg_order_item, parent, false))
}
inner class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
private val title: TextView = view.findViewById(R.id.product_name)
private val count: TextView = view.findViewById(R.id.product_count)
private val price: TextView = view.findViewById(R.id.product_price)
private val btnEdit: Button = view.findViewById(R.id.product_edit)
private val btnChange: Button = view.findViewById(R.id.button_change)
private val productImage: ImageView = view.findViewById(R.id.product_image)
private val btnDone: Button = view.findViewById(R.id.done)
#ExperimentalCoroutinesApi
fun bind(pos: Int) {
title.text = mListProduct?.get(pos)?.name
price.text = mListProduct?.get(pos)?.price.toString()
count.text = mListProduct?.get(pos)?.brgew.toString() + " " + mListProduct?.get(pos)?.gewei.toString() + " " + mListProduct?.get(pos)?.quantity.toString() + " " + mListProduct?.get(pos)?.units.toString()
view.setBackgroundColor(changerColorStatus( mListProduct?.get(pos)?.status!!))
//count.text = pos.toString()
productImage.downloadAndSetImage(mListProduct?.get(pos)?.pathImage!!)
btnDone.setOnClickListener {
val product = mListProduct?.get(pos)
if (product != null) {
mViewModel!!.toCollectProduct(product.id!!)
}
}
btnEdit.setOnClickListener {
val product = mListProduct?.get(pos)
if (product != null) {
view.findNavController().navigate(
OrderFragmentDirections.actionOrderFragmentToProductEntryDialogFragment(product)
}
}
btnChange.setOnClickListener {
val product = mListProduct?.get(pos)
view.findNavController().navigate((OrderFragmentDirections.actionOrderFragmentToBarcodeScanningActivity()))
}
}
}
fun submitList(it: List<Product>?) {
mListProduct = it?.toMutableList()
notifyDataSetChanged()
}
fun changerColorStatus(statusProduct: StatusProduct): Int {
return when (statusProduct) {
StatusProduct.COLLECTED -> Color.GREEN
StatusProduct.NOT_COLLECTED -> Color.YELLOW
StatusProduct.EDIT -> Color.CYAN
StatusProduct.REMOVED -> Color.GRAY
StatusProduct.REPLACE -> Color.GRAY
}
}
}
OrderViewModel
#ExperimentalCoroutinesApi
class OrderViewModel #ViewModelInject constructor(private val ordersRepository: OrdersRepository) :
ViewModel() {
//TODO MAKE MUTABLE
fun getBasket(id: Long): LiveData<Resource<List<Product>>> = ordersRepository.getBasket(id)
fun toCollectProduct(id: Long) {
ordersRepository.toCollectProduct(id)
}
fun updateFromLocalDB(id: Long) = ordersRepository.getOrderLocal(id);
}
OrderFragment
#ExperimentalCoroutinesApi
#AndroidEntryPoint
class OrderFragment : Fragment() {
private var orderId = -1L
private val mViewModel: OrderViewModel by viewModels()
private lateinit var mAdapter: OrderItemAdapter
private lateinit var recycler: RecyclerView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.frg_order, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recycler = view.chat_recycle_view
arguments?.let {
val safeArgs = OrderFragmentArgs.fromBundle(it)
orderId = safeArgs.idOrder
//TODO Log it
setupRecycler()
setupObservers()
}
fab.setOnClickListener {
view.findNavController().navigate(R.id.action_orderFragment_to_barcodeScanningActivity)
}
}
private fun setupRecycler() {
recycler.layoutManager = LinearLayoutManager(context)
val itemDecor = DividerItemDecoration(context, RecyclerView.VERTICAL)
recycler.addItemDecoration(itemDecor)
mAdapter = OrderItemAdapter(mViewModel, null)
recycler.adapter = mAdapter
chat_swipe_refresh.setOnRefreshListener { setupObservers() }
// mViewModel.getBasket(orderId)
}
private fun setupObservers() {
mViewModel.getBasket(orderId).observe(viewLifecycleOwner, Observer {
when (it.status) {
Resource.Status.SUCCESS -> {
//binding.progressBar.visibility = View.GONE
isRefreshing(false)
if (!it.data?.isNullOrEmpty()!!) mAdapter.submitList(it.data)
}
Resource.Status.ERROR -> {
Toast.makeText(requireContext(), it.message, Toast.LENGTH_SHORT).show()
isRefreshing(false)
}
Resource.Status.LOADING -> {
isRefreshing(true)
//progressBar.visibility = View.VISIBLE
}
}
})
}
private fun isRefreshing(refreshing : Boolean){
chat_swipe_refresh.isRefreshing = refreshing
}
fun updateFromLocalDB(id: Long,view: View){
mViewModel!!.updateFromLocalDB(id).observe(viewLifecycleOwner, Observer {
mAdapter.submitList(it)
})
}
}
First of all instead of Passing view model to adapter you should pass function (lambda or listener) to your adapter then your viewHolder like:
class Yourfragment {
val adapter = YourAdapter( { it:Int ->
// here you can use viewModel calls
})
}
class YourAdapter(val clickFunc:(Int) -> Unit){
// ...
}
// (Int)->Unit mean clickFunc is a function which gets integer as argument and return Unit
// then in your viewHolder.
init {
itemView.setOnCLickListener{ clickFunc.invoke(adapterPosition) }
}

ListAdapter does not fill the adapter on 2nd fragment of application

I have an application with 2 fragments, both have lists that are being filled from the same adapter.
The first one works correctly, but the second one -
class CountryBordersFragment : Fragment(R.layout.fragment_country_borders) {
private lateinit var selectedCountry: String
private lateinit var countriesViewModel: CountriesListViewModel
private lateinit var countriesAdapter: CountriesListAdapter
private var countriesList = mutableListOf<CountryEntity>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
countriesViewModel = ViewModelProvider(this).get(CountriesListViewModel::class.java)
initData()
initClickListener()
}
private fun initClickListener() {
backButton.setOnClickListener {
requireActivity().onBackPressed()
}
}
private fun initData() {
countriesAdapter = CountriesListAdapter(null)
countriesAdapter.submitList(countriesList)
countriesRecyclerView.setHasFixedSize(true)
countriesRecyclerView.layoutManager = LinearLayoutManager(context)
countriesRecyclerView.adapter = countriesAdapter
arguments?.let {
selectedCountry = it.getString(getString(R.string.countries_list_fragment_selected_country))!!
}
countryName.text = selectedCountry
countriesViewModel.getCountryBorders(selectedCountry).observeOnce(requireActivity(), Observer { countryBorder ->
if (countryBorder.neighborCountries.isEmpty()) {
bordersWith.text = getString(R.string.country_border_fragment_country_does_not_have_borders)
return#Observer
}
countriesViewModel.getCountryByCioc(countryBorder.neighborCountries).observe(requireActivity(), Observer { countryEntityList ->
countriesAdapter.submitList(countryEntityList)
})
})
}
}
Does not fill the adapter at all. It just does not display any list whatsoever.
For sure something is missing because I am able to fill my adapter correctly at the first fragment but coming to this one the list does not pop up.
Here is my ListAdapter implementation -
class CountriesListAdapter(private val callback: CountryViewHolder.OnCountryClickListener?)
: ListAdapter<CountryEntity, CountryViewHolder>(CountriesDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CountryViewHolder {
val view = LayoutInflater.from(App.context!!).inflate(R.layout.country_viewholder, parent, false)
return CountryViewHolder(view)
}
override fun onBindViewHolder(holder: CountryViewHolder, position: Int) {
holder.bind(getItem(position), callback)
}
class CountriesDiffCallback : DiffUtil.ItemCallback<CountryEntity>() {
override fun areItemsTheSame(oldItem: CountryEntity, newItem: CountryEntity): Boolean {
return oldItem.countryName == newItem.countryName
}
override fun areContentsTheSame(oldItem: CountryEntity, newItem: CountryEntity): Boolean {
return oldItem == newItem
}
}
}
and my ViewHolder and model -
class CountryViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val rootLayout: LinearLayout = view.country_viewholder_root_layout
private val nativeName: TextView = view.country_viewholder_native_name
private val countryName: TextView = view.country_viewholder_country_name
private val area: TextView = view.country_viewholder_area
private val countryImage: ImageView = view.country_viewholder_country_image
fun bind(model: CountryEntity, callback: OnCountryClickListener?) {
nativeName.text = App.context!!.getString(R.string.country_view_holder_native_name).plus(" ${model.countryName}")
countryName.text = App.context!!.getString(R.string.country_view_holder_country_name).plus(" ${model.nativeName}")
area.text = App.context!!.getString(R.string.country_view_holder_country_area).plus(" ${model.area}")
// Glide.with(App.context!!).load("https://www.talkwalker.com/images/2020/blog-headers/image-analysis.png").into(countryImage)
Picasso.get().load(model.imageUri).into(countryImage)
rootLayout.setOnClickListener {
callback?.onCountryClicked(model.countryName)
}
}
interface OnCountryClickListener {
fun onCountryClicked(countryName: String)
}
}
#Entity(tableName = countriesTable, primaryKeys = ["countryName"])
class CountryEntity(
val countryName: String,
val nativeName: String,
val area: Double,
val cioc: String? = null,
val imageUri : String? = null
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CountryEntity
if (countryName != other.countryName) return false
if (nativeName != other.nativeName) return false
if (area != other.area) return false
if (cioc != other.cioc) return false
if (imageUri != other.imageUri) return false
return true
}
override fun hashCode(): Int {
var result = countryName.hashCode()
result = 31 * result + nativeName.hashCode()
result = 31 * result + area.hashCode()
result = 31 * result + (cioc?.hashCode() ?: 0)
result = 31 * result + (imageUri?.hashCode() ?: 0)
return result
}
}
class CountryViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val rootLayout: LinearLayout = view.country_viewholder_root_layout
private val nativeName: TextView = view.country_viewholder_native_name
private val countryName: TextView = view.country_viewholder_country_name
private val area: TextView = view.country_viewholder_area
private val countryImage: ImageView = view.country_viewholder_country_image
fun bind(model: CountryEntity, callback: OnCountryClickListener?) {
nativeName.text = App.context!!.getString(R.string.country_view_holder_native_name).plus(" ${model.countryName}")
countryName.text = App.context!!.getString(R.string.country_view_holder_country_name).plus(" ${model.nativeName}")
area.text = App.context!!.getString(R.string.country_view_holder_country_area).plus(" ${model.area}")
// Glide.with(App.context!!).load("https://www.talkwalker.com/images/2020/blog-headers/image-analysis.png").into(countryImage)
Picasso.get().load(model.imageUri).into(countryImage)
rootLayout.setOnClickListener {
callback?.onCountryClicked(model.countryName)
}
}
interface OnCountryClickListener {
fun onCountryClicked(countryName: String)
}
}
What is it that I am missing? Just started working with ListAdapter from normal RecyclerView.Adapter.
Looks like you are missing - notifyDataSetChanged()
Just After -
countriesAdapter.submitList(countryEntityList)
Add -
countriesAdapter.notifyDataSetChanged()

How to update my Recyclerview using kotlin 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

Categories

Resources