今天我們要新增一個搜尋排序的功能! 還記得我們之前把從datePicker拿到的資料轉成String,再把它傳上去Firestore,而update_time則是直接用System.currentTimeMillis()傳入Firestore。但是我們Firestore排序的話,我們需要排序的是時間,所以我們要修改我們傳時間的方法!
## 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 ,
)
我們在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我們會看到的是以下的格式
但是透過binding拿到的資料還是一樣是Map的格式喔,所以我們等等會提到怎麼把它改成自定義的格式!
直接在我們的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()))
)
我們要修改我們會看到invitation的地方,總共有三個,一個是在InvitationDetailFragment,另外兩個是用在Recyclerview的Layout
我們原本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
這樣就可以啦!
我們來到 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)
}
一樣在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的集合刪除(哭哭
好的! 那這樣就可以打開啦! 對了,資料你要先自己新增喔~
Firestore有提供很簡單的方式讓我們搜尋
我們這邊強迫使用者用單選,我們用radioButton,且一定要選擇,不選不能搜尋,layout這邊我就不貼囉
我們先來到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:一大串網址
它會希望我們去建立複合式索引,我們直接給它提供的鏈結,就可以創建囉!,如下圖
按建立索引,就可以建立了,等它建立完後,我們就可以搜尋啦!
成品如下!!