Google 致力于为黑人社区推动种族平等。查看具体举措

Firebase Android Codelab-建立友好的聊天室

截屏

图片:工作友好的聊天应用程序。

欢迎使用友好聊天代码实验室。在此代码实验室中,您将学习如何使用Firebase平台在Android上创建聊天应用程序。

您将学到什么

  • 如何使用Firebase身份验证来允许用户登录。
  • 如何使用Firebase实时数据库同步数据。
  • 如何在Firebase存储中存储二进制文件。

你需要什么

克隆存储库

从命令行克隆GitHub存储库:

$ git clone https://github.com/firebase/codelab-friendlychat-android

导入到Android Studio

在Android Studio中,点击文件>打开,然后选择build-android-start目录( android_studio_folder )下载示例代码的目录中。

现在,您应该在Android Studio中打开build-android-start项目。如果您看到有关google-services.json文件丢失的警告,请不要担心。它将在下一步中添加。

检查依赖项

在此代码实验室中,已经为您添加了所有需要的依赖项,但是了解如何将Firebase SDK添加到您的应用程序很重要:

build.gradle

buildscript {
    // ...

    dependencies {
        classpath 'com.android.tools.build:gradle:4.1.2'

        // The google-services plugin is required to parse the google-services.json file
        classpath 'com.google.gms:google-services:4.3.5'
        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.1'
    }
}

app / build.gradle

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.google.firebase.crashlytics'
}

android {
    // ...
}

dependencies {
    // ...

    // Google Sign In SDK
    implementation 'com.google.android.gms:play-services-auth:19.0.0'

    // Firebase SDK
    implementation platform('com.google.firebase:firebase-bom:26.6.0')
    implementation 'com.google.firebase:firebase-database-ktx'
    implementation 'com.google.firebase:firebase-storage-ktx'
    implementation 'com.google.firebase:firebase-auth-ktx'

    // Firebase UI Library
    implementation 'com.firebaseui:firebase-ui-database:7.1.1'
}

// Apply the 'google-services' plugin
apply plugin: 'com.google.gms.google-services'

在此步骤中,您将创建一个Firebase项目以在此代码实验室中使用,并将项目配置添加到您的应用程序中。

创建一个新项目

  1. 在浏览器中,转到Firebase控制台
  2. 选择添加项目
  3. 选择或输入项目名称,您可以使用所需的任何名称。
  4. 您无需为此项目使用Google Analytics(分析),因此可以在询问时将其禁用。
  5. 单击创建项目,然后在您的项目准备好后,单击继续

将Firebase添加到您的应用

在开始此步骤之前,获取应用程序的SHA1哈希:在项目目录中运行以下命令,确定调试密钥的SHA1:

./gradlew signingReport

Store: /Users/<username>/.android/debug.keystore
Alias: AndroidDebugKey
MD5: A5:88:41:04:8F:06:59:6A:AE:33:76:87:AA:AD:19:23
SHA1: A7:89:F5:06:A8:07:A1:22:EC:90:6A:A6:EA:C3:D4:8B:3A:30:AB:18
SHA-256: 05:A2:2A:35:EE:F2:51:23:72:4D:72:67:A5:6A:8A:58:22:2C:00:A6:AB:F6:45:D5:A1:82:D8:90:A4:69:C8:FE
Valid until: Wednesday, August 10, 2044

您应该看到类似上面的输出,重要的一行是SHA1键。如果找不到SHA1哈希,请参阅此页面以获取更多信息。

现在,在Firebase控制台中,请按照以下步骤将Android应用添加到您的项目中:

  1. 在新项目的概述屏幕中,单击Android图标以启动设置工作流程:添加Android应用
  2. 在下一个屏幕上,输入com.google.firebase.codelab.friendlychat作为应用程序的软件包名称。
  3. 单击注册应用程序,然后单击下载google-services.json以下载google-services配置文件。
  4. google-services.json文件复制到您项目中的app目录中。下载文件后,您可以跳过控制台中显示的后续步骤(在build-android-start项目中已经为您完成了这些步骤)。
  5. 为确保所有依赖项都可用于您的应用程序,此时应将您的项目与gradle文件同步。从Android Studio工具栏中选择“文件” > “与Gradle文件同步项目”

现在,您已将项目导入Android Studio,并使用JSON文件配置了google-services插件,即可开始首次运行该应用程序。

  1. 启动您的Android设备或模拟器。
  2. 在Android Studio中,点击运行执行 )。

该应用程序应在您的设备上启动。此时,您应该看到一个空的消息列表,并且发送和接收消息将不起作用。在下一部分中,您将对用户进行身份验证,以便他们可以使用友好聊天。

该应用程序将使用Firebase实时数据库存储所有聊天消息。在添加数据之前,我们应确保该应用程序是安全的,并且只有经过身份验证的用户才能发布消息。在此步骤中,我们将启用Firebase身份验证并配置实时数据库安全规则。

配置Firebase身份验证

在您的应用程序可以代表您的用户访问Firebase身份验证API之前,您必须先启用它

  1. 导航到Firebase控制台并选择您的项目
  2. 选择身份验证
  3. 选择登录方法选项卡
  4. Google开关切换到启用状态(蓝色)
  5. 设置支持电子邮件。
  6. 在出现的对话框中按保存

如果稍后在此代码实验室中收到错误消息“ CONFIGURATION_NOT_FOUND”,请返回此步骤并仔细检查您的工作。

配置实时数据库

如前所述,此应用会将聊天消息存储在Firebase实时数据库中。在这一步中,我们将创建一个数据库,并通过一种称为安全规则的JSON配置语言来配置安全性。

  1. 在Firebase控制台中转到您的项目,然后从左侧导航中选择“实时数据库”。
  2. 单击Create Database创建一个新的Realtime Database实例,然后选择us-central1区域,然后单击Next
  3. 当提示有关安全规则时,选择锁定模式,然后单击启用

创建数据库后,选择“规则”选项卡并使用以下内容更新规则配置:

{
  "rules": {
    "messages": {
      ".read": "auth.uid != null",
      ".write": "auth.uid != null"
    }
  }
}

单击“发布”以发布新规则。

有关此工作原理的更多信息(包括有关“ auth”变量的文档),请参阅Firebase安全文档

添加基本​​的登录功能

接下来,我们将向应用程序添加一些基本的Firebase身份验证代码,以检测用户并实现登录屏幕。

检查当前用户

首先将以下实例变量添加到MainActivity.kt类中:

MainActivity.kt

// Firebase instance variables
private lateinit var auth: FirebaseAuth

现在,让我们修改MainActivity以在用户打开应用程序且未经身份验证时将其发送到登录屏幕。将binding附加到视图,将以下内容添加到onCreate()方法:

MainActivity.kt

// Initialize Firebase Auth and check if the user is signed in
auth = Firebase.auth
if (auth.currentUser == null) {
    // Not signed in, launch the Sign In activity
    startActivity(Intent(this, SignInActivity::class.java))
    finish()
    return
}

我们还想检查用户是否在onStart()期间登录:

MainActivity.kt

public override fun onStart() {
    super.onStart()
    // Check if user is signed in.
    if (auth.currentUser == null) {
        // Not signed in, launch the Sign In activity
        startActivity(Intent(this, SignInActivity::class.java))
        finish()
        return
    }
}

然后实现getUserPhotoUrl()getUserName()方法,以返回有关当前经过身份验证的Firebase用户的适当信息:

MainActivity.kt

private fun getPhotoUrl(): String? {
    val user = auth.currentUser
    return user?.photoUrl?.toString()
}

private fun getUserName(): String? {
    val user = auth.currentUser
    return if (user != null) {
        user.displayName
    } else ANONYMOUS
}

然后实现signOut()方法来处理退出按钮:

MainActivity.kt

private fun signOut() {
    auth.signOut()
    signInClient.signOut()
    startActivity(Intent(this, SignInActivity::class.java))
    finish()
}

现在,我们拥有所有适当的逻辑,可以在必要时将用户发送到登录屏幕。接下来,我们需要实现登录屏幕以正确验证用户身份。

实施登录屏幕

打开文件SignInActivity.kt 。这里,一个简单的“登录”按钮用于启动身份验证。在此步骤中,您将实现使用Google登录的逻辑,然后使用该Google帐户向Firebase进行身份验证。

// Firebase instance variables注释下的SignInActivity类中添加Auth实例变量:

SignInActivity.kt

// Firebase instance variables
private lateinit var auth: FirebaseAuth

然后,以与在MainActivity相同的方式,编辑onCreate()方法以初始化Firebase:

SignInActivity.kt

// Initialize FirebaseAuth
auth = Firebase.auth

接下来,开始使用Google登录。更新signIn()方法,如下所示:

SignInActivity.kt

private fun signIn() {
    val signInIntent = signInClient.signInIntent
    startActivityForResult(signInIntent, RC_SIGN_IN)
}

接下来,实现onActivityResult()方法来处理登录结果。如果Google登录结果成功,请使用该帐户向Firebase进行身份验证:

SignInActivity.kt

public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    // Result returned from launching the Intent in signIn()
    if (requestCode == RC_SIGN_IN) {
        val task = GoogleSignIn.getSignedInAccountFromIntent(data)
        try {
            // Google Sign In was successful, authenticate with Firebase
            val account = task.getResult(ApiException::class.java)
            firebaseAuthWithGoogle(account)
        } catch (e: ApiException) {
            // Google Sign In failed, update UI appropriately
            Log.w(TAG, "Google sign in failed", e)
        }
    }
}

实施firebaseAuthWithGoogle()方法以使用已登录的Google帐户进行身份验证:

SignInActivity.kt

private fun firebaseAuthWithGoogle(acct: GoogleSignInAccount?) {
    Log.d(TAG, "firebaseAuthWithGoogle:" + acct?.id)
    val credential = GoogleAuthProvider.getCredential(acct?.idToken, null)
    auth.signInWithCredential(credential)
        .addOnSuccessListener(this) {
            // If sign in succeeds the auth state listener will be notified and logic to
            // handle the signed in user can be handled in the listener.
            Log.d(TAG, "signInWithCredential:success")
            startActivity(Intent(this@SignInActivity, MainActivity::class.java))
            finish()
        }
        .addOnFailureListener(this) { e -> // If sign in fails, display a message to the user.
            Log.w(TAG, "signInWithCredential", e)
            Toast.makeText(
                this@SignInActivity, "Authentication failed.",
                Toast.LENGTH_SHORT
            ).show()
        }
}

就是这样!您仅通过几​​个方法调用就使用Google作为身份提供者实现了身份验证,而无需管理任何服务器端配置。

测试你的工作

在您的设备上运行该应用。您应该立即被发送到登录屏幕。点按“ Google登录”按钮。如果一切正常,则应将您发送到消息传递屏幕。

在这一步中,我们将添加功能来读取和显示实时数据库中存储的消息。

导入样本消息

  1. 在Firebase控制台中,从左侧导航菜单中选择“实时数据库”。
  2. 在“数据”标签的溢出菜单中,选择导入JSON
  3. 浏览到克隆的存储库的根目录中的initial_messages.json文件,然后选择它。
  4. 点击导入

读取资料

同步消息

在本部分中,我们将通过以下方式添加将新添加的消息同步到应用程序UI的代码:

  • 初始化Firebase实时数据库并添加侦听器以处理对数据所做的更改。
  • 更新RecyclerView适配器,以便显示新消息。
  • MainActivity类中将Database实例变量与其他Firebase实例变量一起添加:

MainActivity.kt

// Firebase instance variables
// ...
private lateinit var db: FirebaseDatabase
private lateinit var adapter: FriendlyMessageAdapter

在注释// Initialize Realtime Database and FirebaseRecyclerAdapter下,使用以下定义的代码修改MainActivity的onCreate()方法// Initialize Realtime Database and FirebaseRecyclerAdapter 。此代码从Realtime Database添加所有现有消息,然后在Firebase Realtime Database的messages路径下侦听新的子条目。它为每条消息在UI中添加一个新元素:

MainActivity.kt

// Initialize Realtime Database
db = Firebase.database
val messagesRef = db.reference.child(MESSAGES_CHILD)

// The FirebaseRecyclerAdapter class and options come from the FirebaseUI library
// See: https://github.com/firebase/FirebaseUI-Android
val options = FirebaseRecyclerOptions.Builder<FriendlyMessage>()
    .setQuery(messagesRef, FriendlyMessage::class.java)
    .build()
adapter = FriendlyMessageAdapter(options, getUserName())
binding.progressBar.visibility = ProgressBar.INVISIBLE
manager = LinearLayoutManager(this)
manager.stackFromEnd = true
binding.messageRecyclerView.layoutManager = manager
binding.messageRecyclerView.adapter = adapter

// Scroll down when a new message arrives
// See MyScrollToBottomObserver for details
adapter.registerAdapterDataObserver(
    MyScrollToBottomObserver(binding.messageRecyclerView, adapter, manager)
)

接下来,在FriendlyMessageAdapter.kt类中,在内部类MessageViewHolder()实现bind()方法:

FriendlyMessageAdapter.kt

inner class MessageViewHolder(private val binding: MessageBinding) : ViewHolder(binding.root) {
    fun bind(item: FriendlyMessage) {
        binding.messageTextView.text = item.text
        setTextColor(item.name, binding.messageTextView)

        binding.messengerTextView.text = if (item.name == null) ANONYMOUS else item.name
        if (item.photoUrl != null) {
            loadImageIntoView(binding.messengerImageView, item.photoUrl!!)
        } else {
            binding.messengerImageView.setImageResource(R.drawable.ic_account_circle_black_36dp)
        }
    }
    ...
}

我们还需要显示作为图像的消息,因此还要在内部类ImageMessageViewHolder()实现bind()方法:

FriendlyMessageAdapter.kt

inner class ImageMessageViewHolder(private val binding: ImageMessageBinding) :
    ViewHolder(binding.root) {
    fun bind(item: FriendlyMessage) {
        loadImageIntoView(binding.messageImageView, item.imageUrl!!)

        binding.messengerTextView.text = if (item.name == null) ANONYMOUS else item.name
        if (item.photoUrl != null) {
            loadImageIntoView(binding.messengerImageView, item.photoUrl!!)
        } else {
            binding.messengerImageView.setImageResource(R.drawable.ic_account_circle_black_36dp)
        }
    }
}

最后,返回MainActivity ,开始和停止侦听Firebase Realtime Database的更新。如下所示,在MainActivity更新onPause()onResume()方法:

MainActivity.kt

public override fun onPause() {
    adapter.stopListening()
    super.onPause()
}

public override fun onResume() {
    super.onResume()
    adapter.startListening()
}

测试消息同步

  1. 点击运行执行 )。
  2. 在Firebase控制台中,返回“实时数据库”部分,然后手动添加ID为-ABCD 。确认消息显示在您的Android应用中:

恭喜,您刚刚将实时数据库添加到了您的应用程序中!

实现短信发送

在本部分中,您将添加使应用程序用户能够发送短信的功能。下面的代码片段侦听“发送”按钮上的单击事件,使用消息字段的内容创建一个新的FriendlyMessage对象,并将消息推送到数据库。 push()方法将自动生成的ID添加到被推对象的路径中。这些ID是顺序的,以确保将新消息添加到列表的末尾。

MainActivity类的onCreate()方法中更新发送按钮的单击侦听器。这段代码已经在onCreate()方法的底部。更新onClick()主体以匹配以下代码:

MainActivity.kt

// Disable the send button when there's no text in the input field
// See MyButtonObserver for details
binding.messageEditText.addTextChangedListener(MyButtonObserver(binding.sendButton))

// When the send button is clicked, send a text message
binding.sendButton.setOnClickListener {
    val friendlyMessage = FriendlyMessage(
        binding.messageEditText.text.toString(),
        getUserName(),
        getPhotoUrl(),
        null /* no image */
    )
    db.reference.child(MESSAGES_CHILD).push().setValue(friendlyMessage)
    binding.messageEditText.setText("")
}

实现图像信息发送

在本部分中,您将添加使应用程序用户能够发送图像消息的功能。通过以下步骤创建图像消息:

  • 选择图片
  • 处理图像选择
  • 将临时图像消息写入实时数据库
  • 开始上传所选图片
  • 上传完成后,将图像消息URL更新为已上传图像的URL

选择图片

为了添加图像,此代码实验室使用Cloud Storage for Firebase。云存储是存储应用程序二进制数据的好地方。

在Firebase控制台中,在左侧导航面板中选择“存储”。然后,单击“入门”以为您的项目启用Cloud Storage。使用建议的默认值,继续按照提示中的步骤进行操作。

处理图像选择并写入临时消息

用户选择图像后,将调用startActivityForResult() 。这已经在onCreate()方法末尾的代码中实现。它启动MainActivityonActivityResult()方法。使用下面的代码段,您将向数据库中写入一条带有临时图像URL的消息,指示该图像正在上传。

MainActivity.kt

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    Log.d(TAG, "onActivityResult: requestCode=$requestCode, resultCode=$resultCode")
    if (requestCode == REQUEST_IMAGE) {
        if (resultCode == RESULT_OK && data != null) {
            val uri = data.data
            Log.d(TAG, "Uri: " + uri.toString())
            val user = auth.currentUser
            val tempMessage =
                FriendlyMessage(null, getUserName(), getPhotoUrl(), LOADING_IMAGE_URL)
            db.reference.child(MESSAGES_CHILD).push()
                .setValue(
                    tempMessage,
                    DatabaseReference.CompletionListener { databaseError, databaseReference ->
                        if (databaseError != null) {
                            Log.w(
                                TAG, "Unable to write message to database.",
                                        databaseError.toException()
                                    )
                                    return@CompletionListener
                                }

                                // Build a StorageReference and then upload the file
                                val key = databaseReference.key
                                val storageReference = Firebase.storage
                                    .getReference(user!!.uid)
                                    .child(key!!)
                                    .child(uri!!.lastPathSegment!!)
                                putImageInStorage(storageReference, uri, key)
                            })
        }
    }
}

上传图片并更新消息

将方法putImageInStorage()添加到MainActivity 。在onActivityResult()调用它以启动所选图像的上传。上传完成后,您将更新消息以使用适当的图像。

MainActivity.kt

private fun putImageInStorage(storageReference: StorageReference, uri: Uri, key: String?) {
    // First upload the image to Cloud Storage
    storageReference.putFile(uri)
        .addOnSuccessListener(
            this
        ) { taskSnapshot -> // After the image loads, get a public downloadUrl for the image
            // and add it to the message.
            taskSnapshot.metadata!!.reference!!.downloadUrl
                .addOnSuccessListener { uri ->
                    val friendlyMessage =
                        FriendlyMessage(null, getUserName(), getPhotoUrl(), uri.toString())
                    db.reference
                        .child(MESSAGES_CHILD)
                        .child(key!!)
                        .setValue(friendlyMessage)
                }
        }
        .addOnFailureListener(this) { e ->
            Log.w(
                TAG,
                "Image upload task was unsuccessful.",
                e
            )
        }
}

测试发送消息

  1. 点击执行运行按钮。
  2. 输入消息并单击发送按钮,新消息应在应用程序用户界面和Firebase控制台中可见。
  3. 点按“ +”图像以从设备中选择图像。新消息应该首先显示一个带有占位符的图像,然后在完成图像上传后与所选的图像一起显示。新消息在Firebase控制台中也应可见,作为数据库中的对象和存储中的Blob。

您刚刚使用Firebase构建了实时聊天应用程序!

你学到了什么

  • Firebase身份验证
  • Firebase实时数据库
  • Firebase的云存储

接下来,尝试使用您学到的知识将Firebase添加到自己的Android应用中!要了解有关Firebase的更多信息,请访问firebase.google.com。