绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
使用Cloud Firestore创建体重跟踪器应用
2022-04-08 16:03:21

如今,将应用程序的数据存储在云中非常重要,因为用户倾向于拥有多个设备,并希望其应用程序在所有设备之间保持同步。 使用Cloud Firestore (Firebase平台上可用的实时NoSQL数据库),这样做比以往任何时候都更加轻松,安全。

较早的教程中 ,我向您介绍了Cloud Firestore必须提供的所有强大功能。 今天,我将向您展示如何与其他Firebase产品(例如FirebaseUI Auth和Firebase Analytics)一起使用它,以创建一个简单但可高度扩展的体重跟踪器应用程序。

先决条件

要遵循此分步教程,您需要:

1.项目设置

为了能够在Android Studio项目中使用Firebase产品,您将需要Google Services Gradle插件,Firebase配置文件以及一些implementation依赖项。 使用Firebase Assistant,您可以非常轻松地将它们全部获取。

转到工具> Firebase,打开助手。 接下来,选择“ 分析”选项,然后单击“ 记录分析事件”链接。

Fierbase Assistant panel

现在,您可以按Connect to Firebase按钮将您的Android Studio项目连接到新的Firebase项目。

Connect to Firebase dialog

但是,要实际添加插件和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小部件,以使我们的应用程序具有令人满意的外观。 因此,请确保将“设计支持”库和“ 材质对话框”库添加为依赖项。

  1. implementation 'com.android.support:design:26.1.0'
  2. implementation 'com.afollestad.material-dialogs:core:0.9.6.0'

后,按立即同步按钮以更新项目。

2.配置Firebase身份验证

Firebase身份验证支持各种身份提供程序。 但是,所有这些默认情况下都是禁用的。 要启用其中一个或多个,您必须访问Firebase 控制台 。

在控制台中,选择在上一步中创建的Firebase项目,转到其“ 身份验证”部分,然后按“ 设置登录方法”按钮。

Firebase Authentication home screen

要允许用户使用Google帐户登录到我们的应用,请启用Google作为提供者,为项目指定一个有意义的面向公众的名称,然后按保存按钮。

Google identity provider configuration

Google是您可以使用的简单的身份提供者。 它不需要配置,您的Android Studio项目将不需要任何其他依赖项。

3.配置Cloud Firestore

在开始使用Firestore之前,必须先在Firebase控制台中启用它。 为此,请转到“ 数据库”部分,然后按Cloud Firestore Beta卡中的“入门”按钮。

Cloud Firestore card

现在将提示您选择数据库的安全模式。 确保选择“ 以锁定模式启动”选项,然后按“ 启用”按钮。

Security mode selection screen

默认情况下,在锁定模式下,任何人都无法访问或修改数据库的内容。 因此,您现在必须创建一个安全规则,该规则允许用户仅读写属于他们的那些文档。 首先打开“ 规则”标签。

在为数据库创建安全规则之前,我们必须终确定如何在数据库中存储数据。 假设我们将有一个名为users的集合,其中包含代表我们用户的文档。 这些文档可以具有与Firebase身份验证服务为用户生成的ID相同的ID。

因为用户将在其文档中添加几个权重条目,所以使用子集合存储这些条目是理想的。 我们称子集合weights 。

Firestore database structure

基于上述架构,我们现在可以为路径users/{user_id}/weights/{weight}创建一个规则。 规则是,仅当{user_id}变量等于用户的Firebase身份验证ID时,才允许用户读取和写入路径。

因此,更新规则编辑器的内容。

  1. service cloud.firestore {
  2. match /databases/{database}/documents {
  3. match /users/{user_id}/weights/{weight} {
  4. allow read, write: if user_id == request.auth.uid;
  5. }
  6. }
  7. }

后,按发布按钮以激活规则。

4.验证用户

仅当用户使用Google帐户登录时,我们的应用才可以使用。 因此,打开后,它必须立即检查用户是否具有有效的Firebase身份验证ID。 如果用户确实具有该ID,则应该继续并呈现用户界面。 否则,它应该显示一个登录屏幕。

要检查用户是否具有ID,我们只需检查FirebaseAuth类的currentUser属性是否不为null。 如果为null,则可以通过调用AuthUI类的createSignInIntentBuilder()方法来创建登录意图。

以下代码向您展示了如何将Google作为身份提供者:

  1. if(FirebaseAuth.getInstance().currentUser == null) {
  2. // Sign in
  3. startActivityForResult(
  4. AuthUI.getInstance().createSignInIntentBuilder()
  5. .setAvailableProviders(arrayListOf(
  6. AuthUI.IdpConfig.GoogleBuilder().build()
  7. )).build(),
  8. 1
  9. )
  10. } else {
  11. // Already signed in
  12. showUI()
  13. }

请注意,如果有效的ID已经存在,我们将调用名为showUI()的方法。 此方法尚不存在,因此请创建它,并暂时使其主体保持空白。

  1. private fun showUI() {
  2. // To do
  3. }

为了捕获登录意图的结果,我们必须重写活动的onActivityResult()方法。 在该方法内部,如果resultCode参数的值为RESULT_OK并且currentUser属性不再为null,则意味着用户已成功登录。 在这种情况下,我们必须再次调用showUI()方法来呈现用户界面。

如果用户登录失败,我们可以显示修饰语并通过调用finish()方法关闭应用程序。

因此,将以下代码添加到活动中:

  1. override fun onActivityResult(requestCode: Int, resultCode: Int,
  2. data: Intent?) {
  3. super.onActivityResult(requestCode, resultCode, data)
  4. if(requestCode == 1) {
  5. if(resultCode == Activity.RESULT_OK
  6. && FirebaseAuth.getInstance().currentUser != null) {
  7. // Successfully signed in
  8. showUI()
  9. } else {
  10. // Sign in failed
  11. Toast.makeText(this, "You must sign in to continue",
  12. Toast.LENGTH_LONG).show()
  13. finish()
  14. }
  15. }
  16. }

此时,如果您是运行该应用程序,则应该能够看到如下所示的登录屏幕:

Account selection dialog

在后续运行中(由于默认启用了Google Smart Lock),您将自动登录。

5.定义布局

我们的应用程序需要两种布局:一种用于主要活动,另一种用于重量条目,这些条目将显示为按时间倒序排列的列表项。

主要活动的布局必须具有RecyclerView小部件(将用作列表)和FloatingActionButton小部件,用户可以按该小部件来创建新的重量条目。 将它们都放置在RelativeLayout小部件中后,活动的布局XML文件应如下所示:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout
  3. xmlns:android="https://schemas.android.com/apk/res/android"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. xmlns:tools="http://schemas.android.com/tools"
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"
  8. tools:context="com.tutsplus.weighttracker.MainActivity">
  9. <android.support.v7.widget.RecyclerView
  10. android:layout_width="match_parent"
  11. android:layout_height="match_parent"
  12. android:id="@+id/weights">
  13. </android.support.v7.widget.RecyclerView>
  14. <android.support.design.widget.FloatingActionButton
  15. android:layout_width="wrap_content"
  16. android:layout_height="wrap_content"
  17. android:layout_alignParentRight="true"
  18. android:layout_alignParentBottom="true"
  19. android:layout_margin="16dp"
  20. android:src="@drawable/ic_add_black_24dp"
  21. android:tint="@android:color/white"
  22. android:onClick="addWeight"/>
  23. </RelativeLayout>

我们已经将名为addWeight()的单击事件处理程序与FloatingActionButton小部件相关联。 该处理程序尚不存在,因此请在活动中为其创建一个存根。

  1. fun addWeight(v: View) {
  2. // To do
  3. }

为了使重量条目的布局简单,我们将在其中仅包含两个TextView小部件:一个用于显示重量,另一个用于显示创建条目的时间。 使用LinearLayout小部件作为它们的容器就足够了。

因此,创建一个名为weight_entry.xml的新布局XML文件,并向其中添加以下代码:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:orientation="vertical"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. android:padding="16dp">
  8. <TextView
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. style="@style/TextAppearance.AppCompat.Large"
  12. android:id="@+id/weight_view"/>
  13. <TextView
  14. android:layout_width="wrap_content"
  15. android:layout_height="wrap_content"
  16. style="@style/TextAppearance.AppCompat.Small"
  17. android:id="@+id/time_view"/>
  18. </LinearLayout>

6.创建模型

在上一步中,您看到了每个重量条目都有一个关联的重量和时间。 要让Firestore知道这一点,我们必须为重量条目创建一个模型。

Firestore模型通常是具有所需成员变量的简单数据类。

  1. data class WeightEntry(var weight: Double=0.0,
  2. var timestamp: Long=0)

现在也是为每个重量条目创建视图支架的好时机。 正如您可能已经猜到的那样, RecyclerView小部件将使用视图保持器来呈现列表项。 因此,创建一个名为WeightEntryVH的新类,该类扩展了RecyclerView.ViewHolder类,并为两个TextView小部件创建了成员变量。 不要忘记使用findViewById()方法初始化它们。 以下代码向您展示了如何简洁地执行操作:

  1. class WeightEntryVH(itemView: View?)
  2. : RecyclerView.ViewHolder(itemView) {
  3. var weightView: TextView? =
  4. itemView?.findViewById(R.id.weight_view)
  5. var timeView: TextView? =
  6. itemView?.findViewById(R.id.time_view)
  7. }

7.创建的用户文档

当用户尝试创建权重条目时,我们的应用必须在Firestore上的users集合内为该users创建一个单独的文档。 正如我们之前所决定的那样,文档的ID就是用户的Firebase身份验证ID,可以使用currentUser对象的uid属性获得。

要获得对users集合的引用,我们必须使用FirebaseFirestore类的collection()方法。 然后,我们可以调用其document()方法并将uid作为参数传递,以创建用户的文档。

在读取和创建重量条目时,我们将需要访问用户特定的文档。 为避免对上述逻辑进行两次编码,建议您为其创建一个单独的方法。

  1. private fun getUserDocument():DocumentReference {
  2. val db = FirebaseFirestore.getInstance()
  3. val users = db.collection("users")
  4. val uid = FirebaseAuth.getInstance().currentUser!!.uid
  5. return users.document(uid)
  6. }

请注意,每个用户只能创建一次文档。 换句话说,只要用户使用相同的Google帐户,多次调用上述方法将始终返回同一文档。

8.添加体重条目

当用户按下我们应用程序的浮动操作按钮时,他们必须能够创建新的体重条目。 为了允许他们输入权重,让我们现在创建一个包含EditText小部件的对话框。 使用“材质对话框”库,这样做非常直观。

在用作按钮的单击事件处理程序的addWeight()方法内部,创建MaterialDialog.Builder实例,并调用其title()content()方法为对话框提供标题和有意义的消息。 同样,调用inputType()方法并传递TYPE_CLASS_NUMBER作为其参数,以确保用户只能在对话框中键入数字。

接下来,调用input()方法以指定提示,并将事件处理程序与对话框相关联。 处理程序将接收用户输入的权重作为参数。

后,请确保调用show()方法以显示对话框。

  1. MaterialDialog.Builder(this)
  2. .title("Add Weight")
  3. .content("What's your weight today?")
  4. .inputType(InputType.TYPE_CLASS_NUMBER
  5. or InputType.TYPE_NUMBER_FLAG_DECIMAL)
  6. .input("weight in pounds", "", false,
  7. { _, weight ->
  8. // To do
  9. })
  10. .show()

在事件处理程序内部,我们现在必须添加代码以实际创建和填充新的重量输入文档。 因为该文档必须属于用户文档的weights集合,所以要访问该集合,必须调用getUserDocument()方法返回的文档的collection() getUserDocument()方法。

有了集合后,就可以调用其add()方法,并将WeightEntry类的新实例传递给它来存储条目。

  1. getUserDocument()
  2. .collection("weights")
  3. .add(
  4. WeightEntry(
  5. weight.toString().toDouble(),
  6. Date().time
  7. )
  8. )

在上面的代码中,您可以看到我们正在使用Date类的time属性将Date戳记与条目相关联。

如果立即运行该应用程序,则应该可以将新的重量条目添加到Firestore。 您暂时不会在应用程序中看到它们,但是它们将在Firebase控制台中可见。

Add weight dialog

9.显示重量条目

现在是时候填充我们布局的RecyclerView小部件了。 因此,首先使用findViewById()方法为其创建引用,并为其分配LinearLayoutManager类的新实例。 这必须在我们之前创建的showUI()方法中完成。

  1. val weightsView = findViewById<RecyclerView>(R.id.weights)
  2. weightsView.layoutManager = LinearLayoutManager(this)

RecyclerView小部件必须显示用户文档的weights集合中存在的所有文档。 此外,新文件应首先出现。 为了满足这些要求,我们现在必须通过调用collection()orderBy()方法来创建查询。

为了提高效率,可以通过调用limit()方法来limit()查询返回的值的数量。

以下代码创建一个查询,该查询返回用户创建的后90个重量条目:

  1. val query = getUserDocument().collection("weights")
  2. .orderBy("timestamp", Query.Direction.DESCENDING)
  3. .limit(90)

现在,使用该查询,我们必须创建一个FirestoreRecyclerOptions对象,稍后将使用该对象来配置RecyclerView小部件的适配器。 将query实例传递给其生成器的setQuery()方法时,请确保指定返回的结果为WeightEntry对象的形式。 以下代码显示了如何执行此操作:

  1. val options = FirestoreRecyclerOptions.Builder<WeightEntry>()
  2. .setQuery(query, WeightEntry::class.java)
  3. .setLifecycleOwner(this)
  4. .build()

您可能已经注意到,我们正在使当前活动成为FirestoreRecyclerOptions对象的生命周期所有者。 这样做很重要,因为我们希望适配器对常见的生命周期事件做出适当的响应,例如用户打开或关闭应用程序。

此时,我们可以创建一个FirestoreRecyclerAdapter对象,该对象使用FirestoreRecyclerOptions对象进行自我配置。 由于FirestoreRecyclerAdapter类是抽象类,因此Android Studio应该自动覆盖其方法来生成如下代码:

  1. val adapter = object:FirestoreRecyclerAdapter<WeightEntry,
  2. WeightEntryVH>(options) {
  3. override fun onBindViewHolder(holder: WeightEntryVH,
  4. position: Int, model: WeightEntry) {
  5. // To do
  6. }
  7. override fun onCreateViewHolder(parent: ViewGroup?,
  8. viewType: Int): WeightEntryVH {
  9. // To do
  10. }
  11. }

如您所见, FirestoreRecyclerAdapter类与RecyclerView.Adapter类非常相似。 实际上,它是从它派生的。 这意味着您可以像使用RecyclerView.Adapter类一样使用它。

onCreateViewHolder()方法内部,您所需要做的就是膨胀weight_entry.xml布局文件,并基于该文件返回一个WeightEntryVH视图持有者对象。

  1. val layout = layoutInflater.inflate(R.layout.weight_entry, null)
  2. return WeightEntryVH(layout)

onBindViewHolder()方法内部,必须使用model参数更新视图持有人内部存在的TextView小部件的内容。

尽管更新weightView小部件很简单,但更新timeView小部件却有些复杂,因为我们不想直接向用户显示时间戳(以毫秒为单位)。

将时间戳转换为可读的日期和时间的简单方法是使用DateUtils类的formatDateTime()方法。 除了时间戳之外,该方法还可以接受几个不同的标志,这些标志将用于格式化日期和时间。 您可以自由使用与您的项匹配的标志。

  1. // Show weight
  2. holder.weightView?.text = "${model.weight} lb"
  3. // Show date and time
  4. val formattedDate = DateUtils.formatDateTime(applicationContext,
  5. model.timestamp,
  6. DateUtils.FORMAT_SHOW_DATE or
  7. DateUtils.FORMAT_SHOW_TIME or
  8. DateUtils.FORMAT_SHOW_YEAR)
  9. holder.timeView?.text = "On $formattedDate"

后,不要忘记将RecyclerView小部件指向我们刚刚创建的适配器。

weightsView.adapter = adapter

该应用已准备就绪。 现在,您应该能够添加新条目,并几乎立即看到它们出现在列表中。 如果您在具有相同Google帐户的另一台设备上运行该应用程序,则也会看到相同的重量条目。

Weight entries shown as a list

结论

在本教程中,您了解了使用Cloud Firestore作为数据库为Android创建功能齐全的体重跟踪器应用程序的便捷性。 随时为其添加更多功能! 我也建议您尝试将其发布在Google Play上。 有了Firebase Spark计划,该计划目前免费提供1 GB的数据存储,因此为至少数千名用户提供服务不会有任何问题。

翻译自: https://code.tutsplus.com/tutorials/create-a-weight-tracker-app-with-cloud-firestore--cms-30772

分享好友

分享这个小栈给你的朋友们,一起进步吧。

Cloud Firestore
创建时间:2022-04-08 15:52:09
Cloud Firestore
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • LCR_
    专家
戳我,来吐槽~