如何在Android中使用Coroutines和实时数据库进行Firebase认证

105 阅读6分钟

如何在Android中使用Coroutines和实时数据库进行Firebase认证

Firebase认证提供了后端服务、易于使用的SDK和随时可用的UI框架,用于验证用户的应用。Firebase实时数据库可以用来存储用户的详细信息。

由于大多数Android开发者不希望编写自己的后端,所有这些都使他们的工作变得更容易。

在Android中,在进行网络调用时,如用户认证、发送或从Firebase数据库查询数据,你不应该在Main Thread 。你应该在Background Thread ,然后相应地更新UI。

当我们使用Coroutines 来进行Firebase操作时,我们的代码会显得更干净,没有模板,使代码更易读/清晰,从长远来看,可以提高应用程序的生产力。

前提条件

要继续学习本教程,你将需要以下条件。

  • 在你的电脑上安装[Android Studio]。
  • 对如何创建和运行Android应用程序有一个扎实的了解。
  • Kotlin编程语言的基础知识。
  • Kotlin Coroutines的基本知识。
  • 使用Jetpack组件的知识,即LivedataViewModelRepository 模式。
  • 了解如何将一个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步 - 主屏幕

在这一步,我们将创建两个活动,即LoginActivityRegisterActivity 。在你创建的目录上,右键单击ui 目录并创建这两个活动。

Main

第4步 - 设计用户界面

在创建这两个活动后,我们将定义它们的布局应该是什么样子。

activity_register.xml 的外观应该如下图所示。根据你的使用情况,可以随意添加更多的字段。

demo

activity_login.xml 应该如下图所示。

demo

第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函数。