昨天我們已經把登入畫面做好了,大家有沒有覺得萬事起頭難呢? 既然我們已經有登入畫面了,當然要有註冊畫面啦,否則我們永遠登不進去畫面~ 那麼就開始啦!
先給大家看長相
2.1 先建立 dimen/string
2.2 去 layout
<dimen name="toolbar_textSize">18sp</dimen>
<string name="toolbar_title_register_account">註冊帳號</string>
<string name="hint_enter_your_name">請輸入您的姓名</string>
<string name="hint_enter_again_password">請再次輸入您的密碼</string>
<string name="hint_do_not_enter_same_password">請再確認密碼是否一致</string>
<string name="register_already_have_account">我已經註冊囉!</string>
<string name="register_pick_me_to_login">點我登入</string>
<string name="register_success">恭喜您註冊成功!</string>
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.RegisterFragment">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar_register_fragment"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:background="@color/light_pewter_blue"
app:layout_constraintTop_toTopOf="parent">
<com.example.petsmatchingapp.utils.JFTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textColor="@color/white"
android:text="@string/toolbar_title_register_account"
android:textStyle="bold"
android:gravity="center"
android:textSize="@dimen/toolbar_textSize"/>
</androidx.appcompat.widget.Toolbar>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/toolbar_register_fragment"
app:layout_constraintBottom_toBottomOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tip_register_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="@dimen/tip_margin_start_end"
android:layout_marginEnd="@dimen/tip_margin_start_end"
android:layout_marginTop="@dimen/tip_margin_top_bottom">
<com.example.petsmatchingapp.utils.JFEditText
android:id="@+id/ed_register_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/edText_padding"
android:inputType="textPersonName"
android:hint="@string/hint_enter_your_name"
android:textSize="@dimen/edText_textSize"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tip_register_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tip_register_name"
android:layout_marginStart="@dimen/tip_margin_start_end"
android:layout_marginEnd="@dimen/tip_margin_start_end"
android:layout_marginTop="@dimen/tip_margin_top_bottom">
<com.example.petsmatchingapp.utils.JFEditText
android:id="@+id/ed_register_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/edText_padding"
android:inputType="textEmailAddress"
android:hint="@string/hint_enter_your_email"
android:textSize="@dimen/edText_textSize"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tip_register_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tip_register_email"
android:layout_marginStart="@dimen/tip_margin_start_end"
android:layout_marginEnd="@dimen/tip_margin_start_end"
android:layout_marginTop="@dimen/tip_margin_top_bottom">
<com.example.petsmatchingapp.utils.JFEditText
android:id="@+id/ed_register_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/edText_padding"
android:inputType="numberPassword"
android:hint="@string/hint_enter_your_password"
android:textSize="@dimen/edText_textSize"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tip_register_again_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tip_register_password"
android:layout_marginStart="@dimen/tip_margin_start_end"
android:layout_marginEnd="@dimen/tip_margin_start_end"
android:layout_marginTop="@dimen/tip_margin_top_bottom">
<com.example.petsmatchingapp.utils.JFEditText
android:id="@+id/ed_register_password_again"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/edText_padding"
android:inputType="numberPassword"
android:hint="@string/hint_enter_again_password"
android:textSize="16sp"/>
</com.google.android.material.textfield.TextInputLayout>
<com.example.petsmatchingapp.utils.JFButton
android:id="@+id/btn_register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintTop_toBottomOf="@id/tip_register_again_password"
android:layout_marginTop="@dimen/tip_margin_top_bottom"
android:text="@string/register"
android:background="@drawable/button_background"
android:foreground="?attr/selectableItemBackground"
android:textColor="@color/white"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/btn_register"
android:layout_marginTop="20dp"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<com.example.petsmatchingapp.utils.JFTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/hint_word_textSize"
android:text="@string/register_already_have_account"/>
<com.example.petsmatchingapp.utils.JFTextView
android:id="@+id/tv_register_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/hint_word_textSize"
android:textStyle="bold"
android:text="@string/register_pick_me_to_login"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
稍微看了一下我們的layout後會發現,我們會有好幾個editText,代表我們要確認使用者是否輸入為空,太棒了,我們就可以用之前在LoginFragment的方式來check
private fun validDataForm(): Boolean{
return when{
TextUtils.isEmpty(binding.edRegisterName.text.toString().trim()) -> {
showSnackBar("請輸入名稱",true)
return false
}
TextUtils.isEmpty(binding.edRegisterEmail.text.toString().trim()) -> {
showSnackBar("請輸入有效的Email",true)
return false
}
TextUtils.isEmpty(binding.edRegisterPassword.text.toString().trim()) -> {
showSnackBar("請輸入有效的密碼",true)
return false
}
TextUtils.isEmpty(binding.edRegisterPasswordAgain.text.toString().trim()) -> {
showSnackBar("請輸入有效的確認密碼",true)
return false
}
binding.edRegisterPasswordAgain.text.toString().trim() != binding.edRegisterPassword.text.toString().trim() ->{
showSnackBar("請再確認密碼是否一致",true)
return false
}
else -> true
}
}
這邊需要跟大家稍微說明一下,我們 sign in 的時候帳號資訊會在Authentication裡面,而每一個帳號也可以去設定userName跟photoUri,但是因為我們除了這些外,我們還會需要有其他user資訊。所以我們這次只在auth儲存 email+password,其他的資訊都儲存在 Firestore database。
首先我們需要來創造一個data class,並且命名為 User
//每個都設預設值,好讓我們在創立 Object的時候,可以不用全部都指定
data class User(
val id: String = "",
val name: String = "",
val email: String = "",
val password: String = "",
val image: String = "",
val gender: String = "",
val profileCompleted: Boolean = false
)
在RegisterFragment一樣去繼承 View.OnClickListener,並且override onClick
override fun onClick(v: View?) {
//透過 when,來設定當user點擊時的回饋方式
when(v){
binding.btnRegister ->{
//如果validDataForm 回饋的為 true
if(validDataForm()){
showDialog(resources.getString(R.string.please_wait))
//實例化 user,並且把 edText拿到的資料給它。
val user = User(
name = binding.edRegisterName.text.toString().trim(),
email = binding.edRegisterEmail.text.toString().trim(),
password = binding.edRegisterPassword.text.toString().trim(),
)
//並把 fragment + user 丟給 viewModel
accountViewModel.registerWithEmailAndPassword(this,user)
}
}
}
}
一樣,因為要叫出 AccountViewModel,所以我們要在最上面新增
private val accountViewModel: AccountViewModel by sharedViewModel()
以及因為我們用override OnClick,所以我們也要在 onCreateView裡面設定
binding.btnRegister.setOnClickListener(this)
好啦,我們要回到 AccountViewModel
//把 RegisterFragment跟 user傳進去
fun registerWithEmailAndPassword(fragment: RegisterFragment, user: User){
//直接拿 auth的instance,並且透過 email跟 password來辦帳號
FirebaseAuth.getInstance().createUserWithEmailAndPassword(user.email,user.password)
.addOnSuccessListener{
val newUser = User(
name = user.name,
email = user.email,
password = user.password,
id =it.user!!.uid
)
addUserDetailsToFireStore(fragment,newUser)
}
.addOnFailureListener{
fragment.registerFail(it.toString())
}
}
★ 注意,我們在createUserWithEmailAndPassword的funtion後面新增的 addOnSuccessListener,它會傳回一個 AuthResult,我們可以直接透過它去拿到該帳號的使用者id,我們再透過這個id,去設定firestore database的 document id,我們把兩個id設為一樣,可以幫助我們找尋資料。
好的,寫完上面的 Code後會發現,我們有紅字,那我們現在要解決的就是在 Firebase Auth創號帳號後,要再把其他資訊在Firestore database 儲存資料。
private fun addUserDetailsToFireStore(fragment: RegisterFragment,user: User){
//這邊創立 Firestore的 instance,並且collection內指定集合為user,集合裡面填string,我們用Constant來確保每次呼叫都不會拼錯字
FirebaseFirestore.getInstance().collection(Constant.USER)
//這邊指定 document的id為剛剛auth回傳的 uid
.document(user.id)
.set(user, SetOptions.merge())
.addOnSuccessListener{
fragment.registerSuccessful()
}
.addOnFailureListener{
fragment.registerFail(it.toString())
}
}
Firestote有兩種新增資料的方法
至於說剛剛的Constant怎麼寫呢?
在創立 class的地方,我們創立一個Object,並且命名為 Constant
並在裡面新增名為 USER的變數即可,就可以在任何地方呼叫它。
object Constant{
const val USER: String = "user"
}
接下來,我們回到 RegisterFragment,並且新增把資料上傳到Firestore成功或失敗後會跑的funtion
fun registerSuccessful(){
hideDialog()
showSnackBar(resources.getString(R.string.register_success),false)
//當成功上傳後,跳轉到 loginFragment
nav.navigate(R.id.action_registerFragment_to_loginFragment)
}
fun registerFail(e: String){
hideDialog()
//失敗則show 錯誤訊息
showSnackBar(e,true)
}
我們可以看到 nav.navigate(R.id.action_registerFragment_to_loginFragment) 是紅字。
首先我們要把nav這個解決,之前提到我們可以透過findNavController,來控制Fragment跳轉的事件~
再過來要解決後面的 R.id的紅字,這邊的id都是action的id
因為我們目前在 account_nav裡面只有一個Fragment,所以我們要跟昨天交的方式,把RegisterFragment新增進去,並且用連連看的方式把它們連起來。點選圓圈圈,就可以拖移到想要轉換到的Fragment。並且因為我們會從LoginFragment透過textView轉換到RegisterFragment,以及RegisterFragment透過textView轉到LoginFragment,所以我們兩邊都要連起來。
這樣就可以啦! 那我們是不是還忘了什麼啊??
沒錯,就是我們 layout裡面有一個 已經登入了的選項,我們當然也要把這個加入到 onClick的地方啊,並且透過 navigation 的方式轉移到 LoginFragment,在onClick新增
binding.tvRegisterLogin -> {
nav.navigate(R.id.action_registerFragment_to_loginFragment)
}
並在 onCreateView新增
binding.tvRegisterLogin.setOnClickListener(this)
那再來的步驟,就是我們要把我們的 LoginFragment轉到RegisterFragment
先到LoginFragment,並在 onClick的Funtion 裡面新增
binding.tvRegister -> {
nav.navigate(R.id.action_loginFragment_to_registerFragment)
}
還有在class下面新增
//延遲初始化
private lateinit var nav: NavController
在onCreateView 裡面
//初始化 NavController
nav = findNavController()
//設定 onClickListener
binding.tvRegister.setOnClickListener(this)
接下來要設定在toolbar左上方的回退鍵
1.先新增 vector asset,並且選擇一個往左邊的箭頭
2.在onCreateView寫入以下Code
//設定剛剛的icon
binding.toolbarRegisterFragment.setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
//設定導航事件
binding.toolbarRegisterFragment.setNavigationOnClickListener {
requireActivity().onBackPressed()
}
然後當然還有到 Firebase平台,點選自己的專案後,到達 Firestroe database,點選建立資料庫,選擇測試模式。
並且需要更改讀取和寫入的規則
上面的流程透過 email+password註冊帳號後,可以從Firebase Auth 看到
從 Firestore database 看到
完成品出來啦!!
(請原諒這個動畫是我在還沒新增返回鍵時就拍攝的,所以時間跟完文章後,左上角會有白色的返回鍵,且可以使用 XD)
好啦!! 明天會是輕鬆的部分,如果忘記密碼後怎麼辦呢!!!!
大家糾期待一下啦!!!! へけ