好的,那接下來我們就要來顯示我們的資料啦!! 由於我們的資料會有一個是對方傳過來的,一個是我們自己發送過去的,而時間的先後順序Realtime那邊會按照先後順序幫我們排序起來,所以我們不用去管它。
既然提到了recyclerview,我們當然要有item_view_list阿,而且因為我們這次會有一個是對方傳過來的,一個是我們傳過去的(簡單分法就是一個在左,一個在右),所以我們會建立兩個item_view_list。
簡單明瞭,帶點line的風格
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="message"
type="com.example.petsmatchingapp.model.Message" />
</data>
<RelativeLayout
android:padding="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.example.petsmatchingapp.utils.JFTextView
android:id="@+id/tv_item_message_me_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toStartOf="@id/tv_item_message_me_message"
android:layout_alignBottom="@id/tv_item_message_me_message"
android:layout_marginEnd="5dp"
android:textSize="12sp"
tools:text = "13:00">
</com.example.petsmatchingapp.utils.JFTextView>
<com.example.petsmatchingapp.utils.JFTextView
android:id="@+id/tv_item_message_me_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{message.message}"
android:layout_alignParentEnd="true"
android:padding="5dp"
android:textSize="16sp"
android:maxWidth="250dp"
android:background="@drawable/message_background_me"
tools:text = "你今天吃飽了嗎?">
</com.example.petsmatchingapp.utils.JFTextView>
</RelativeLayout>
</layout>
這次我們用RelativeLayout的原因是因為,我原本用constraintlayout,就是不能從右邊排版,所以我們這次用RelativeLayout的android:layout_alignParentEnd="true"
也要新增我們message的background喔
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
android:color="@color/message_me_color">
</solid>
<corners
android:radius="20dp">
</corners>
</shape>
綠色顏色代碼:#78e37a
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="message"
type="com.example.petsmatchingapp.model.Message" />
</data>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp">
<ImageView
android:id="@+id/iv_item_message_other_image"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentStart="true"
tools:src = "@drawable/icon_dog"/>
<com.example.petsmatchingapp.utils.JFTextView
android:id="@+id/item_message_other_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@{message.message}"
android:padding="5dp"
android:maxWidth="250dp"
android:textSize="16sp"
android:layout_toEndOf="@id/iv_item_message_other_image"
android:layout_alignTop="@id/iv_item_message_other_image"
android:background="@drawable/message_backgorund_other"
tools:text = "今天我吃飽了喔,我想應該也還沒吃飽啦"/>
<com.example.petsmatchingapp.utils.JFTextView
android:id="@+id/item_message_other_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/item_message_other_message"
android:layout_alignBottom="@id/item_message_other_message"
android:layout_marginStart="5dp"
tools:text="03:05 pm"
android:textSize="12sp"
app:layout_constraintStart_toEndOf="@id/item_message_other_message">
</com.example.petsmatchingapp.utils.JFTextView>
</RelativeLayout>
</layout>
background只是把剛剛的綠色改成白色
跟以往的adapter不一樣,我們這次因為有兩個item_view_list,所以我們這次會在adapter來判斷,而判斷的依據就是這個message的資料的發送者是不是現在的使用者
直接在Adapter的class新增以下,讓我們可以判斷要用哪一個viewHolder
private val MESSAGE_TYPE_LEFT = 0
private val MESSAGE_TYPE_RIGHT = 1
private var firebaseUser: FirebaseUser? = null
再過來就是override以下,來判斷是否是現在登入的user
override fun getItemViewType(position: Int): Int {
firebaseUser = FirebaseAuth.getInstance().currentUser
if (firebaseUser?.uid == getItem(position).send_user_id){
return MESSAGE_TYPE_RIGHT
}else{
return MESSAGE_TYPE_LEFT
}
}
再過來要新增兩個viewHolder,一個是我們傳過去的,一個是別人傳過來的
首先先做我們傳出去的
class MyMessageViewHolder(val binding: MessageItemListMeBinding):RecyclerView.ViewHolder(binding.root){
//這個funtion是我們要轉換時間的
fun getDate(timestamp: Any): String{
//我們只要小時跟分鐘就好
val mFormat = "HH:mm"
val sdf = SimpleDateFormat(mFormat, Locale.getDefault())
return sdf.format(timestamp)
}
//一樣綁定資料,只有時間因為需要轉換,所以我們寫在adapter
fun bind(item:Message){
binding.message = item
item.time?.let {
binding.tvItemMessageMeTime.text = getDate(it)
}
binding.executePendingBindings()
}
}
別人傳過來的一樣
class OtherMessageViewHolder(val binding: MessageListItemOtherBinding): RecyclerView.ViewHolder(binding.root){
fun getDate(timestamp: Any): String{
val mFormat = "HH:mm"
val sdf = SimpleDateFormat(mFormat, Locale.getDefault())
return sdf.format(timestamp)
}
fun bind(item: Message){
binding.message = item
binding.itemMessageOtherTime.setText(getDate(item.time!!))
item.send_user_image?.let { Constant.loadUserImage(it,binding.ivItemMessageOtherImage) }
binding.executePendingBindings()
}
}
再過來就是要在onCreateViewHolder來回傳不同的viewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if(viewType == MESSAGE_TYPE_RIGHT){
return MyMessageViewHolder(MessageItemListMeBinding.inflate(LayoutInflater.from(parent.context)))
}else{
return OtherMessageViewHolder(MessageListItemOtherBinding.inflate(LayoutInflater.from(parent.context)))
}
}
然後綁定viewHolder
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val model = getItem(position)
when(holder){
is MyMessageViewHolder ->{
holder.bind(model)
}
is OtherMessageViewHolder -> {
holder.bind(model)
}
}
}
回到ChatRoomFragment,來setAdapter
private fun setAdapter(){
chatAdapter = ChatRoomAdapter()
binding.rvChatRoom.adapter = chatAdapter
val linerLayout = LinearLayoutManager(requireContext())
//讓我們的list從尾開始生成
linerLayout.reverseLayout = true
binding.rvChatRoom.layoutManager = linerLayout
}
接下來我們會讀取數據,我們可以透過設定addValueEventListener來幫助我們當數據有更改的時候,我們就拿出所有數據
首先我們先去ChatViewModle新增
private val _messageList = MutableLiveData<List<Message>>()
val messageList: LiveData<List<Message>>
get() = _messageList
fun messageValueListener(fragment: ChatRoomFragment, currentUID: String, invitationUID: String){
val ref = FirebaseDatabase.getInstance().reference.child(currentUID).child(invitationUID)
ref.addValueEventListener(object : ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
val list = mutableListOf<Message>()
for (i in snapshot.children){
val message = i.getValue(Message::class.java)
if (message != null) {
list.add(message)
}
}
_messageList.postValue(list.reversed())
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
我們要加入我們當前使用者的userId,以及要聊天的user_id,因為我們的所有聊天內容,不管是我傳給別人,或是別人傳給我,都會同時儲存在A→B,B→A的資料。
而onDataChagne,它的DataSnapshot是會回傳所有的資料,而不是只有新的資料,所以我們要把它全部加入到livedata,並且顯示到recyclerview。
而如果只是想要觀察child的資料呢,如你想要某個新增的資料,而不是全部都回傳給你,addChildEventListener是你的好選擇,裡面可以override以下的funtion,你可以看是要觀察新增的還是刪除的..
ref.addChildEventListener(object : ChildEventListener{
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
val model = snapshot.getValue(Message::class.java)
Timber.d("child model : $model")
}
override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
val model = snapshot.getValue(Message::class.java)
Timber.d("child model chagne: $model")
}
override fun onChildRemoved(snapshot: DataSnapshot) {
}
override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
}
override fun onCancelled(error: DatabaseError) {
}
})
而因為我們這次是要丟入到livedata,所以我們要所有資料,所以我們就選擇addValueEventListener
再過來就是在Fragment呼叫它囉。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
chatViewModel.messageValueListener(this,accountViewModel.userDetail.value!!.id,matchingViewModel.selectedInvitation.value!!.user_id)
super.onViewCreated(view, savedInstanceState)
}
如果我們希望使用者在沒有網路狀態的時候,依舊可以看到聊天訊息呢? 總是怕如果再沒有網路的情況,竟然也忘記邀約的時間?
我們直接在 MyApp.kt的onCreated新增以下,就好啦!
Firebase.database.setPersistenceEnabled(true)
它會緩存聊天紀錄,而且也可以實現當你今天離線寫入的時候,別怕,當你恢復網路的時候就會自動幫你寫入~
我們希望我們的chatRoom上面顯示我們的聊天對象名稱,所以我們要把name放入Invitation
data class Invitation(
val id: String = "",
val user_id: String = "",
//新增這個
val user_name: String? = "",
val pet_image: String = "",
val pet_type: String = "",
val pet_type_description: String = "",
val area: String = "",
val date_place: String = "",
val date_time: Timestamp? = null,
val note: String = "",
val update_time: Timestamp? = null ,
)
並且回到AddInvitationFragment的onClick裡面新增
user_name = accountViewModel.userDetail.value?.name,
最後回到ChatRoomFragment來新增以下,把它顯示出來就好啦
matchingViewModel.selectedInvitation.value?.user_name?.let {
binding.tvChatRoomAcceptUserName.text = it
}