如今,将应用程序的数据存储在云中非常重要,因为用户倾向于拥有多个设备,并希望其应用程序在所有设备之间保持同步。 使用Cloud Firestore (Firebase平台上可用的实时NoSQL数据库),这样做比以往任何时候都更加轻松,安全。
在较早的教程中 ,我向您介绍了Cloud Firestore必须提供的所有强大功能。 今天,我将向您展示如何与其他Firebase产品(例如FirebaseUI Auth和Firebase Analytics)一起使用它,以创建一个简单但可高度扩展的体重跟踪器应用程序。
先决条件
要遵循此分步教程,您需要:
- 新版本的Android Studio
- 一个Firebase帐户
- 以及运行Android 5.0或更高版本的设备或模拟器
1.项目设置
为了能够在Android Studio项目中使用Firebase产品,您将需要Google Services Gradle插件,Firebase配置文件以及一些implementation
依赖项。 使用Firebase Assistant,您可以非常轻松地将它们全部获取。
转到工具> Firebase,打开助手。 接下来,选择“ 分析”选项,然后单击“ 记录分析事件”链接。
现在,您可以按Connect to Firebase按钮将您的Android Studio项目连接到新的Firebase项目。
但是,要实际添加插件和implementation
依赖关系,您还需要按“ 向应用程序添加分析”按钮。
我们今天创建的体重跟踪器应用程序将仅具有两个功能:存储体重并将其显示为按时间倒序排序的列表。 当然,我们将使用Firestore存储权重。 为了将它们显示为列表,我们将使用FirebaseUI库中可用的与Firestore相关的组件。 因此,将以下implementation
依赖项添加到app
模块的build.gradle文件中:
implementation 'com.firebaseui:firebase-ui-firestore:3.2.2'
用户必须只能查看自己的体重,而不能查看使用该应用程序的每个人的体重。 因此,我们的应用程序需要具有标识其用户的能力。 FirebaseUI Auth提供了此功能,因此接下来添加以下依赖项:
implementation 'com.firebaseui:firebase-ui-auth:3.2.2'
我们还将需要一些Material Design小部件,以使我们的应用程序具有令人满意的外观。 因此,请确保将“设计支持”库和“ 材质对话框”库添加为依赖项。
- implementation 'com.android.support:design:26.1.0'
- implementation 'com.afollestad.material-dialogs:core:0.9.6.0'
后,按立即同步按钮以更新项目。
2.配置Firebase身份验证
Firebase身份验证支持各种身份提供程序。 但是,所有这些默认情况下都是禁用的。 要启用其中一个或多个,您必须访问Firebase 控制台 。
在控制台中,选择在上一步中创建的Firebase项目,转到其“ 身份验证”部分,然后按“ 设置登录方法”按钮。
要允许用户使用Google帐户登录到我们的应用,请启用Google作为提供者,为项目指定一个有意义的面向公众的名称,然后按保存按钮。
Google是您可以使用的简单的身份提供者。 它不需要配置,您的Android Studio项目将不需要任何其他依赖项。
3.配置Cloud Firestore
在开始使用Firestore之前,必须先在Firebase控制台中启用它。 为此,请转到“ 数据库”部分,然后按Cloud Firestore Beta卡中的“入门”按钮。
现在将提示您选择数据库的安全模式。 确保选择“ 以锁定模式启动”选项,然后按“ 启用”按钮。
默认情况下,在锁定模式下,任何人都无法访问或修改数据库的内容。 因此,您现在必须创建一个安全规则,该规则允许用户仅读写属于他们的那些文档。 首先打开“ 规则”标签。
在为数据库创建安全规则之前,我们必须终确定如何在数据库中存储数据。 假设我们将有一个名为users
的集合,其中包含代表我们用户的文档。 这些文档可以具有与Firebase身份验证服务为用户生成的ID相同的ID。
因为用户将在其文档中添加几个权重条目,所以使用子集合存储这些条目是理想的。 我们称子集合weights
。
基于上述架构,我们现在可以为路径users/{user_id}/weights/{weight}
创建一个规则。 规则是,仅当{user_id}
变量等于用户的Firebase身份验证ID时,才允许用户读取和写入路径。
因此,更新规则编辑器的内容。
- service cloud.firestore {
- match /databases/{database}/documents {
- match /users/{user_id}/weights/{weight} {
- allow read, write: if user_id == request.auth.uid;
- }
- }
- }
后,按发布按钮以激活规则。
4.验证用户
仅当用户使用Google帐户登录时,我们的应用才可以使用。 因此,打开后,它必须立即检查用户是否具有有效的Firebase身份验证ID。 如果用户确实具有该ID,则应该继续并呈现用户界面。 否则,它应该显示一个登录屏幕。
要检查用户是否具有ID,我们只需检查FirebaseAuth
类的currentUser
属性是否不为null。 如果为null,则可以通过调用AuthUI
类的createSignInIntentBuilder()
方法来创建登录意图。
以下代码向您展示了如何将Google作为身份提供者:
- if(FirebaseAuth.getInstance().currentUser == null) {
- // Sign in
- startActivityForResult(
- AuthUI.getInstance().createSignInIntentBuilder()
- .setAvailableProviders(arrayListOf(
- AuthUI.IdpConfig.GoogleBuilder().build()
- )).build(),
- 1
- )
- } else {
- // Already signed in
- showUI()
- }
请注意,如果有效的ID已经存在,我们将调用名为showUI()
的方法。 此方法尚不存在,因此请创建它,并暂时使其主体保持空白。
- private fun showUI() {
- // To do
- }
为了捕获登录意图的结果,我们必须重写活动的onActivityResult()
方法。 在该方法内部,如果resultCode
参数的值为RESULT_OK
并且currentUser
属性不再为null,则意味着用户已成功登录。 在这种情况下,我们必须再次调用showUI()
方法来呈现用户界面。
如果用户登录失败,我们可以显示修饰语并通过调用finish()
方法关闭应用程序。
因此,将以下代码添加到活动中:
- override fun onActivityResult(requestCode: Int, resultCode: Int,
- data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- if(requestCode == 1) {
- if(resultCode == Activity.RESULT_OK
- && FirebaseAuth.getInstance().currentUser != null) {
- // Successfully signed in
- showUI()
- } else {
- // Sign in failed
- Toast.makeText(this, "You must sign in to continue",
- Toast.LENGTH_LONG).show()
- finish()
- }
- }
- }
此时,如果您是运行该应用程序,则应该能够看到如下所示的登录屏幕:
在后续运行中(由于默认启用了Google Smart Lock),您将自动登录。
5.定义布局
我们的应用程序需要两种布局:一种用于主要活动,另一种用于重量条目,这些条目将显示为按时间倒序排列的列表项。
主要活动的布局必须具有RecyclerView
小部件(将用作列表)和FloatingActionButton
小部件,用户可以按该小部件来创建新的重量条目。 将它们都放置在RelativeLayout
小部件中后,活动的布局XML文件应如下所示:
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout
- xmlns:android="https://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="com.tutsplus.weighttracker.MainActivity">
-
- <android.support.v7.widget.RecyclerView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/weights">
-
- </android.support.v7.widget.RecyclerView>
-
- <android.support.design.widget.FloatingActionButton
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_alignParentBottom="true"
- android:layout_margin="16dp"
- android:src="@drawable/ic_add_black_24dp"
- android:tint="@android:color/white"
- android:onClick="addWeight"/>
-
- </RelativeLayout>
我们已经将名为addWeight()
的单击事件处理程序与FloatingActionButton
小部件相关联。 该处理程序尚不存在,因此请在活动中为其创建一个存根。
- fun addWeight(v: View) {
- // To do
- }
为了使重量条目的布局简单,我们将在其中仅包含两个TextView
小部件:一个用于显示重量,另一个用于显示创建条目的时间。 使用LinearLayout
小部件作为它们的容器就足够了。
因此,创建一个名为weight_entry.xml的新布局XML文件,并向其中添加以下代码:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:padding="16dp">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AppCompat.Large"
- android:id="@+id/weight_view"/>
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AppCompat.Small"
- android:id="@+id/time_view"/>
-
- </LinearLayout>
6.创建模型
在上一步中,您看到了每个重量条目都有一个关联的重量和时间。 要让Firestore知道这一点,我们必须为重量条目创建一个模型。
Firestore模型通常是具有所需成员变量的简单数据类。
- data class WeightEntry(var weight: Double=0.0,
- var timestamp: Long=0)
现在也是为每个重量条目创建视图支架的好时机。 正如您可能已经猜到的那样, RecyclerView
小部件将使用视图保持器来呈现列表项。 因此,创建一个名为WeightEntryVH
的新类,该类扩展了RecyclerView.ViewHolder
类,并为两个TextView
小部件创建了成员变量。 不要忘记使用findViewById()
方法初始化它们。 以下代码向您展示了如何简洁地执行操作:
- class WeightEntryVH(itemView: View?)
- : RecyclerView.ViewHolder(itemView) {
- var weightView: TextView? =
- itemView?.findViewById(R.id.weight_view)
- var timeView: TextView? =
- itemView?.findViewById(R.id.time_view)
- }
7.创建的用户文档
当用户尝试创建权重条目时,我们的应用必须在Firestore上的users
集合内为该users
创建一个单独的文档。 正如我们之前所决定的那样,文档的ID就是用户的Firebase身份验证ID,可以使用currentUser
对象的uid
属性获得。
要获得对users
集合的引用,我们必须使用FirebaseFirestore
类的collection()
方法。 然后,我们可以调用其document()
方法并将uid
作为参数传递,以创建用户的文档。
在读取和创建重量条目时,我们将需要访问用户特定的文档。 为避免对上述逻辑进行两次编码,建议您为其创建一个单独的方法。
- private fun getUserDocument():DocumentReference {
- val db = FirebaseFirestore.getInstance()
- val users = db.collection("users")
- val uid = FirebaseAuth.getInstance().currentUser!!.uid
- return users.document(uid)
- }
请注意,每个用户只能创建一次文档。 换句话说,只要用户使用相同的Google帐户,多次调用上述方法将始终返回同一文档。
8.添加体重条目
当用户按下我们应用程序的浮动操作按钮时,他们必须能够创建新的体重条目。 为了允许他们输入权重,让我们现在创建一个包含EditText
小部件的对话框。 使用“材质对话框”库,这样做非常直观。
在用作按钮的单击事件处理程序的addWeight()
方法内部,创建MaterialDialog.Builder
实例,并调用其title()
和content()
方法为对话框提供标题和有意义的消息。 同样,调用inputType()
方法并传递TYPE_CLASS_NUMBER
作为其参数,以确保用户只能在对话框中键入数字。
接下来,调用input()
方法以指定提示,并将事件处理程序与对话框相关联。 处理程序将接收用户输入的权重作为参数。
后,请确保调用show()
方法以显示对话框。
- MaterialDialog.Builder(this)
- .title("Add Weight")
- .content("What's your weight today?")
- .inputType(InputType.TYPE_CLASS_NUMBER
- or InputType.TYPE_NUMBER_FLAG_DECIMAL)
- .input("weight in pounds", "", false,
- { _, weight ->
-
- // To do
-
- })
- .show()
在事件处理程序内部,我们现在必须添加代码以实际创建和填充新的重量输入文档。 因为该文档必须属于用户文档的weights
集合,所以要访问该集合,必须调用getUserDocument()
方法返回的文档的collection()
getUserDocument()
方法。
有了集合后,就可以调用其add()
方法,并将WeightEntry
类的新实例传递给它来存储条目。
- getUserDocument()
- .collection("weights")
- .add(
- WeightEntry(
- weight.toString().toDouble(),
- Date().time
- )
- )
在上面的代码中,您可以看到我们正在使用Date
类的time
属性将Date
戳记与条目相关联。
如果立即运行该应用程序,则应该可以将新的重量条目添加到Firestore。 您暂时不会在应用程序中看到它们,但是它们将在Firebase控制台中可见。
9.显示重量条目
现在是时候填充我们布局的RecyclerView
小部件了。 因此,首先使用findViewById()
方法为其创建引用,并为其分配LinearLayoutManager
类的新实例。 这必须在我们之前创建的showUI()
方法中完成。
- val weightsView = findViewById<RecyclerView>(R.id.weights)
- weightsView.layoutManager = LinearLayoutManager(this)
RecyclerView
小部件必须显示用户文档的weights
集合中存在的所有文档。 此外,新文件应首先出现。 为了满足这些要求,我们现在必须通过调用collection()
和orderBy()
方法来创建查询。
为了提高效率,可以通过调用limit()
方法来limit()
查询返回的值的数量。
以下代码创建一个查询,该查询返回用户创建的后90个重量条目:
- val query = getUserDocument().collection("weights")
- .orderBy("timestamp", Query.Direction.DESCENDING)
- .limit(90)
现在,使用该查询,我们必须创建一个FirestoreRecyclerOptions
对象,稍后将使用该对象来配置RecyclerView
小部件的适配器。 将query
实例传递给其生成器的setQuery()
方法时,请确保指定返回的结果为WeightEntry
对象的形式。 以下代码显示了如何执行此操作:
- val options = FirestoreRecyclerOptions.Builder<WeightEntry>()
- .setQuery(query, WeightEntry::class.java)
- .setLifecycleOwner(this)
- .build()
您可能已经注意到,我们正在使当前活动成为FirestoreRecyclerOptions
对象的生命周期所有者。 这样做很重要,因为我们希望适配器对常见的生命周期事件做出适当的响应,例如用户打开或关闭应用程序。
此时,我们可以创建一个FirestoreRecyclerAdapter
对象,该对象使用FirestoreRecyclerOptions
对象进行自我配置。 由于FirestoreRecyclerAdapter
类是抽象类,因此Android Studio应该自动覆盖其方法来生成如下代码:
- val adapter = object:FirestoreRecyclerAdapter<WeightEntry,
- WeightEntryVH>(options) {
-
- override fun onBindViewHolder(holder: WeightEntryVH,
- position: Int, model: WeightEntry) {
- // To do
- }
-
- override fun onCreateViewHolder(parent: ViewGroup?,
- viewType: Int): WeightEntryVH {
- // To do
- }
- }
如您所见, FirestoreRecyclerAdapter
类与RecyclerView.Adapter
类非常相似。 实际上,它是从它派生的。 这意味着您可以像使用RecyclerView.Adapter
类一样使用它。
在onCreateViewHolder()
方法内部,您所需要做的就是膨胀weight_entry.xml布局文件,并基于该文件返回一个WeightEntryVH
视图持有者对象。
- val layout = layoutInflater.inflate(R.layout.weight_entry, null)
- return WeightEntryVH(layout)
在onBindViewHolder()
方法内部,必须使用model
参数更新视图持有人内部存在的TextView
小部件的内容。
尽管更新weightView
小部件很简单,但更新timeView
小部件却有些复杂,因为我们不想直接向用户显示时间戳(以毫秒为单位)。
将时间戳转换为可读的日期和时间的简单方法是使用DateUtils
类的formatDateTime()
方法。 除了时间戳之外,该方法还可以接受几个不同的标志,这些标志将用于格式化日期和时间。 您可以自由使用与您的项匹配的标志。
- // Show weight
- holder.weightView?.text = "${model.weight} lb"
-
- // Show date and time
- val formattedDate = DateUtils.formatDateTime(applicationContext,
- model.timestamp,
- DateUtils.FORMAT_SHOW_DATE or
- DateUtils.FORMAT_SHOW_TIME or
- DateUtils.FORMAT_SHOW_YEAR)
- holder.timeView?.text = "On $formattedDate"
后,不要忘记将RecyclerView
小部件指向我们刚刚创建的适配器。
weightsView.adapter = adapter
该应用已准备就绪。 现在,您应该能够添加新条目,并几乎立即看到它们出现在列表中。 如果您在具有相同Google帐户的另一台设备上运行该应用程序,则也会看到相同的重量条目。
结论
在本教程中,您了解了使用Cloud Firestore作为数据库为Android创建功能齐全的体重跟踪器应用程序的便捷性。 随时为其添加更多功能! 我也建议您尝试将其发布在Google Play上。 有了Firebase Spark计划,该计划目前免费提供1 GB的数据存储,因此为至少数千名用户提供服务不会有任何问题。
翻译自: https://code.tutsplus.com/tutorials/create-a-weight-tracker-app-with-cloud-firestore--cms-30772