Google se compromete a impulsar la igualdad racial para las comunidades afrodescendientes. Obtén información al respecto.

Firebase Android Codelab: crea un chat amigable

captura de pantalla

Imagen: Aplicación Working Friendly Chat.

Bienvenido al codelab de Friendly Chat. En este laboratorio de código, aprenderá a usar la plataforma Firebase para crear una aplicación de chat en Android.

Lo que aprenderás

  • Cómo usar Firebase Authentication para permitir que los usuarios inicien sesión.
  • Cómo sincronizar datos con Firebase Realtime Database.
  • Cómo almacenar archivos binarios en Firebase Storage.

Lo que necesitarás

  • Android Studio versión 4.0+.
  • Un dispositivo Android o un emulador con Android 4.4+.
  • Familiaridad con el lenguaje de programación Kotlin.

Clonar el repositorio

Clona el repositorio de GitHub desde la línea de comando:

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

Importar a Android Studio

En Android Studio, haga clic en Archivo > Abrir y seleccione el directorio build-android-start ( android_studio_folder ) del directorio donde descargó el código de muestra.

Ahora debería tener abierto el proyecto build-android-start en Android Studio. Si ve una advertencia sobre la falta de un archivo google-services.json , no se preocupe. Se agregará en el siguiente paso.

Comprobar dependencias

En este laboratorio de código, ya se agregaron todas las dependencias que necesitará, pero es importante comprender cómo agregar el SDK de Firebase a su aplicación:

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'

En este paso, creará un proyecto de Firebase para usar durante este laboratorio de código y agregará la configuración del proyecto a su aplicación.

Crea un nuevo proyecto

  1. En su navegador, vaya a la consola de Firebase .
  2. Seleccione Agregar proyecto .
  3. Seleccione o ingrese un nombre de proyecto, puede usar el nombre que desee.
  4. No necesitará Google Analytics para este proyecto, por lo que puede deshabilitarlo cuando se le solicite.
  5. Haga clic en Crear proyecto y cuando su proyecto esté listo, haga clic en Continuar

Agrega Firebase a tu aplicación

Antes de comenzar este paso, obtenga el hash SHA1 de su aplicación: Ejecute el siguiente comando en el directorio del proyecto para determinar el SHA1 de su clave de depuración:

./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

Debería ver un resultado como el anterior, la línea importante es la tecla SHA1 . Si no puede encontrar su hash SHA1, consulte esta página para obtener más información.

Ahora, en Firebase console, sigue estos pasos para agregar una aplicación de Android a tu proyecto:

  1. Desde la pantalla de descripción general de su nuevo proyecto, haga clic en el icono de Android para iniciar el flujo de trabajo de configuración: agregar aplicación de Android
  2. En la siguiente pantalla, ingrese com.google.firebase.codelab.friendlychat como el nombre del paquete para su aplicación.
  3. Haga clic en Registrar aplicación y luego haga clic en Descargar google-services.json para descargar el archivo de configuración de google-services .
  4. Copie el archivo google-services.json en el directorio de la app en su proyecto. Después de descargar el archivo, puede omitir los siguientes pasos que se muestran en la consola (ya se han realizado en el proyecto build-android-start).
  5. Para asegurarse de que todas las dependencias estén disponibles para su aplicación, debe sincronizar su proyecto con los archivos gradle en este momento. Seleccione Archivo > Sincronizar proyecto con archivos Gradle en la barra de herramientas de Android Studio.

Ahora que ha importado el proyecto a Android Studio y configurado el complemento de google-services con su archivo JSON, está listo para ejecutar la aplicación por primera vez.

  1. Inicie su dispositivo o emulador Android.
  2. En Android Studio, haga clic en Ejecutar (ejecutar ) en la barra de herramientas.

La aplicación debería iniciarse en su dispositivo. En este punto, debería ver una lista de mensajes vacía y el envío y la recepción de mensajes no funcionarán. En la siguiente sección, autentica a los usuarios para que puedan usar el Chat amistoso.

Esta aplicación utilizará Firebase Realtime Database para almacenar todos los mensajes de chat. Antes de agregar datos, debemos asegurarnos de que la aplicación sea segura y que solo los usuarios autenticados puedan publicar mensajes. En este paso, habilitaremos la autenticación de Firebase y configuraremos las reglas de seguridad de la base de datos en tiempo real.

Configurar la autenticación de Firebase

Antes de que su aplicación pueda acceder a las API de Firebase Authentication en nombre de sus usuarios, deberá habilitarla.

  1. Navegue a la consola de Firebase y seleccione su proyecto
  2. Seleccionar autenticación
  3. Seleccione la pestaña Método de inicio de sesión
  4. Mueva el interruptor de Google a habilitado (azul)
  5. Establezca un correo electrónico de soporte.
  6. Presione Guardar en el cuadro de diálogo resultante

Si obtiene errores más adelante en este laboratorio de código con el mensaje "CONFIGURATION_NOT_FOUND", vuelva a este paso y vuelva a verificar su trabajo.

Configurar la base de datos en tiempo real

Como se mencionó, esta aplicación almacenará mensajes de chat en Firebase Realtime Database. En este paso crearemos una base de datos y configuraremos la seguridad a través de un lenguaje de configuración JSON llamado Reglas de seguridad.

  1. Vaya a su proyecto en Firebase console y seleccione Realtime Database en el panel de navegación de la izquierda.
  2. Haga clic en Crear base de datos para crear una nueva instancia de Realtime Database y luego seleccione la región us-central1 y haga clic en Siguiente .
  3. Cuando se le pregunte acerca de las reglas de seguridad, elija el modo bloqueado y haga clic en Activar .

Una vez creada la base de datos, seleccione la pestaña Reglas y actualice la configuración de las reglas con lo siguiente:

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

Haga clic en "Publicar" para publicar las nuevas reglas.

Para obtener más información sobre cómo funciona esto (incluida la documentación sobre la variable "auth"), consulte la documentación de seguridad de Firebase.

Agregar funcionalidad básica de inicio de sesión

A continuación, agregaremos un código básico de autenticación de Firebase a la aplicación para detectar usuarios e implementar una pantalla de inicio de sesión.

Verificar usuario actual

Primero agregue la siguiente variable de instancia a la clase MainActivity.kt :

MainActivity.kt

// Firebase instance variables
private lateinit var auth: FirebaseAuth

Ahora modifiquemos MainActivity para enviar al usuario a la pantalla de inicio de sesión cada vez que abra la aplicación y no esté autenticado. Agregue lo siguiente al método onCreate() después de adjuntar el binding a la vista:

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
}

También queremos comprobar si el usuario ha onStart() durante 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
    }
}

Luego, implemente los getUserPhotoUrl() y getUserName() para devolver la información apropiada sobre el usuario de Firebase actualmente autenticado:

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
}

Luego implemente el método signOut() para manejar el botón de cerrar sesión:

MainActivity.kt

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

Ahora tenemos toda la lógica para enviar al usuario a la pantalla de inicio de sesión cuando sea necesario. A continuación, debemos implementar la pantalla de inicio de sesión para autenticar correctamente a los usuarios.

Implementar la pantalla de inicio de sesión

Abra el archivo SignInActivity.kt . Aquí se utiliza un botón de inicio de sesión simple para iniciar la autenticación. En este paso, implementará la lógica para iniciar sesión con Google y luego usará esa cuenta de Google para autenticarse con Firebase.

Agrega una variable de instancia de Auth en la clase SignInActivity debajo del comentario // Firebase instance variables :

SignInActivity.kt

// Firebase instance variables
private lateinit var auth: FirebaseAuth

Luego, edite el método onCreate() para inicializar Firebase de la misma manera que lo hizo en MainActivity :

SignInActivity.kt

// Initialize FirebaseAuth
auth = Firebase.auth

A continuación, inicie sesión con Google. Actualice el método signIn() para que se vea así:

SignInActivity.kt

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

A continuación, implemente el método onActivityResult() para manejar el resultado del inicio de sesión. Si el resultado del inicio de sesión de Google fue exitoso, use la cuenta para autenticarse con 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)
        }
    }
}

Implemente el método firebaseAuthWithGoogle() para autenticarse con la cuenta de Google firmada:

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()
        }
}

¡Eso es! Ha implementado la autenticación utilizando Google como proveedor de identidad en solo unas pocas llamadas a métodos y sin necesidad de administrar ninguna configuración del lado del servidor.

Pon a prueba tu trabajo

Ejecute la aplicación en su dispositivo. Debería ser enviado inmediatamente a la pantalla de inicio de sesión. Toque el botón Iniciar sesión con Google. Luego, debería ser enviado a la pantalla de mensajería si todo funcionó bien.

En este paso agregaremos funcionalidad para leer y mostrar mensajes almacenados en Realtime Database.

Importar mensajes de muestra

  1. En Firebase console, selecciona Realtime Database en el menú de navegación de la izquierda.
  2. En el menú adicional de la pestaña Datos, seleccione Importar JSON .
  3. Busque el archivo initial_messages.json en la raíz del repositorio clonado y selecciónelo.
  4. Haga clic en Importar .

Leer datos

Sincronizar mensajes

En esta sección, agregamos código que sincroniza los mensajes recién agregados a la interfaz de usuario de la aplicación mediante:

  • Inicializando Firebase Realtime Database y agregando un oyente para manejar los cambios realizados en los datos.
  • Actualizando el adaptador RecyclerView para que se muestren nuevos mensajes.
  • Agregue las variables de instancia de la base de datos con sus otras variables de instancia de Firebase en la clase MainActivity :

MainActivity.kt

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

Modifica el método onCreate() de onCreate() en el comentario // Initialize Realtime Database and FirebaseRecyclerAdapter con el código definido a continuación. Este código agrega todos los mensajes existentes de Realtime Database y luego escucha las nuevas entradas secundarias en la ruta de los messages en tu Firebase Realtime Database. Agrega un nuevo elemento a la interfaz de usuario para cada mensaje:

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)
)

A continuación, en la clase FriendlyMessageAdapter.kt , implemente el método bind() dentro de la clase interna MessageViewHolder() :

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)
        }
    }
    ...
}

También necesitamos mostrar mensajes que son imágenes, así que también implementar el método bind() dentro de la clase interna ImageMessageViewHolder() :

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)
        }
    }
}

Finalmente, de vuelta en MainActivity , comience y deje de escuchar actualizaciones de Firebase Realtime Database. Actualice los onPause() y onResume() en MainActivity como se muestra a continuación:

MainActivity.kt

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

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

Prueba de sincronización de mensajes

  1. Haga clic en Ejecutar (ejecutar ).
  2. En Firebase console, regresa a la sección Realtime Database y agrega manualmente un nuevo mensaje con el ID -ABCD . Confirma que el mensaje aparece en tu aplicación de Android:

¡Felicitaciones, acaba de agregar una base de datos en tiempo real a su aplicación!

Implementar el envío de mensajes de texto

En esta sección, agregará la capacidad para que los usuarios de la aplicación envíen mensajes de texto. El fragmento de código a continuación escucha los eventos de clic en el botón enviar, crea un nuevo objeto FriendlyMessage con el contenido del campo del mensaje y envía el mensaje a la base de datos. El método push() agrega una ID generada automáticamente a la ruta del objeto empujado. Estos ID son secuenciales, lo que garantiza que los nuevos mensajes se agreguen al final de la lista.

Actualice el oyente de clics del botón enviar en el método onCreate() en la clase MainActivity . Este código ya está en la parte inferior del método onCreate() . Actualice el cuerpo de onClick() para que coincida con el código siguiente:

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("")
}

Implementar el envío de mensajes de imagen

En esta sección, agregará la capacidad para que los usuarios de la aplicación envíen mensajes con imágenes. La creación de un mensaje de imagen se realiza con estos pasos:

  • Seleccionar imagen
  • Manejar la selección de imágenes
  • Escribe un mensaje de imagen temporal en Realtime Database
  • Comenzar a cargar la imagen seleccionada
  • Actualice la URL del mensaje de imagen a la de la imagen cargada, una vez que se complete la carga

Seleccionar imagen

Para agregar imágenes, este codelab usa Cloud Storage para Firebase. Cloud Storage es un buen lugar para almacenar los datos binarios de su aplicación.

En la consola de Firebase, seleccione Almacenamiento en el panel de navegación izquierdo. Luego, haga clic en Comenzar para habilitar Cloud Storage para su proyecto. Continúe siguiendo los pasos del mensaje, utilizando los valores predeterminados sugeridos.

Manejar la selección de imágenes y escribir mensajes temporales

Una vez que el usuario ha seleccionado una imagen, se llama a startActivityForResult() . Esto ya está implementado en el código al final del método onCreate() . Lanza el MainActivity onActivityResult() . Usando el fragmento de código a continuación, escribirá un mensaje con una URL de imagen temporal en la base de datos indicando que la imagen se está cargando.

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)
                            })
        }
    }
}

Cargar imagen y actualizar mensaje

Agregue el método putImageInStorage() a MainActivity . Se llama en onActivityResult() para iniciar la carga de la imagen seleccionada. Una vez que se complete la carga, actualizará el mensaje para usar la imagen adecuada.

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
            )
        }
}

Prueba de envío de mensajes

  1. Haga clic en elejecutar Botón Ejecutar .
  2. Ingrese un mensaje y presione el botón enviar, el nuevo mensaje debería estar visible en la interfaz de usuario de la aplicación y en la consola de Firebase.
  3. Toque la imagen "+" para seleccionar una imagen de su dispositivo. El nuevo mensaje debe ser visible primero con una imagen de marcador de posición y luego con la imagen seleccionada una vez que se complete la carga de la imagen. El nuevo mensaje también debe estar visible en Firebase console, como un objeto en la base de datos y como un blob en Storage.

¡Acabas de crear una aplicación de chat en tiempo real con Firebase!

Que has aprendido

  • Autenticación de Firebase
  • Firebase Realtime Database
  • Cloud Storage para Firebase

A continuación, intente usar lo que aprendió para agregar Firebase a su propia aplicación de Android. Para obtener más información sobre Firebase, visite firebase.google.com.