iT邦幫忙

2021 iThome 鐵人賽

DAY 10
0
Mobile Development

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

【Day10】AddInvitationFragment(下) X DatePickerDialog

接下上集!!,我們已經完成layout,還有上傳照片了。那麼接下來我們要做的就是把選取時間的日曆叫出來,好讓我們的user選擇邀約的時間!我們主要是透過 datePicker,然後讓使用者按下date_time的edText的時候,會跳出日曆讓我們選取。

來看上一篇:https://ithelp.ithome.com.tw/articles/10271280

1.取得日期

  • 建立Calendar的實例
  • 建立DatePickerDialog

我們就直接建立setUpDatePickerDialog的funtion。

private fun setUpDatePickerDialog(){
		//建立Calendar,好讓我們可以把DatePicker拿到的資料放到Calendar,並且顯示
        val calendar = Calendar.getInstance()
		//我們在這邊先給它創立後,然後只要在onClick那邊用show()把它叫出來
        datePicker = DatePickerDialog(requireContext(),
            { _, year, month, dayOfMonth ->
				//指定格式
                val mFormat = "yyyy-MM-dd"
                val sdf = SimpleDateFormat(mFormat, Locale.getDefault())
                calendar.set(year,month,dayOfMonth)
				//賦值給selectedDate,好讓我們晚點傳給Firestore
                selectedDate = sdf.format(calendar.time)
                binding.edAddInvitationDateTime.setText(sdf.format(calendar.time)) },calendar.get(Calendar.YEAR),calendar.get(Calendar.MONTH),calendar.get(Calendar.DAY_OF_MONTH))

    }

在class下新增

private lateinit var datePicker: DatePickerDialog
private var selectedDate: String? = null

DatePickerDialog的參數

  • context
  • listener:這部分我們直接用lambda來寫,它會監聽使用者選擇的時間,我們直接在裡面setText,讓我們的EditText顯示時間。
  • year:指定初始年
  • month:指定初始月
  • dayOfMonth:指定初始日期

★我們用Calendar.getInstance()初始化的calendar會得到當下的年月份日期,所以我們這邊用的get拿到的Calendar也是拿到當下的時間。

★記得不要直接把listener拿到的年月日期改成String顯示喔! 因為它的月份是會-1的,譬如使用者選擇的是1月,它的Month會拿到0。

然後在onCreateView呼叫

setUpDatePickerDialog()
//順便新增EdText的listener
binding.edAddInvitationDateTime.setOnClickListener(this)

再onClick新增

binding.edAddInvitationDateTime -> {
                datePicker.show()
            }

這時候,我們發現

day10.focus

它竟然要點兩次!! 這時候我們就要去layout的ed_add_invitation_date_time 新增一行

//預設是當EditText被觸碰時,會得到focus,然後進到可編輯模式
//把它改成false時,它就不會得到focus,並且只響應onClick事件
android:focusableInTouchMode="false"

最後,我們發現,選擇時間的時候,竟然可以讓我們選擇過去的時間!! 這就有點不符合邏輯啦,怎麼可以約昨天呢!!? 於是這邊我們可以用設定minDate的方式來為我們強迫user只能往前選擇! 依據就是今天的日期!

我們直接在setUpDatePickerDialog()新增

//直接把datePicker的最小日期設定成當前時間
datePicker.datePicker.minDate = System.currentTimeMillis()

可能有些人不太懂最小/最大時間是什麼意思

  • minDate,可以選擇的最早時間,如果是設定System.currentTimeMills,則就可以從今天開始往未來選擇。
  • maxDate,反之,可以選擇時間的最晚時間,如設定System.currentTimeMills,則代表可以從過去開始選擇,但最後面的時間,只能選擇現在。

以下為DatePicker的範例!! 不用寫在本App呦

那如果你要設定其他日期,概念上是都會有一個picker要用的calendar(也就是要set我們listener監聽到的日期),另外一個calendar則是我們想要限制的最大/小的日期。

我們以設定最晚日期maxDate為例子,一樣我們在setUpDatePickerDialog()裡面新增

//新增一個顯示最大日期的Calendar
		val maxDateCalendar = Calendar.getInstance()
		//把它設置時間,(year,month-1,day),記得月份要-1喔!
        maxDateCalendar.set(2021,8,26)
		//再把datePicker的最大日期設定成maxDateCalendar的時間就好啦
        datePicker.datePicker.maxDate = maxDateCalendar.timeInMillis

如果要設定當下時間過多久後可以用

		//新增一個顯示最大日期的Calendar
		val maxDateCalendar = Calendar.getInstance()
		//把它設置時間,第一個參數填單位,第二個則是填Int
        maxDateCalendar.add(Calendar.DAY_OF_MONTH,10)
		//再把datePicker的最大日期設定成maxDateCalendar的時間就好啦
        datePicker.datePicker.maxDate = maxDateCalendar.timeInMillis

2.上傳資料到FireStore

Firestore的 add會自動產生新的id,set則是在寫得時候就要填入id,而我們這次的做法,就是把透過add新增的文件(它就會有自動生成的id),把它update到我們資料原本裡面的id,因為原本預設是""

我們先去MatchingViewModel新增以下

fun addInvitationToFireStore(fragment:AddInvitationFragment,invitation: Invitation){
        Firebase.firestore.collection(Constant.INVITATION)
            .add(invitation)
            .addOnSuccessListener {
				//我們透過HashMap來新增值
                val mHashMap = HashMap<String,Any>()
                mHashMap[Constant.ID] = it.id
                it.update(mHashMap)
                    .addOnSuccessListener { 
                        fragment.addInvitationSuccess()
                    }
                    .addOnFailureListener { 
                        fragment.addInvitationFail(it.toString())
                    }
            }
            .addOnFailureListener {  
                fragment.addInvitationFail(it.toString())
            }
    }

我們希望文件id跟資料的id是保持一致,這樣我們才可以快速找到想要的資料

https://ithelp.ithome.com.tw/upload/images/20210925/20138017wZkFyZoQHi.png

新增完後,我們發現我們的Constant還需要新增一些資料

//新增collection的名稱
const val INVITATION: String = "invitation"
//Invitation內id欄位
const val ID: String = "id"

最後在來到Fragment新增成功/失敗的funtion

fun addInvitationSuccess(){
       hideDialog()
       showSnackBar(resources.getString(R.string.add_invitation_successful),false)
    }

    fun addInvitationFail(e: String){
        hideDialog()
        showSnackBar(e,true)
    }

string

<string name="add_invitation_successful">上傳邀約成功!</string>

既然我們都有許多editText,那我們當然要確認值

private fun validDataForm(): Boolean{
        return when{
            mUri.isNullOrBlank() ->{
     showSnackBar(resources.getString(R.string.hint_select_your_image),true)
                false
            }
            selectedPetType.isNullOrBlank() -> {
     showSnackBar(resources.getString(R.string.hint_enter_pet_type),true)
                false
            } TextUtils.isEmpty(binding.edAddInvitationPetTypeDescription.text.toString().trim()) -> {    showSnackBar(resources.getString(R.string.hint_enter_pet_type_description),true)
                false
            }
            selectedArea.isNullOrBlank() -> {
    showSnackBar(resources.getString(R.string.hint_enter_your_area),true)
                false
            }
  TextUtils.isEmpty(binding.edAddInvitationDatePlace.text.toString().trim()) -> {
     showSnackBar(resources.getString(R.string.hint_enter_date_place),true)
                false
            }
            selectedDate.isNullOrBlank() ->{
       showSnackBar(resources.getString(R.string.hint_enter_date_time),true)
                false
            }
   TextUtils.isEmpty(binding.edAddInvitationNote.text.toString().trim()) ->{
        showSnackBar(resources.getString(R.string.hint_enter_date_note),true)
                false
            }
            else -> true
        }
    }

接下來回到 onClick

binding.btnAddInvitationFragmentSubmit ->{
                showDialog(resources.getString(R.string.please_wait))
               if (validDataForm()){
                   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()
                   )
                   matchingViewModel.addInvitationToFireStore(this,invitation)
               }
            }

再過來別忘了在onCreateView新增

binding.btnAddInvitationFragmentSubmit.setOnClickListener(this)

!!! 這時候我們發現一個錯誤,就是userDetail沒有資料,儘管我們在AccountViewModel的地方是把getUserDetail的funtion放在init{}裡面,但是有沒有可能使用者根本還沒有用到需要進到AccountViewModel的時候,就進來我們的AddInvitationFragment了呢? 所以我們就要在進到這個MatchingActivity的第一個Fragment的地方,就先把userDetail放到livedata啦!

到DashboardFragment的onCreateView來新增

accountViewModel.getUserDetail()

也別忘了在class下面新增

private val accountViewModel: AccountViewModel by sharedViewModel()

3.返回鍵+刪除bottomNavigation跟ActionBar

3.1返回鍵

				binding.toolbarAddInvitationFragment.setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
        binding.toolbarAddInvitationFragment.setNavigationOnClickListener {
            requireActivity().onBackPressed()
        }

3.2.刪除bottomNavigation跟ActionBar

private fun dismissActivityActionBarAndBottomNavigationView(){
        val activityInstance = this.activity as MatchingActivity
        activityInstance.supportActionBar?.hide()
        activityInstance.findViewById<BottomNavigationView>(R.id.nav_view).visibility = View.GONE

    }

然後在 onCreateView呼叫就可以啦!!

day10.finish

大功告成!!!! 明天預計會在我們的HomeFragment新增我們有發成功的資料!!


上一篇
【Day9】AddInvitationFragment(上)
下一篇
【Day11】HomeFragment X RecyclerView X Firestore取/刪除資料
系列文
30天建立寵物約散App-Android新手篇30

尚未有邦友留言

立即登入留言