DiffUtil.ItemCallback<T> is called twice - android

I'm making a chat application using Firebase and I have a listener for my documents so that every time there's a new message I'll set the new lists to the value of my LiveData<List<T>> that is being observe in my Fragment and calls submistList() to my ListAdapter<>
My problem is everytime I add a new message, I can see a small flash on the recyclerview and after further debugging, I found out my DiffUtil.ItemCallback is being twice despite calling submitList() only once.
I really have no idea what I missed, thank you for any help.
DiffUtil.ItemCallback
class ChatsDiffCallBack : DiffUtil.ItemCallback<Chat>() {
override fun areItemsTheSame(oldItem: Chat, newItem: Chat): Boolean {
Log.d("Adapter", "Old: ${oldItem.id} New: ${newItem.id}")
Log.d("Adapter", "Equal: ${oldItem.id == newItem.id}")
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Chat, newItem: Chat): Boolean {
return oldItem == newItem
}
}
Adapter
class ChatAdapter : ListAdapter<Chat, ChatAdapter.ViewHolder>(ChatsDiffCallBack()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent, viewType)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}
override fun getItemViewType(position: Int): Int {
if (getItem(position).chatType == ChatType.mine) return R.layout.my_chat_item
return R.layout.their_chat_item
}
class ViewHolder private constructor(val binding: ViewDataBinding) :
RecyclerView.ViewHolder(binding.root) {
companion object {
fun from(parent: ViewGroup, layoutId: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding: ViewDataBinding =
DataBindingUtil.inflate(layoutInflater, layoutId, parent, false)
return ViewHolder(binding)
}
}
fun bind(chat: Chat) {
when (binding) {
is MyChatItemBinding -> binding.chat = chat
is TheirChatItemBinding -> binding.chat = chat
}
binding.executePendingBindings()
}
}
}
ViewModel
class ChatViewModel #Inject constructor(private val repository: ChatRepository) : BaseViewModel() {
lateinit var userName: String
val message = MutableLiveData<String>()
private val _chats = MutableLiveData<List<Chat>>().apply { value = emptyList() }
val chats: LiveData<List<Chat>> = _chats
fun setup(userName: String?) {
if (userName == null) return
Log.d("Chat", "Username is: $userName")
this.userName = userName
getChats()
}
fun onSendClick() {
if (userName.isEmpty() && message.value.toString().isEmpty()) return
val chat = Chat(userName = userName, message = message.value.toString())
repository.addMessage(chat).addOnSuccessListener {
Log.d("Chat", "Added new message!")
}.addOnFailureListener {
Log.e("Chat", "Failed adding message")
}
}
private fun getChats() {
repository.getMessages().orderBy("timestamp", Query.Direction.ASCENDING)
.addSnapshotListener { value, e ->
if (value?.documents?.size == chats.value?.size) return#addSnapshotListener
if (e != null) {
Log.e("Chat", "Listen Failed!", e)
return#addSnapshotListener
}
val tempChats = mutableListOf<Chat>()
for (doc in value?.documents!!) {
val chat = Chat.fromDocumentSnapshot(doc)
chat.chatType = if (this.userName == chat.userName) ChatType.mine
else ChatType.theirs
tempChats.add(chat)
}
_chats.value = tempChats
}
}
}
Fragment
private fun initRecyclerView() {
val adapter = ChatAdapter()
binding.rv.adapter = adapter
viewModel.chats.observe(viewLifecycleOwner, Observer {
Log.d("ChatFragment", "*************")
adapter.submitList(it.toList())
})
}

I was facing the same issue, DiffUtil.ItemCallback was being called twice, and the RecyclerView was blinking on deleting an item, to fix this i had to override getItemId like this.
override fun getItemId(adapterPosition: Int): Long {
return getItem(adapterPosition).getId()
}

Related

How to use pagination in Android

In my application I want create chat application and for show messages list I used PagingLibrary (version 3) for RecyclerView.
I can show messages and add new item, but when added new item replace this new item with previous item. For example, this is messages list:
Hi
How are you?
I'm android developer.
You?
When send new message for example "My job is ...", replace this item instead of "You?" Such as below:
Hi
How are you?
I'm android developer.
My job is ...
But should show such as below:
Hi
How are you?
I'm android developer.
You?
My job is ...
My adapter codes:
class ChatAdapter #Inject constructor() : PagingDataAdapter<Result, ChatAdapter.ViewHolder>(differCallback) {
private lateinit var binding: ItemChatBinding
private val userToken by lazy { GoodPrefs.getInstance().getString(USER_TOKEN, "") }
private lateinit var context: Context
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
binding = ItemChatBinding.inflate(LayoutInflater.from(parent.context), parent, false)
context = parent.context
return ViewHolder()
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position)!!)
//holder.setIsRecyclable(false)
}
inner class ViewHolder : RecyclerView.ViewHolder(binding.root) {
val root = itemView.findViewById(R.id.root) as ConstraintLayout
private val meTxt = itemView.findViewById(R.id.meTxt) as TextView
private val doctorTxt = itemView.findViewById(R.id.doctorTxt) as TextView
#SuppressLint("SetTextI18n")
fun bind(item: Result) {
//Message
if (item.message != null) {
meTxt.text = item.message
meTxt.isVisible = true
doctorTxt.text = item.message
doctorTxt.isVisible = true
} else {
meTxt.isVisible = false
doctorTxt.isVisible = false
}
}
}
fun addNewItem(position: Int, item: Result) {
snapshot()[position]?.message = item.message
notifyItemChanged(position)
}
companion object {
private val differCallback = object : DiffUtil.ItemCallback<Result>() {
override fun areItemsTheSame(oldItem: Result, newItem: Result): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Result, newItem: Result): Boolean {
return oldItem == newItem
}
}
}
}
Fragment codes:
//RecyclerView
chatLayoutManager.reverseLayout = true
chatList.apply {
layoutManager = chatLayoutManager
setHasFixedSize(true)
itemAnimator = null
adapter = chatAdapter.withLoadStateHeaderAndFooter(
header = LoadMoreAdapter { chatAdapter.retry() },
footer = LoadMoreAdapter { chatAdapter.retry() }
)
}
pusherChannel.bind(PUSHER_EVENT_NAME, object : PrivateChannelEventListener {
#SuppressLint("NotifyDataSetChanged")
override fun onEvent(event: PusherEvent) {
val newChat = ResponseChatList.Data.Result(
message = pusherGsonData.message, deliveredAt = pusherGsonData.deliveredAt,
toUserId = toUserData, chatFile = chatFile
)
//Update UI
runOnUiThread {
chatAdapter.addNewItem(chatDataList.indices.first, newChat)
chatList.smoothScrollToPosition(chatDataList.indices.first)
}
How can I fix this problem?

holder.bindingAdapterPosition returns -1

binding adapter position is returning -1 and i cannot figure out why, anyone has an idea of what i could be doing wrong ?
holder.bindingAdapterPosition in onCreateViewHolder.
Adapter and viewHolder
class CommentAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var likeClick: LikeClick? = null
private var disLikeClick: DisLikeClick? = null
inner class CommentHolder(view: View) : RecyclerView.ViewHolder(view) {
val nicknameTextView: TextView = itemView.findViewById(R.id.nickname_text_view)
val commentTextView: TextView = itemView.findViewById(R.id.comment_text_view)
val likeCount: TextView = itemView.findViewById(R.id.like_count_text_view)
val disLikeCount: TextView = itemView.findViewById(R.id.dislike_count_text_view)
val likeBtn: ImageButton = itemView.findViewById(R.id.like_image_btn)
val disLikeBtn: ImageButton = itemView.findViewById(R.id.disLike_Image_btn)
}
fun setLikeClickListener(clickListener: LikeClick) {
likeClick = clickListener
}
fun setDisLikeClickListener(clickListener: DisLikeClick) {
disLikeClick = clickListener
}
private val diffCallback = object : DiffUtil.ItemCallback<Comment>() {
override fun areItemsTheSame(oldItem: Comment, newItem: Comment): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Comment, newItem: Comment): Boolean {
return oldItem.id == newItem.id
}
}
private val diff = AsyncListDiffer(this, diffCallback)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item_comment, parent, false)
val holder: RecyclerView.ViewHolder = CommentHolder(view)
//set on click listener for like and dislike
Log.d("TESTLOG",diff.currentList.toString())
//TODO FIX
val commentItem = diff.currentList[holder.bindingAdapterPosition]
val likeBtn=view.findViewById<ImageButton>(R.id.like_image_btn)
val dislikeBtn=view.findViewById<ImageButton>(R.id.disLike_Image_btn)
likeBtn.setOnClickListener {
likeClick?.onLikeClick(commentItem.id,likeBtn)
}
dislikeBtn.setOnClickListener {
disLikeClick?.onDisLikeClick(commentItem.id,dislikeBtn)
}
return holder
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val commentItem = diff.currentList[position]
val videoHolder = holder as CommentHolder
videoHolder.apply {
commentTextView.text = commentItem.comment
nicknameTextView.text = commentItem.nickname
likeCount.text = commentItem.commentlike.toString()
disLikeCount.text = commentItem.dislike.toString()
//check if user already liked/disliked this comment and change icon based on that
Utils.likes.forEach {
if(it == commentItem.id){
likeBtn.isSelected=true
}
}
Utils.disLikes.forEach {
if(it==commentItem.id){
disLikeBtn.isSelected=true
}
}
}
}
override fun getItemCount(): Int {
return diff.currentList.size
}
fun submitList(list: List<Comment>) {
diff.submitList(list)
}
interface LikeClick {
fun onLikeClick(commentId: Int, likeBtn:ImageButton)
}
interface DisLikeClick {
fun onDisLikeClick(commentId: Int, disLikeBtn:ImageButton)
}
}
Log.d in oncreateViewHolder
Shows what diff.currentlist contains.
D/TESTLOG: [Comment(comment=firstcomment
, commentlike=0, createdAt=2022-07-06T04:35:43.000Z, dislike=0, id=4, nickname=Alpac, videoId=170c3d0c-331f-4da9-974b-39703940f819)]
Where i set the adapter
i submit a list of comments to adapter, set like and dislike listeners then apply adapter to recyclerView
ChikiCommentsFetcher().fetchCommentsOfAVideo(videoId).observe(viewLifecycleOwner){
commentsRecyclerView.apply {
commentAdapter.submitList(it)
commentAdapter.setLikeClickListener(this#CommentsFragment)
commentAdapter.setDisLikeClickListener(this#CommentsFragment)
adapter=commentAdapter
}
}
Holder positions should be handled inside the onBindViewHolder method.
Also, move your clicks to the onBindViewHolder method.
holder.likeBtn.setOnClickListener {
likeClick?.onLikeClick(commentItem.id,likeBtn)
}
holder.dislikeBtn.setOnClickListener {
disLikeClick?.onDisLikeClick(commentItem.id,dislikeBtn)
}
check out this RecyclerView Tutorial

How To send request with different body to an Api with paging3 kotlin

Hi I have a function inside my viewModel which make a pager and return a flow:
fun loadLastMoviesList(genreId: Int) = Pager(config = PagingConfig(10)) {
LastMoviesPaging(repository, genreId)
}.flow.cachedIn(viewModelScope)
and a click listener in my HomeFragment which whenever user click it execute the function with different genreId:
genresAdapter.setOnItemClickListener { genre, name ->
lastMoviesTitle.text = "$name Movies"
genre.id?.let { id ->
lifecycleScope.launchWhenCreated {
viewModel.loadLastMoviesList(id).collect {
lastMoviesAdapter.submitData(it)
}
}
}
}
my problem is that when I click on an Item of recyclerView and it send new request with new body
the new itm's appear on the top of last item in recyclerView I want to update the new list with the last list in the recycler view when the genreId change how can I do it??
Adapter:
class LastMoviesAdapter #Inject constructor(): PagingDataAdapter<Data, LastMoviesAdapter.ViewHolder>(differCallBack) {
private lateinit var binding: ItemHomeMoviesLastBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LastMoviesAdapter.ViewHolder {
binding = ItemHomeMoviesLastBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder()
}
override fun onBindViewHolder(holder: LastMoviesAdapter.ViewHolder, position: Int) {
getItem(position)?.let { holder.setData(it) }
holder.setIsRecyclable(false)
}
inner class ViewHolder: RecyclerView.ViewHolder(binding.root){
fun setData(item: Data){
binding.apply {
movieNameTxt.text = item.title
movieRateTxt.text = item.imdbRating
movieCountryTxt.text = item.country
movieYearTxt.text = item.year
moviePosterImg.load(item.poster){
crossfade(true)
crossfade(800)
}
root.setOnClickListener{
onItemClickListener?.let {
it(item)
}
}
}
}
}
private var onItemClickListener: ((Data) -> Unit)? = null
fun setOnItemClickListener(listener: (Data) -> Unit){
onItemClickListener = listener
}
companion object{
val differCallBack = object : DiffUtil.ItemCallback<Data>(){
override fun areItemsTheSame(oldItem: Data, newItem: Data): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Data, newItem: Data): Boolean {
return oldItem == newItem
}
}
}
pagingSourceClase:
class LastMoviesPaging (private val repository: HomeRepository, private val genreId: Int): PagingSource<Int, Data>() {
override fun getRefreshKey(state: PagingState<Int, Data>): Int? {
return null
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Data> {
return try {
val currentPage = params.key ?: 1
Log.d("paging3", "paging running genre: $genreId")
val responseData = repository.lastMoviesList(genreId, currentPage).run {
body()?.data?.toString()?.let { Log.d("paging3", it) }
body()?.data ?: emptyList()
}
LoadResult.Page(responseData, if (currentPage == 1) null else -1 , if (responseData.isEmpty()) null else currentPage + 1)
}catch (e: Exception){
LoadResult.Error(e)
}
}
and I also want to save the flow in viewModel instead of resiving it directly in the HomeFragment
so any one can suggest a solution for my problem? I will provide more information if needed

Connect viewmodel class to adapter(recyclerview)

I have viewModel class which name UserListViewModel And on that class there is function which named sumUserIncrease and I want to get that functions value and send that to adapter to show in recyclerview I hope you got what I mean if not, take look at this:
here is my userListViewModel:
class UserListViewModel(
val mUserInfoDAO: UserDAO,
val mTransactionDAO: TransactionsDAO,
val mLoanDAO: LoanDAO,
val mBankDAO: BankDAO,
application: Application
) :
AndroidViewModel(application) {
var viewModelJob = Job()
val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
fun sumAllIncrease(id: Long): Long {
return mTransactionDAO.sumUserIncrease(id)
}
fun sumAllDecrease(id: Long): Long {
return mTransactionDAO.sumUserDecrease(id)
}
}
my Adapter:
package com.example.holyquran.ui.userList
class UserAdapter() : ListAdapter<UserInfo, RecyclerView.ViewHolder>(BillDiffCallback()) {
private val ITEM_VIEW_TYPE_EMPTY = 0
private val ITEM_VIEW_TYPE_ITEM = 1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_VIEW_TYPE_ITEM -> ViewHolder.from(parent)
ITEM_VIEW_TYPE_EMPTY -> EmptyViewHolder.from(parent)
else -> throw ClassCastException("Unknown viewType $viewType")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ViewHolder -> {
val item = getItem(position)
holder.bind(item, clickListener)
}
is EmptyViewHolder -> {
holder.bind()
}
}
}
lateinit var clickListener: AdapterListener
fun setOnclickListener(listener: AdapterListener) {
clickListener = listener
}
override fun getItemViewType(position: Int): Int {
return if (itemCount > 0)
ITEM_VIEW_TYPE_ITEM
else
ITEM_VIEW_TYPE_EMPTY
}
class ViewHolder private constructor(val binding: ItemUserListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: UserInfo, adapterListener: AdapterListener) {
if (item.gender == "مرد") {
binding.img.setImageResource(R.drawable.user_avata_male);
}else{
binding.img.setImageResource(R.drawable.user_avatar_female);
}
binding.userInfo = item
binding.clickListener = adapterListener
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemUserListBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
class EmptyViewHolder private constructor(val binding: ItemUserListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind() {
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): EmptyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemUserListBinding.inflate(layoutInflater, parent, false)
return EmptyViewHolder(binding)
}
}
}
}
class BillDiffCallback : DiffUtil.ItemCallback<UserInfo>() {
override fun areItemsTheSame(oldItem: UserInfo, newItem: UserInfo): Boolean {
return oldItem.userId == newItem.userId
}
override fun areContentsTheSame(
oldItem: UserInfo,
newItem: UserInfo
): Boolean {
return oldItem == newItem
}
}
class AdapterListener(
val clickListener: (id: Long) -> Unit,
val deleteListener: (userInfo: UserInfo) -> Unit,
private val longClickListener: (id: Long) -> Unit
) {
fun onclick(userInfo: UserInfo) = clickListener(userInfo.userId)
fun onDeleteClick(userInfo: UserInfo) = deleteListener(userInfo)
fun onLongClick(userInfo: UserInfo) = longClickListener(userInfo.userId)
}
Have your UserListViewModel contain LiveData that your Fragment/Activity observes. Once it gets an update it will send it to the Adapter.
In ViewModel
private val currentSum : MutableLiveData<Int> = MutableLiveData(0)
fun sumAllIncrease(id: Long): Long {
var sum = mTransactionDAO.sumUserIncrease(id)
currentSum.value = sum
return sum
}
fun sumAllDecrease(id: Long): Long {
var sum = mTransactionDAO.sumUserDecrease(id)
currentSum.value = sum
return sum
}
fun getCurrentSum(): LiveData<Long> {
return currentSum
}
In Fragment/Activity
viewModel.getCurrentSum().observe(this, Observer {
adapter.setSum(it)
})
In Adapter
fun setSum(sum : Long){
//The sum is now in you adapter.
//Use it how you need too.
}
But this is kinda unusual. What you really want to do is pass a new UserInfo with the changed sum. And let the DiffCallback change the one that is different. What you probably want to do is implement Paging https://proandroiddev.com/paging-3-easier-way-to-pagination-part-1-584cad1f4f61 This will allow you to change stuff in the database anywhere in the app. When that happens the data will be updated in the Adapter. Its kinda complex but once you get it working you want regret it.

Having 2 model class(dataClass) in adapter(ListAdapter)

Recently, I was faced with an issue that is a bit hard for me to solve
I need to put 2 models in the List adapter, but it always says 2 types of arguments expected
Here is the Link of the whole project
and I want to have Transaction and Bank models in ListAdapter
you can read the project README.
The adapter that I want to have 2 models is in ui.TransactionHistory
here is my whole adapter class:
class TransactionHistory() :
ListAdapter<Transaction, RecyclerView.ViewHolder>(BillDiffCallback()) {
private val ITEM_VIEW_TYPE_EMPTY = 0
private val ITEM_VIEW_TYPE_ITEM = 1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_VIEW_TYPE_ITEM -> ViewHolder.from(parent)
ITEM_VIEW_TYPE_EMPTY -> EmptyViewHolder.from(parent)
else -> throw ClassCastException("Unknown viewType $viewType")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ViewHolder -> {
val item = getItem(position)
holder.bind(item, clickListener)
// holder.bind2(Bank, clickListener)
}
is EmptyViewHolder -> {
holder.bind()
}
}
}
lateinit var clickListener: AdapterListener2
fun setOnclickListener(listener: AdapterListener2) {
clickListener = listener
}
override fun getItemViewType(position: Int): Int {
return if (itemCount > 0)
ITEM_VIEW_TYPE_ITEM
else
ITEM_VIEW_TYPE_EMPTY
}
class ViewHolder
private constructor(val binding: ItemUserTransactionListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Transaction, adapterListener2: AdapterListener2) {
binding.transaction = item
binding.clickListener = adapterListener2
binding.executePendingBindings()
if (item.type == "payPayment") {
binding.transactionStatus.text = "برداخت قسط"
} else if (item.type == "decrease") {
binding.transactionStatus.text = "برداشت"
} else if (item.type == "increase") {
binding.transactionStatus.text = "واریز"
}
if (item.decrease == null) {
binding.amount.text = item.increase
} else {
binding.amount.text = item.decrease
}
}
fun bind2(item2: Bank, adapterListener2: AdapterListener2) {
binding.bankInfo = item2
binding.clickListener = adapterListener2
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemUserTransactionListBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
class EmptyViewHolder private constructor(val binding: ItemUserTransactionListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind() {
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): EmptyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemUserTransactionListBinding.inflate(layoutInflater, parent, false)
return EmptyViewHolder(binding)
}
}
}
class BillDiffCallback : DiffUtil.ItemCallback<Transaction>() {
override fun areItemsTheSame(oldItem: Transaction, newItem: Transaction): Boolean {
return oldItem.transId == newItem.transId
}
override fun areContentsTheSame(
oldItem: Transaction,
newItem: Transaction
): Boolean {
return oldItem == newItem
}
}
class AdapterListener2(
val clickListener: (id: Long?) -> Unit,
val deleteListener: (category: Transaction) -> Unit
) {
fun onclick(transaction: Transaction) = clickListener(transaction.userId)
fun onDeleteClick(userInfo: Transaction) = deleteListener(userInfo)
}
whenever i placed secound model here:
ListAdapter<Transaction, **Bank**, RecyclerView.ViewHolder>(BillDiffCallback()) {}
it says 2 type argument expected.
I don't know if it helps you or not but some one told me I have to use join in Kotlin
THANKS FOR YOUR ANSWERS :)
ListAdaper can only accept one data model however you can add multiple items using another class like Sealed Class
sealed class DataItem {
abstract val id: Long
data class TransactionItem(val transaction: Transaction): DataItem() {
override val id = transaction.transId
}
object Empty: DataItem() {
override val id = Long.MIN_VALUE
}
}
and deal with this only class in your ListAdapter for your code you will need to apply this changes
class TransactionHistory() :
ListAdapter<DataItem, RecyclerView.ViewHolder>(BillDiffCallback()) {
private val ITEM_VIEW_TYPE_EMPTY = 0
private val ITEM_VIEW_TYPE_ITEM_TRANSACTION = 1
private val ITEM_VIEW_TYPE_ITEM_BANK = 2
private val adapterScope = CoroutineScope(Dispatchers.Default)
/**
* DO NOT USE .submit(), use the method bellow
*/
fun addTransactionsAndBanks(transactionList: List<Transaction>?, bankList: List<Bank>?) {
adapterScope.launch {
val transactionItems: List<DataItem> = when {
transactionList == null || transactionList.isEmpty() -> listOf(DataItem.Empty)
else -> transactionList.map { DataItem.TransactionItem(it) }
}
val bankItems: List<DataItem> = when {
bankList == null || bankList.isEmpty() -> listOf(DataItem.Empty)
else -> bankList.map { DataItem.BankItem(it) }
}
val items = transactionItems + bankItems
withContext(Dispatchers.Main) {
submitList(items)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_VIEW_TYPE_ITEM_TRANSACTION -> ViewHolder.from(parent)
ITEM_VIEW_TYPE_ITEM_BANK -> ViewHolder.from(parent)
ITEM_VIEW_TYPE_EMPTY -> EmptyViewHolder.from(parent)
else -> throw ClassCastException("Unknown viewType $viewType")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ViewHolder -> {
when (val item = getItem(position)) {
is DataItem.TransactionItem -> holder.bind(item.transaction, clickListener)
is DataItem.BankItem -> holder.bind2(item.bank, clickListener)
}
}
is EmptyViewHolder -> holder.bind()
}
}
lateinit var clickListener: AdapterListener2
fun setOnclickListener(listener: AdapterListener2) {
clickListener = listener
}
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is DataItem.Empty -> ITEM_VIEW_TYPE_EMPTY
is DataItem.TransactionItem -> ITEM_VIEW_TYPE_ITEM_TRANSACTION
is DataItem.BankItem -> ITEM_VIEW_TYPE_ITEM_BANK
}
}
class ViewHolder
private constructor(val binding: ItemUserTransactionListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Transaction, adapterListener2: AdapterListener2) {
binding.transaction = item
binding.clickListener = adapterListener2
binding.executePendingBindings()
if (item.type == "payPayment") {
binding.transactionStatus.text = "برداخت قسط"
} else if (item.type == "decrease") {
binding.transactionStatus.text = "برداشت"
} else if (item.type == "increase") {
binding.transactionStatus.text = "واریز"
}
if (item.decrease == null) {
binding.amount.text = item.increase
} else {
binding.amount.text = item.decrease
}
}
fun bind2(item2: Bank, adapterListener2: AdapterListener2) {
binding.bankInfo = item2
binding.clickListener = adapterListener2
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemUserTransactionListBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
class EmptyViewHolder private constructor(val binding: ItemUserTransactionListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind() {
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): EmptyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemUserTransactionListBinding.inflate(layoutInflater, parent, false)
return EmptyViewHolder(binding)
}
}
}
class BillDiffCallback : DiffUtil.ItemCallback<DataItem>() {
override fun areItemsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
return oldItem == newItem
}
}
class AdapterListener2(
val clickListener: (id: Long?) -> Unit,
val deleteListener: (category: Transaction) -> Unit
) {
fun onclick(transaction: Transaction) = clickListener(transaction.userId)
fun onDeleteClick(userInfo: Transaction) = deleteListener(userInfo)
}
sealed class DataItem {
abstract val id: Long
data class TransactionItem(val transaction: Transaction) : DataItem() {
override val id = transaction.transId
}
data class BankItem(val bank: Bank) : DataItem() {
override val id = bank.bankId
}
object Empty : DataItem() {
override val id = Long.MIN_VALUE
}
}
You can build the list based on only a single data source. If you want to have multiple data sources, you should create a third data model and add the other two models in it.
data class ListData(val transaction : Transaction , val Bank : Bank)
ListView/Recycler view will create number of list items based on the list of objects passed.
ListAdapter<ListData, RecyclerView.ViewHolder>
I found the answer to my Question
We have to use Join in query:
//for single info
#Query("SELECT `transaction`.increase, `transaction`.decrease, bank.bank_name From `transaction` JOIN bank WHERE `transaction`.bank_id=:key ")
fun joinTables(key: Long): LiveData<TransactionAndBank>?
//for list of info
#Query("SELECT `transaction`.increase, `transaction`.decrease,`transaction`.type, bank.bank_name From `transaction` JOIN bank WHERE `transaction`.bank_id=:key ")
fun joinAllTables(key: Long): LiveData<List<TransactionAndBank>>
I had to place it In TransactionDAO
source

Categories

Resources