【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


<<:  Day14 - 模型评估 part 1

>>:  认识CSS(一):什麽是CSS

CSS微动画 - Animation也会影响网页效能?

Q: 终於要讲效能了! A: 以Loading为范例讲黑~ Animation Loading 直...

【Day07】事件处理 Handling Events

React 事件处理 React 和 HTML 事件处理的语法略有不同: HTML 的事件语法: &...

【Day 02】从零开始的 Line Chatbot 系统-序章 Part 2

认识一些软件开发的专业术语 在做软件专案的时候,常常会看到一些英文简写,像是 Day 01 讲到的 ...

[Golang]恢复panic(recover、defer)-心智图总结

1. 如何让panic,包含一个值 在呼叫panic函数时,把某个值做为参数传给该函数就可以了。pa...

D13 删除特定的使用者文件

已经先有测试资料了 来试试看删除文件的方法 doc_info/views.py 一样使用修饰器来验证...