0

Hey I am working on chat application. I completed my incoming and outgoing message from How to use different layouts for incoming & outgoing chat messages. Also I tried this How to show date in between conversation in recyclerview or in listview. I added output how my all looks like.

ConversationAdapter.kt

class ConversationAdapter : ListAdapter<Message, RecyclerView.ViewHolder>(MESSAGE_COMPARATOR) {

    companion object {
        private val MESSAGE_COMPARATOR = object : DiffUtil.ItemCallback<Message>() {
            override fun areItemsTheSame(oldItem: Message, newItem: Message): Boolean {
                return oldItem.id == newItem.id
            }

            override fun areContentsTheSame(oldItem: Message, newItem: Message): Boolean {
                return ((oldItem.name == newItem.name) && (oldItem.text == oldItem.text)
                        && (oldItem.time == newItem.time) && (oldItem.type == newItem.type))
            }

        }

        private const val INCOMING_MESSAGE = 1
        private const val OUTGOING_MESSAGE = 2
        private const val TIMING = 3
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            INCOMING_MESSAGE -> {
                IncomingViewHolder(
                    IncomingLayoutBinding.inflate(
                        LayoutInflater.from(parent.context),
                        parent,
                        false
                    )
                )
            }
            OUTGOING_MESSAGE -> {
                OutGoingViewHolder(
                    OutgoingLayoutBinding.inflate(
                        LayoutInflater.from(parent.context),
                        parent,
                        false
                    )
                )
            }
            else -> {
                TimingViewHolder(
                    TimingLayoutBinding.inflate(
                        LayoutInflater.from(parent.context),
                        parent,
                        false
                    )
                )
            }
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is IncomingViewHolder -> {
                holder.bindItem(getItem(position))
            }
            is OutGoingViewHolder -> {
                holder.bindItem(getItem(position))
            }
            is TimingViewHolder -> {
                holder.bindItem(getItem(position))
            }
        }
    }

    override fun getItemViewType(position: Int): Int {
        return when {
            getItem(position).type.equals("incoming") -> {
                INCOMING_MESSAGE
            }
            getItem(position).type.equals("outgoing") -> {
                OUTGOING_MESSAGE
            }
            getItem(position).type.equals("added") -> {
                TIMING
            }
            else -> {
                super.getItemViewType(position)
            }
        }
    }


    inner class IncomingViewHolder(val binding: IncomingLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bindItem(item: Message) {
            binding.incomingMessage.text = item.text
        }
    }

    inner class OutGoingViewHolder(val binding: OutgoingLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bindItem(item: Message) {
            binding.outGoingMessage.text = item.text
        }
    }

    inner class TimingViewHolder(val binding: TimingLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bindItem(item: Message) {
            binding.timing.text = item.time
        }
    }
}

MM.kt

data class Message(
    val id: String? = null,
    val text: String? = null,
    val time: String? = null,
    val type: String? = null,
    val name: String? = null
)

MainActivity.kt

class MainActivity : BaseActivity() {
    
        lateinit var binding: MainLayoutBinding
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = MainLayoutBinding.inflate(layoutInflater)
            setContentView(binding.root)
            setupAdapter()
        }
    
        private fun setupAdapter() {
     val list = listOf<Message>(
            Message(id = "1", text = "avc", time = "2021-10-08T15:34:00", type = "outgoing"),
            Message(id = "2", text = "dsds", time = "2021-10-08T15:36:00", type = "incoming"),
            Message(id = "3", type = "added", time = "2021-10-08T00:00:00", name = "ABC"),
            Message(id = "4", type = "added", time = "2021-10-07T15:50:00", name = "XYZ"),
            Message(id = "5", text = "asvc", time = "2021-10-07T15:46:00", type = "outgoing"),
            Message(id = "6", text = "asvc", time = "2021-10-07T15:46:00", type = "incoming"),
            Message(id = "6", text = "asvc", time = "2021-10-06T12:34:00", type = "outgoing"),
            Message(id = "8", text = "asvc", time = "2021-10-06T12:50:00", type = "incoming"),
            Message(id = "9", type = "added", time = "2021-10-06T12:46:00", name = "DEF")
        )
        val adapter = ConversationAdapter()
        binding.conversationRecyclerView.adapter = adapter
        adapter.submitList(list)
    
        }
    }

timing_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:background="@color/aqua"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/timing"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

incoming_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:background="@color/red"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/incomingMessage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

outgoing_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:background="@color/yellow"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/outGoingMessage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Output

enter image description here

How to achieve other functionality.

1. How to category my date to show dates between conversation of each day in efficient way because my list is too huge big. I just added the sample

2. How to show scroll effect of dates when user scroll chats just like whatsapp have yesterday, today and other dates format.

3. In above list example there is one property added, which means someone added in group chat. In whatsapp XYZ joined using the groups invite link.

I am adding screen shot of whatsapp

enter image description here enter image description here

Kotlin Learner
  • 3,995
  • 6
  • 47
  • 127
  • You can use [getItemViewType](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.Adapter#getItemViewType(int)). [Example in java](https://stackoverflow.com/questions/26245139/how-to-create-recyclerview-with-multiple-view-types) – Kaushik Oct 11 '21 at 04:27
  • @Kaushik I already use getitemViewType but it's not working. Please see my updated question. Thanks – Kotlin Learner Oct 11 '21 at 09:24

2 Answers2

0

Where is the holder of TimingViewHolder?

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is IncomingViewHolder) {
            holder.bindItem(getItem(position))
        } else if (holder is OutGoingViewHolder) {
            holder.bindItem(getItem(position))
        }
    }

[Update]

How to category my date to show dates between conversations of each day inefficient way because my list is too huge big.

Do you want to make the conversation UI like this? the UI

If it is, you can try this approach, for explaining clearly, I need to change your code.

data class MessageEntry // it's from a server, it's raw data

data class Message // it's from MessageEntry

data class MessageEntry(
    val id: String? = null,
    val text: String? = null,
    val time: String? = null,
    val type: String? = null,
    val name: String? = null
)

make sure no nullable field in class Message

data class Message(
    val id: String,
    val text: String,
    val time: Date, // SDK's class
    val type: String, // It's better to use enum
    val name: String
)
//Raw data from Server
val mutableRawList = mutableListOf<MessageEntry>(
            MessageEntry(id = "1", text = "avc", time = "2021-10-08T15:34:00", type = "outgoing"),
            MessageEntry(id = "2", text = "dsds", time = "2021-10-08T15:36:00", type = "incoming"),
            MessageEntry(id = "3", type = "added", time = "2021-10-08T00:00:00", name = "ABC"),
            MessageEntry(id = "4", type = "added", time = "2021-10-07T15:50:00", name = "XYZ"),
            MessageEntry(id = "5", text = "asvc", time = "2021-10-07T15:46:00", type = "outgoing"),
            MessageEntry(id = "6", text = "asvc", time = "2021-10-07T15:46:00", type = "incoming"),
            MessageEntry(id = "6", text = "asvc", time = "2021-10-06T12:34:00", type = "outgoing"),
            MessageEntry(id = "8", text = "asvc", time = "2021-10-06T12:50:00", type = "incoming"),
            MessageEntry(id = "9", type = "added", time = "2021-10-06T12:46:00", name = "DEF")
        )

//I ignore the changing, MessageEntry.toMessage(), for an instance.
val mutableMessageList = mutableListOf<Message>(
            Message(id = "1", text = "avc", time = Date(2021-10-08T15:34:00), type = "outgoing"),
            Message(id = "2", text = "dsds", time = Date(2021-10-08T15:36:00), type = "incoming"),
            Message(id = "3", type = "added", time = Date(2021-10-08T00:00:00), name = "ABC"),
            Message(id = "4", type = "added", time = Date(2021-10-07T15:50:00), name = "XYZ"),
            Message(id = "5", text = "asvc", time = Date(2021-10-07T15:46:00), type = "outgoing"),
            Message(id = "6", text = "asvc", time = Date(2021-10-07T15:46:00), type = "incoming"),
            Message(id = "6", text = "asvc", time = Date(2021-10-06T12:34:00), type = "outgoing"),
            Message(id = "8", text = "asvc", time = Date(2021-10-06T12:50:00), type = "incoming"),
            Message(id = "9", type = "added", time = Date(2021-10-06T12:46:00), name = "DEF")
        )


private var lastMessageDate: Date? = null
...
val adapter = ConversationAdapter()
binding.conversationRecyclerView.adapter = adapter

// Need to change raw list MessageEntry to Message
// TODO, maybe you should change the related code of xxxAdapter, xxxViewHolder...

adapter.submitList(mutableMessageList.toFlatMessageItems)

What's the method of toFlatMessageItems()

//Rebuild the message list from TreeMap<Date, List<Message>>
private fun MutableList<Message>.toFlatMessageItems() =
        mutableListOf<Message>().apply {
            val messageByDate = this@toFlatMessageItems.splitByDate()
            for (time in messageByDate.keys) {
                if (time != lastMessageDate) {
                    val dateHeaderItem = time.toMessage()
                    add(dateHeaderItem)                   
                }
                for (message in messageByDate.getValue(date)) {
                    val messageItem = message
                    add(messageItem)
                }
            }
            lastMessageDate = messageByDate.keys.lastOrNull()
        }

What's the method of splitByDate:

//Split the data by Date
private fun List<Message>.splitByDate() =
        TreeMap<Date, List<Message>>(Collections.reverseOrder()).apply {
            for (message in this@splitByDate) {
                val date = Message.time.noTime() // need you implement noTime() extension function, only the (year, month, day)
                var value = this[date] as MutableList<Message>?
                if (value == null) {
                    value = mutableListOf()
                    this[date] = value
                }

                value.add(message)
            }
        }

Kotlin extension functions

  1. Change MessageEntry to Message
MessageEntry.toMessage() = Message(
    id = id ?: "", // or add an extension fuction to set empty string
    text = text ?: "",
    time = time.toDate(), // extension function from time string to Date, for spliting date
    type = type ?: "",
    name = name ?: ""
)
  1. time(String or Long) to Date
String/Long.toDate(): Date {
 //TODO change string/long time format to Date
}
  1. Date.noTime()
Date.toDate(): Date {
 //TODO, only year, month, day
}
  1. Date.toMessage()
Date.toMessage() = Message(
    id = "",
    text = "",
    time = time, // the type of time is Date
    type = "added",
    name = ""
)

Note:

  1. I didn't test it, just show you an idea.
  2. Using MutableList or List, it's up to you.
  3. Some extension functions need you to finish them.
shrimpcolo
  • 249
  • 2
  • 8
0

even though it's not clear what do you mean by "this is not working in my solution" and I don't know if the problem is a crash or empty list or ... but the logic of checking items in methods areItemsTheSame and areContentsTheSame is wrong : the method areItemsTheSame is used to check if the identity of the old item is equal to the identity of the new item. and the method areContentsTheSame is used to check if values in 2 items are equal

so in areContentsTheSame you have to check only id of items because other things are values of item and what you have written in areContentsTheSame have no meaning beacause you are just checking the reference of the items. so change it to this :

   private val MESSAGE_COMPARATOR = object : DiffUtil.ItemCallback<MM>() {
        override fun areItemsTheSame(oldItem: MM, newItem: MM): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: MM, newItem: MM): Boolean {
   return ((oldItem.added == newItem.added)
                    && (oldItem.addedTime == newItem.addedTime)
                    && (oldItem.sentAt == newItem.sentAt)
                    && (oldItem.text == newItem.text))            }

    }