iT邦幫忙

2021 iThome 鐵人賽

DAY 11
0
Mobile Development

30天建立寵物約散App-Android新手篇系列 第 11

【Day11】HomeFragment X RecyclerView X Firestore取/刪除資料

既然我們都已經有了上傳資料,當然我們也要有可以看我們所有上架內容的地方,還有下架資料的地方啦!! 且我們要讓只有當前登入的人才可以刪除! 不然隨便一個人都可以去刪除,那著實有點監介

1.建立RecyclerView

RecyclerView也就是所謂的動態視圖列表,我們透過它來呈現我們每一筆的約散! 還不太了解RecyclerView的夥伴們,可以參考官方文檔喔! https://developer.android.com/guide/topics/ui/layout/recyclerview?hl=zh-cn

1.1.建立layou,並命名為home_invitation_item_list

主要是給我們Recyclerview的item
我們裡面用到 dataBinding,用法會是

  • xml外面包layout
  • 設定data,裡面variale的type設定我們的dataclass-Invitation
  • 再想要綁定的view用 @{(data>variable>name)} 點出資料
  • 然後之後我們會在adapter把資料餵進來
<?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="invitation"
            type="com.example.petsmatchingapp.model.Invitation" />


    </data>


<androidx.constraintlayout.widget.ConstraintLayout

    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp">


    <LinearLayout
        android:id="@+id/ll_home_invitation_item_list_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="2dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ImageView
            android:id="@+id/iv_home_invitation_item_list_image"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:scaleType="center">

        </ImageView>

    </LinearLayout>


    <LinearLayout
        android:id="@+id/ll_home_invitation_item_list_description"
        android:layout_width="200dp"
        android:orientation="vertical"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:gravity="center"
        android:layout_marginEnd="10dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/iv_home_invitation_item_list_delete"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toEndOf="@id/ll_home_invitation_item_list_image">

        <!--            在你想要指定的view塞入資料-->
        <com.example.petsmatchingapp.utils.JFTextView
            android:id="@+id/tv_home_invitation_item_list_pet_type"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{@string/home_fragment_pet_type(invitation.pet_type,invitation.pet_type_description)}"
            tools:text="博美犬">

        </com.example.petsmatchingapp.utils.JFTextView>

        <com.example.petsmatchingapp.utils.JFTextView
            android:id="@+id/tv_home_invitation_item_list_date_place"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{@string/home_fragment_pet_place(invitation.area,invitation.date_place)}"
            tools:text="新竹縣客家園區">

        </com.example.petsmatchingapp.utils.JFTextView>

        <com.example.petsmatchingapp.utils.JFTextView
            android:id="@+id/tv_home_invitation_item_list_date_time"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{@string/home_fragment_pet_time(invitation.date_time)}"
            tools:text="2021/01/05">

        </com.example.petsmatchingapp.utils.JFTextView>


    </LinearLayout>



    <ImageView
        android:id="@+id/iv_home_invitation_item_list_delete"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:src="@drawable/ic_baseline_delete_forever_24"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toEndOf="@id/ll_home_invitation_item_list_description">

    </ImageView>





</androidx.constraintlayout.widget.ConstraintLayout>

</layout>

由於我們希望我們可以在每個string前面都新增說明,譬如說這個資料是時間,那我們就在前面新增約散時間:XXX,這樣使用者才不會誤會這個資料是代表什麼意思! 所以我們可以到string新增以下

    <string name="home_fragment_pet_type">寵物種類: %1$s-%2$s</string>
    <string name="home_fragment_pet_place">地點: %1$s-%2$s</string>
    <string name="home_fragment_pet_time">時間: %1$s</string>
  • %1$s-%2$s:的第一個數字1代表第一個參數,數字2代表第二個參數,s代表字串
    也就是我們在xml的textView指定的第一個參數跟第二個參數
android:text="@{@string/home_fragment_pet_place(invitation.area,invitation.date_place)}"

1.2.ListAdapter

我們要去新增一個ListAdapter,好讓我們可以在裡面綁定資料,而ListAdapter跟我們以往的Adapter有什麼不一樣呢? 簡單來說就是新增了DiffCallback,讓我們當今天的list有改變時,我們不用自己去notifyDataSetChanged()參考文章:https://codertw.com/程式語言/665013/

package com.example.petsmatchingapp.ui.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.petsmatchingapp.databinding.FragmentAddInvitationBinding
import com.example.petsmatchingapp.databinding.HomeInvitationItemListBinding
import com.example.petsmatchingapp.model.Invitation
import com.example.petsmatchingapp.ui.fragment.HomeFragment
import com.example.petsmatchingapp.utils.Constant

//我們傳入Fragment,好讓我們好呼叫HomeFragment的funtion,並且繼承ListAdapter,第一個塞入想要丟進的資料,第二個則是ViewHoder,再過來也要丟入 DiffCallback

class HomeAdapter(val fragment: HomeFragment): ListAdapter<Invitation, HomeAdapter.HomeViewHolder>(DiffCallback) {


//DiffCallback會幫我們比對新舊list是否有差異,讓我們不用去呼叫notifyDataSetChanged()
    
    companion object DiffCallback: DiffUtil.ItemCallback<Invitation>(){
        override fun areItemsTheSame(oldItem: Invitation, newItem: Invitation): Boolean {
            return oldItem === newItem

        }

        override fun areContentsTheSame(oldItem: Invitation, newItem: Invitation): Boolean {
            return oldItem.id == oldItem.id
        }
    }

    class HomeViewHolder(val binding: HomeInvitationItemListBinding): RecyclerView.ViewHolder(binding.root){
        fun bind(item: Invitation){
//我們透過databinding來綁定我們home_invitation_item_list的data的variable的name
            binding.invitation = item
			//透過Glide來顯示Pet圖片
    Constant.loadPetImage(item.pet_image,binding.ivHomeInvitationItemListImage)
            //要加入這個,才可以即時更新UI
			binding.executePendingBindings()

        }


    }


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeViewHolder {
        return HomeViewHolder(HomeInvitationItemListBinding.inflate(LayoutInflater.from(parent.context)))


    }
	
		
    override fun onBindViewHolder(holder: HomeViewHolder, position: Int) {
        val model = getItem(position)
        holder.bind(model)
		//在這邊設定delete的按鈕Listener
        holder.binding.ivHomeInvitationItemListDelete.setOnClickListener{
            fragment.setAndShowDeleteDialog(model.id)
        }

    }
}

這時候我們會看到setAndShowDeleteDialog這邊是紅字,沒關係,我們晚點再回來解決! 我們先初始化我們的adapter

1.3.回到fragment_home.xml新增

在ConstraintLayout新增recyclerview

		<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_home_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        </androidx.recyclerview.widget.RecyclerView>

1.4.初始化Adapter

我們回到HomeFragment新增以下,並且在onCreateView呼叫它

private fun setAdapter() {
		//LinearLayoutManager負責RecyclerView是水平還是垂直,預設是垂直
        binding.rvHomeFragment.layoutManager =  LinearLayoutManager(requireContext())
        homeAdapter = HomeAdapter(this)
        //綁定adapter
		binding.rvHomeFragment.adapter = homeAdapter
    }

好的!! 我們的Adapter就完成啦!! 接下來則是要開始顯示我們的Invitation囉!

2.顯示當前會員的Invitation

2.1.MatchingViewModel新增livedata跟funtion


private val _homeInvitationList =  MutableLiveData<List<Invitation>>()
val homeInvitationList: LiveData<List<Invitation>>
get() = _homeInvitationList



fun getCurrentUserInvitation(fragment:HomeFragment,id: String){
		//直接找到 Invitaion的集合
        Firebase.firestore.collection(Constant.INVITATION)
			//用get去拿資料
            .get()
            .addOnSuccessListener {
				//創立一個空的mutableList
                val currentInvitationList = mutableListOf<Invitation>()
//因為get會拿到所有的資料,所以我們要透過for迴圈來找出資料欄位裡面的user_id是符合我們當前登入的使用者Id

                for (i in it.documents){
                    val model = i.toObject(Invitation::class.java)
                    if (model?.user_id == id){
						//若符合則加入list			
                        currentInvitationList.add(model)
                    }
                }
				//最後再把list加入到livedata	
                _homeInvitationList.postValue(currentInvitationList)
                
            }
            .addOnFailureListener {
                fragment.getCurrentUserInvitationListFail(it.toString())
            }
    }


並且在onResume()來呼叫我們的funtion

override fun onResume() {

accountViewModel.userDetail.value?.id?.let { matchingViewModel.getCurrentUserInvitation(this,it) }
        super.onResume()
    }

2.2.回到HomeFragment來新增getCurrentUserInvitationListFail

fun getCurrentUserInvitationListFail(e: String){
        showSnackBar(e,true)
    }

2.3觀測Livedata

回到HomeFragment,並在onCreateView新增

//觀察livedata變化時,新增資料到adapter
matchingViewModel.homeInvitationList.observe(viewLifecycleOwner, Observer {
		  //把list丟進去
          homeAdapter.submitList(it)
        })

這時候就可以看到我們的Invitation的列表囉!!

3.刪除邀約內容

我們現在需要處理的就是setAndShowDeleteDialog(),我們希望當今天user按下在紅色垃圾桶的時候,會顯示對話框AlertDialog,並且根據使用者的選擇來刪除或關掉對話框。

3.1.新增string

	<string name="alertDialog_title_do_you_want_delete">刪除約散</string>
    <string name="alertDialog_message_delete_description">刪除約散後,其他會員無法看到您的邀約。</string>
    <string name="alertDialog_delete_positive_yes">刪除</string>
    <string name="alertDialog_delete_negative_no">再考慮一下</string>
    <string name="delete_successful">成功刪除!</string>

3.2.建立對話框

fun setAndShowDeleteDialog(id: String){
	  //建立builder來設定一些參數
      val builder = AlertDialog.Builder(requireContext())
      builder.apply {
		//設定Title
        setTitle(resources.getString(R.string.alertDialog_title_do_you_want_delete))
        //設定Message
		setMessage(resources.getString(R.string.alertDialog_message_delete_description))
        //設定肯定的字跟ClickListener  
        setPositiveButton(resources.getString(R.string.alertDialog_delete_positive_yes),object :DialogInterface.OnClickListener{
          override fun onClick(dialog: DialogInterface?, which: Int) {
			//使用者按肯定,就傳入Invitation的id,並刪除
            deleteInvitation(id)
			//關掉對話框
            dialog?.dismiss()
          }
        })
		//否定的字跟ClickListener
        setNegativeButton(resources.getString(R.string.alertDialog_delete_negative_no),object :DialogInterface.OnClickListener{
          override fun onClick(dialog: DialogInterface?, which: Int) {
			//關掉對話框
            dialog?.dismiss()
          }

        })

      }

      val alertDialog = builder.create()
	  //設定不能點擊其它地方關掉
      alertDialog.setCancelable(false)
      alertDialog.show()

    }

接下來則是要到MatchingViewModel來新增刪除的funtion

fun deleteInvitation(id: String,fragment: HomeFragment){
        Firebase.firestore.collection(Constant.INVITATION)
            .document(id)
			//直接用delete來刪除我們特定的資料
            .delete()
            .addOnSuccessListener {
            fragment.deleteInvitationSuccess()
            }
            .addOnFailureListener {
                fragment.deleteInvitationFail(it.toString())
            }
    }

這時候發現有紅字! 一樣來HomeFragment新增

fun deleteInvitationSuccess() {
        showSnackBar(resources.getString(R.string.delete_successful), false)
        matchingViewModel.getCurrentUserInvitation(this,accountViewModel.getCurrentUID()!!)
    }

    fun deleteInvitationFail(e: String) {
        showSnackBar(e, true)
    }

這樣就大功告成啦!!

成果如下

day11.finish


上一篇
【Day10】AddInvitationFragment(下) X DatePickerDialog
下一篇
【day12】InvitationDetailFragment
系列文
30天建立寵物約散App-Android新手篇30

尚未有邦友留言

立即登入留言