如何在Android中使用Coroutines和实时数据库进行Firebase认证
Firebase认证提供了后端服务、易于使用的SDK和随时可用的UI框架,用于验证用户的应用。Firebase实时数据库可以用来存储用户的详细信息。
由于大多数Android开发者不希望编写自己的后端,所有这些都使他们的工作变得更容易。
在Android中,在进行网络调用时,如用户认证、发送或从Firebase数据库查询数据,你不应该在Main Thread
。你应该在Background Thread
,然后相应地更新UI。
当我们使用Coroutines
来进行Firebase操作时,我们的代码会显得更干净,没有模板,使代码更易读/清晰,从长远来看,可以提高应用程序的生产力。
前提条件
要继续学习本教程,你将需要以下条件。
- 在你的电脑上安装[Android Studio]。
- 对如何创建和运行Android应用程序有一个扎实的了解。
- Kotlin编程语言的基础知识。
- Kotlin Coroutines的基本知识。
- 使用Jetpack组件的知识,即
Livedata
、ViewModel
和Repository
模式。 - 了解如何将一个Android项目与Firebase连接起来。如果没有,可以看看这篇文章[Firebase Email and Password Authentication]。
- 了解如何使用
ViewBinding
。
Coroutines回顾
Kotlin Coroutines管理长期运行的操作,如果它们在主线程上运行,就会阻塞它。
在这篇文章中,我们将使用Coroutines
.Coroutines的以下功能。
-
withContext
- 它使用提供的 上下文来调用指定的挂起块, ,直到它完成,然后返回结果。它是按顺序而不是按并发方式执行作业的。请记住,当你有一个单一的作业在后台运行,并希望返回任务的结果时, 是有用的。Coroutine
suspends
withContext
-
await
- 当一个作业正在运行时, ,直到它完成而不需要阻塞线程。await
-
viewModelScope
- 定义了一个与ViewModel绑定的范围。一旦ViewModel被清除,该作用域将被取消。
让我们开始吧。在本教程中,我们将创建一个简单的应用程序,它有一个认证功能,并将用户的数据存储在Firebase Realtime
数据库中。
请确保你已经将你的项目与
Firebase
,并且你已经启用了ViewBinding
。
第1步 - 创建一个Android项目
打开你的Android Studio,创建一个空项目,并给它起一个你选择的名字。
第2步 - 设置项目
在这一步,我们将为我们的项目做所有必要的设置
在你的ap-level
build.gradle中,添加以下依赖项。
def lifecycle_version = "2.4.0-alpha03"
def coroutines_version = "1.3.9"
implementation platform('com.google.firebase:firebase-bom:28.4.1')
// Firebase Realtime Database
implementation 'com.google.firebase:firebase-database-ktx'
// Firebase Auth
implementation 'com.google.firebase:firebase-auth-ktx'
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// Livedata
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.5.1'
接下来,我们将定义项目的结构。
由于我们的应用程序将有几个类,所以我们最好想出一些目录,以尝试将它们分开。
右键单击主包并创建以下目录。ui
,repository
,viewmodel
,util
, 和model
。
第3步 - 主屏幕
在这一步,我们将创建两个活动,即LoginActivity
和RegisterActivity
。在你创建的目录上,右键单击ui
目录并创建这两个活动。
第4步 - 设计用户界面
在创建这两个活动后,我们将定义它们的布局应该是什么样子。
activity_register.xml
的外观应该如下图所示。根据你的使用情况,可以随意添加更多的字段。
activity_login.xml
应该如下图所示。
第5步 - 创建模型类
让我们为用户定义一个模型类。
data class User(
val name: String? = "",
val email: String? = "",
val phone: String? = ""
)
第6步 - 实用项目
在我们进一步行动之前,让我们在util
目录中定义两个项目。
i).SafeCall
函数 - 这个内联函数将允许我们进行安全的网络请求。
inline fun <T> safeCall(action: () -> Resource<T>): Resource<T> {
return try {
action()
} catch (e: Exception) {
Resource.Error(e.message ?: "An unknown Error Occurred")
}
}
ii).Resource
类 - 这个密封的类将代表我们网络调用的三种状态,要么是Loading
,要么是Success
,要么是 。Error
sealed class Resource<T>(val data: T? = null, val message: String? = null) {
class Success<T>(data: T) : Resource<T>(data)
class Loading<T>(data: T? = null) : Resource<T>(data)
class Error<T>(message: String, data: T? = null) : Resource<T>(data, message)
}
第7步 - 存储库类
在这一步,我们将定义我们的业务逻辑,即注册和登录用户的代码。
在你的repository
目录中,创建一个名为MainRepository
的类。
首先定义以下变量。
private val firebaseAuth = FirebaseAuth.getInstance()
private val databaseReference = FirebaseDatabase.getInstance().getReference("users")
注册一个用户
让我们创建一个函数,这个函数将有注册用户的逻辑,并将他们的详细信息存储在Firebase数据库中。
suspend fun createUser(userName: String, userEmailAddress: String, userPhoneNum: String, userLoginPassword: String): Resource<AuthResult> {
return withContext(Dispatchers.IO) {
safeCall {
val registrationResult = firebaseAuth.createUserWithEmailAndPassword(userEmailAddress, userLoginPassword).await()
val userId = registrationResult.user?.uid!!
val newUser = User(userName, userEmailAddress, userPhoneNum)
databaseReference.child(userId).setValue(newUser).await()
Resource.Success(registrationResult)
}
}
}
解释
createUser
函数是一个suspend
函数,其返回类型为AuthResult
,它被包裹在我们之前创建的Resource
类中。在这个函数中,我们利用coroutine的withContext
,并确保我们的coroutine运行在Dispatchers.IO
。
然后我们使用我们在util
目录下创建的内联函数--safeCall
。
在内联函数内。
- 我们调用
firebaseAuth.createUserWithEmailAndPassword(email, password)
,并确保在最后添加await()
。执行的结果被存储在变量result
。 - 接下来,我们提取新创建用户的
uid
,然后创建一个带有所有相应参数的User
类对象。 - 然后我们调用
databaseReference.child(uid).setValue(user).await()
,将用户存储在Firebase Realtime
数据库中。 - 最后,我们返回一个成功的结果。
登录一个用户
我们还定义了另一个用于登录用户的悬念函数。
suspend fun login(email: String, password: String): Resource<AuthResult> {
return withContext(Dispatchers.IO) {
safeCall {
val result = firebaseAuth.signInWithEmailAndPassword(email, password).await()
Resource.Success(result)
}
}
}
解释
类似于register
函数,在login
函数中,我们要确保在firebaseAuth.signInWithEmailAndPassword(email, password)
的末尾加上await()
。
第8步--ViewModel类
一旦你完成了对MainRepository
的工作,右键点击viewmodel
目录并创建一个新的类,称为MainViewModel
。
在ViewModel
,我们将定义一些变量来表示创建和登录用户的状态,同时也是我们的Repository
的实例。
private val _userRegistrationStatus = MutableLiveData<Resource<AuthResult>>()
val userRegistrationStatus: LiveData<Resource<AuthResult>> = _userRegistrationStatus
private val _userSignUpStatus = MutableLiveData<Resource<AuthResult>>()
val userSignUpStatus: LiveData<Resource<AuthResult>> = _userSignUpStatus
private val mainRepository = MainRepository()
然后我们定义两个函数,对应于Repository
中定义的登录和注册函数。
fun createUser(userName: String, userEmailAddress: String, userPhoneNum: String, userLoginPassword: String) {
var error =
if (userEmailAddress.isEmpty() || userName.isEmpty() || userLoginPassword.isEmpty() || userPhoneNum.isEmpty()) {
"Empty Strings"
} else if (!Patterns.EMAIL_ADDRESS.matcher(userEmailAddress).matches()) {
"Not a valid Email"
} else null
error?.let {
_userRegistrationStatus.postValue(Resource.Error(it))
return
}
_userRegistrationStatus.postValue(Resource.Loading())
viewModelScope.launch(Dispatchers.Main) {
val registerResult = mainRepository.createUser(userName = userName, userEmailAddress = userEmailAddress, userPhoneNum = userPhoneNum, userLoginPassword = userLoginPassword)
_userRegistrationStatus.postValue(registerResult)
}
}
fun signInUser(userEmailAddress: String, userLoginPassword: String) {
if (userEmailAddress.isEmpty() || userLoginPassword.isEmpty()) {
_userSignUpStatus.postValue(Resource.Error("Empty Strings"))
} else {
_userSignUpStatus.postValue(Resource.Loading())
viewModelScope.launch(Dispatchers.Main) {
val loginResult = mainRepository.login(userEmailAddress, userLoginPassword)
_userSignUpStatus.postValue(loginResult)
}
}
}
解释
在这两个函数中,我们在viewModelScope
中调用Repository
函数,并确保我们在launch
函数中使用Dispatchers.Main
。
第9步 - 注册活动
一旦用户点击了注册按钮,我们就在ViewModel
中调用registerUser
函数,传递必要的参数。我们还要观察登录的状态,是加载、错误还是成功。
binding.userRegisterButton.setOnClickListener {
// the ids might differ based on how you've named your views
viewModel.createUser(
binding.edxtUserName.editText?.text.toString(),
binding.edxtEmailAddress.editText?.text.toString(),
binding.edxtPhoneNum.editText?.text.toString(),
binding.edxtPassword.editText?.text.toString()
)
}
同时,我们将定义一个观察者来观察注册的状态。
viewModel.registerStatus.observe(this, Observer {
when (it) {
is Resource.Loading -> {
binding.registerProgress.isVisible = true
}
is Resource.Success -> {
binding.registerProgress.isVisible = false
Toast.makeText(applicationContext, "Registered Successfully", Toast.LENGTH_SHORT).show()
}
is Resource.Error -> {
binding.registerProgress.isVisible = false
Toast.makeText(applicationContext, it.message, Toast.LENGTH_SHORT).show()
}
}
})
第10步 - 登录活动
一旦用户点击了登录按钮,我们就在ViewModel
,通过必要的参数调用loginUser
函数。我们也会观察到登录的状态是加载中,错误,还是成功。
binding.buttonLogin.setOnClickListener {
viewModel.loginUser(
binding.editTextLoginEmail.editText?.text.toString(),
binding.editTextLoginPass.editText?.text.toString()
)
}
同时,我们将定义一个观察者来观察登录的状态。
viewModel.loginStatus.observe(this, Observer {
when (it) {
is Resource.Loading -> {
binding.loginProgressBar.isVisible = true
}
is Resource.Success -> {
binding.loginProgressBar.isVisible = false
Toast.makeText(applicationContext, "Logged In Successfully", Toast.LENGTH_SHORT).show()
}
is Resource.Error -> {
binding.loginProgressBar.isVisible = false
Toast.makeText(applicationContext, it.message, Toast.LENGTH_SHORT).show()
}
}
})
结语
在本教程中,我们已经学会了如何用Coroutines在后台线程中运行Firebase函数。