【day17】 时间格式 X 搜寻结果排序

今天我们要新增一个搜寻排序的功能! 还记得我们之前把从datePicker拿到的资料转成String,再把它传上去Firestore,而update_time则是直接用System.currentTimeMillis()传入Firestore。但是我们Firestore排序的话,我们需要排序的是时间,所以我们要修改我们传时间的方法!

1.时间格式

## 1.1.修改Invitation

data class Invitation(
        val id: String = "",
        val user_id: 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 = "",
		//我们可以透过Firebase的方法拿到现在时间,但是是Map的格式
        val update_time: Timestamp? = null ,
)

1.2.DatePicker的时间转换成Firestore的时间格式

我们在setUpDatePickerDialog()修改

datePicker = DatePickerDialog(requireContext(),
            { _, year, month, dayOfMonth ->
                val mFormat = "yyyy-MM-dd"
                val sdf = SimpleDateFormat(mFormat, Locale.getDefault())
                calendar.set(year,month,dayOfMonth)
				//直接现在UI先设定时间
               binding.edAddInvitationDateTime.setText(sdf.format(calendar.time))
                //我们再把现在的时间转换成Long格式的毫秒
				val cal = calendar.timeInMillis
				//建立Timestamp,把刚刚的Long丢进去
                val timeStamp = Timestamp(Date(cal))
                selectedDate = timeStamp
         
            },calendar.get(Calendar.YEAR),calendar.get(Calendar.MONTH),calendar.get(Calendar.DAY_OF_MONTH))

这时候印出来的 timeStamp会是Map的格式

Timestamp(seconds=1634196413, nanoseconds=302000000)

虽然我们在Firestore我们会看到的是以下的格式

https://ithelp.ithome.com.tw/upload/images/20211002/20138017jz6HPtB92f.png

但是透过binding拿到的资料还是一样是Map的格式喔,所以我们等等会提到怎麽把它改成自定义的格式!

1.3.修改update_time

直接在我们的binding.btnAddInvitationFragmentSubmit的Click事件里面的val invitation进行修改

val invitation = Invitation(
            user_id = accountViewModel.userDetail.value!!.id,
            pet_image = mUri!!,
            pet_type = selectedPetType!!,
            pet_type_description = binding.edAddInvitationPetTypeDescription.text.toString().trim(),
            area = selectedArea!!,
            date_place = binding.edAddInvitationDatePlace.text.toString().trim(),
            date_time = selectedDate!!,
            note = binding.edAddInvitationNote.text.toString().trim(),
//透过System.currentTimeMillis来拿到现在的时间,并且传进去给Date的Class得到Date,再传进Timestamp,得到StampTime的格式
			update_time = Timestamp(Date(System.currentTimeMillis()))
                   )

1.4.从Timestamp转成我们自订的格式

我们要修改我们会看到invitation的地方,总共有三个,一个是在InvitationDetailFragment,另外两个是用在Recyclerview的Layout

1.4.1. InvitationDetailFragment

我们原本date_time的格式是String,所以我们才可以直接在xml进行绑定,但是现在我们要把Timestamp的格式先改成String,才可以显示在UI,所以我们在kt档用观察的方式写,也记得要把所有的xml绑定的资料都删掉喔!

matchingViewModel.selectedInvitation.observe(viewLifecycleOwner, Observer {
			//我们要的格式
            val mFormat = "yyyy-MM-dd"
            val sdf = SimpleDateFormat(mFormat, Locale.getDefault())
            val displayTime = sdf.format(it.date_time?.toDate()?.time)
            binding.tvInvitationDetailDateTime.text = displayTime
        })

就跟我们从Long的格式转成Timestamp的方式一样

Long转成Timestamp: Long→Date→Timestamp
而Timestamp转成Long则是: Timestamp→Date→Long

这样就可以啦!

1.4.2.DashboardAdapter

我们来到 DashboardViewHolder的bind()来设定,基本上跟刚刚做的事情一样,只是我们换地方写而已

fun bind(item: Invitation){
            binding.invitation = item
			//一样格式
            val format = "yyyy-MM-dd"
            val sdf = SimpleDateFormat(format, Locale.getDefault())
            val dateTime = sdf.format(item.date_time?.toDate()?.time)
            val time = "时间: $dateTime"
            binding.tvDashboardInvitationItemListDateTime.text = time
            binding.executePendingBindings()
            Constant.loadPetImage(item.pet_image,binding.ivDashboardInvitationItemListImage)

        }

1.4.3.HomeAdapter

一样在ViewHolder里面的bind写入

fun bind(item: Invitation){
            binding.invitation = item
            val mFormat = "yyyy-MM-dd"
            val sdf = SimpleDateFormat(mFormat, Locale.getDefault())
            val stampToTime = item.date_time?.toDate()?.time
            val time = "时间: " + sdf.format(stampToTime)
            binding.tvHomeInvitationItemListDateTime.text = time
        Constant.loadPetImage(item.pet_image,binding.ivHomeInvitationItemListImage)
            binding.executePendingBindings()
        }

这时候我们点Run~

就会出现问题,原因则是因为我们之前的时间格式有的用String,有的用Long,所以我们要忍痛到Firestore把invitation的集合删除(哭哭

https://ithelp.ithome.com.tw/upload/images/20211002/20138017JDwwNjT40V.png

好的! 那这样就可以打开啦! 对了,资料你要先自己新增喔~

https://ithelp.ithome.com.tw/upload/images/20211002/20138017anrojCGBEF.png

2.搜寻资料排序

Firestore有提供很简单的方式让我们搜寻

2.1.新增我们的排序按钮

我们这边强迫使用者用单选,我们用radioButton,且一定要选择,不选不能搜寻,layout这边我就不贴罗

2.2.修改我们onClick的事件

我们先来到SearchFragment来新增

//确认我们刚刚设定的rb是否都没有被点跟选择的数量不能超过10(Firestore的限制,不然会报错)
private fun validDataForm(): Boolean{
        return when{
            !binding.rbResultSortInvitationTime.isChecked && !binding.rbResultSortUpdateTime.isChecked -> {
                showSnackBar(resources.getString(R.string.msg_choose_your_result_sort),true)
                false
            }
            areaCheckedList.size >10 -> {
                showSnackBar("最多只能选择10个地区",true)
                false
            }
            petTypeCheckedList.size > 10 -> {
                showSnackBar("最多只能选择10种宠物",true)
                false
            }
           
            else -> true
        }
    }

并把它新增在onClick事件

binding.btnSearchSubmit.setOnClickListener {

				getAreaCheckedList()
                getPetTypeCheckedList()
                if (validDataForm()){
                   
		//设定当哪个排序的哪个按钮被按,就把它传给viewmodel
                    var result_sort: String = ""
                    if (rb_result_sort_invitation_time.isChecked){
                        result_sort = Constant.RESULT_SORT_INVITATION_DAY
                    }else{
                        result_sort = Constant.RESULT_SORT_UPDATE_DAY
                    }
                    matchingViewModel.searchInvitation(accountViewModel.getCurrentUID()!!,areaCheckedList,petTypeCheckedList,result_sort,this)
                }
            
           }

所以我们也要在Constant新增

const val RESULT_SORT_UPDATE_DAY = "result_sort_update_day"
const val RESULT_SORT_INVITATION_DAY = "result_sort_invitation_day"

接下来就是来到我们的matchingViewModel修改成,首先要把参数的地方新增搜寻的方式跟传入当前user的ID,因为我们同样不希望搜寻到自己的资料,并且传入排序方式

fun searchInvitation(currentUserId: String,areaList: MutableList<String>,petTypeList: MutableList<String>,result_sort: String,fragment: SearchFragment){}

再过来里面最外层首先会用 if来判断 result_sort的参数是什麽

if (result_sort == Constant.RESULT_SORT_UPDATE_DAY){
                ...
     }else{
            ...
        }

接下来重点是,我们在如果今天排序方式是Constant.RESULT_SORT_UPDATE_DAY的话(已更新日期排序),

我们在whereIn的下面,get()的上面新增

.orderBy(Constant.UPDATE_TIME,Query.Direction.DESCENDING)

第一个参数是要比较的栏位,第二个则是你要递增或递减
asc 递增(由小到大); desc 递减(由大到小)
而我们希望我们看到的顺序是越靠近我们(越近更新的),我们越早看到,所以我们要用递减

先以单选地区的搜寻当例子

areaList.isNotEmpty() && petTypeList.isEmpty()  ->{
               
                    Firebase.firestore.collection(Constant.INVITATION)
                        .whereIn(Constant.AREA,areaList)
												//新增这一行
                        .orderBy(Constant.UPDATE_TIME,Query.Direction.DESCENDING)
                        .get()
                        .addOnSuccessListener {
                            val list = mutableListOf<Invitation>()
                            for (i in it.documents){
                                val model = i.toObject(Invitation::class.java)
						//这边判断是否邀约的userId是否为本人
                                model?.let {
                                    if (model.user_id != currentUserId){
                                        list.add(it)
                                    }
                                }
                                Timber.d("model: $model")
                            }
                            _dashboardInvitationList.postValue(list)
                            fragment.searchInvitationSuccess(list.size)
                        }
                        .addOnFailureListener {
                            Timber.d("搜寻fail $it")
                            fragment.searchInvitationFail(it.toString())
                        }
                }

而反之如果 result_sort == Constant.RESULT_SORT_INVITATION_DAY,也就是我们要看离我们最近的我们越想看到,太远的东西不用去管它。所以我们就要用递增

新增以下

.orderBy(Constant.DATE_TIME,Query.Direction.ASCENDING)

一样拿只选地区为范例

areaList.isNotEmpty() && petTypeList.isEmpty()  ->{
                
                    Firebase.firestore.collection(Constant.INVITATION)
                        .whereIn(Constant.AREA,areaList)
                        .orderBy(Constant.DATE_TIME,Query.Direction.ASCENDING)
                        .get()
                        .addOnSuccessListener {
                            val list = mutableListOf<Invitation>()
                            for (i in it.documents){
                                val model = i.toObject(Invitation::class.java)
                                model?.let {
                                    if (model.user_id != currentUserId){
                                        list.add(it)
                                    }
                                }
                           
                            }
                            _dashboardInvitationList.postValue(list)
                            fragment.searchInvitationSuccess(list.size)
                        }
                        .addOnFailureListener {
                      
                            fragment.searchInvitationFail(it.toString())
                        }
                }

接下来我们Run了之後,发现会出现错误!!

description=The query requires an index. You can create it here:一大串网址

它会希望我们去建立复合式索引,我们直接给它提供的链结,就可以创建罗!,如下图

https://ithelp.ithome.com.tw/upload/images/20211002/2013801703hanjLnaU.png

按建立索引,就可以建立了,等它建立完後,我们就可以搜寻啦!

成品如下!!

day17.finish


<<:  Angular建立专案(二)(Day17)

>>:  D17 - 吃一颗 Class 语法糖 (上)

[Day08] - 弹跳视窗 Modal - Slot 介绍

网页元件中 , 常会使用 Modal 这种类型的元件 如果我们将其制作成一个 <Modal&g...

响应式网站注意细节-30天学会HTML+CSS,制作精美网站

现在使用智慧型手机比率最高,手机画面很小,所以在制作网页时应注意以下细节 只显示重要的资讯及减少栏位...

Day10 有图有真相

Chart function 身为一个键盘柯南,最重要的技能之一就是储存和下载分析後的结果。另外c...

[Day21] still placeholder

写在前面 still placeholder still placeholder still pla...

Progressive Web App Service Worker (4)

什麽是 Service Workers Service Workers 的角色是位於 Web App...