iT邦幫忙

2021 iThome 鐵人賽

DAY 17
0

今天我們要新增一個搜尋排序的功能! 還記得我們之前把從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


上一篇
【day16】Realtime Database
下一篇
【day18】聊天室(上) X Realtime database
系列文
30天建立寵物約散App-Android新手篇30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言