Как создать андроид приложение для чата с помощью Firebase

Это очередное видео из серии “как создать android приложение”, где мы создаем простые, но вполне работающие приложения.

Сегодня мы создадим простое приложение – чат на андроид, используя сервис Firebase. Это backend service от Google, который мы подробно рассматриваем в нескольких выпусках “Инструментов андроид разработчика”.

Подробно процесс создания приложения-чата смотрите в видео:

Приложение будет использовать авторизацию по email. После авторизации открывается экран с полем ввода, кнопкой отправки и списком сообщений. В этом списке отображаются все отправленные сообщения на всех устройствах, где установлено данное приложение.

Итак начнем. для начала создадим проект в Android Studio. Назовем его FirebaseChat. Шаблон выберем Empty Activity.

Теперь свяжем проект с сервисом Firebase. Для этого перейдем в меню Tools/Firebase. Выберем вкладку Cloud Messaging. Здесь нужно выполнить 2 первых пункта.

Нажатие первой кнопки свяжет наш проект с сервисом Firebase. При этом вам будет предложено авторизоваться с помощью учетной записи Google.
В случае успеха вместо кнопки появится зеленый значок “connected”.

А в консоли разработчика по адресу https://console.firebase.google.com вы увидите новое приложение.

Теперь нужно добавить в проект необходимые зависимости. Нажатие кнопки во втором пункте добавит в файлы сборки проекта ссылки на библиотеки google-services и firebase-messaging.

А в папке модуля app должен появиться файл google-services.json с параметрами, необходимыми для работы проекта с Firebase.

Проект мы подключили, но библиотека firebase-messaging – не совсем то, что нам нужно. Идем в файл сборки пакета build.gradle и заменим ее на библиотеку firebase-ui. Минимальный уровень API, с которым работает эта библиотека – API 16. Изменим соответствующую директиву и синхронизируем с gradle.

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.1"
    defaultConfig {
        applicationId "info.fandroid.firebasechat"
        minSdkVersion 16
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.0.1'
    testCompile 'junit:junit:4.12'

    //Add Library
    compile 'com.android.support:design:25.0.1'
    compile 'com.firebaseui:firebase-ui:0.6.2'
}
apply plugin: 'com.google.gms.google-services'

Теперь перейдем к кодингу.

Для начала создадим макет разметки главного экрана. Нам понадобится поле ввода, кнопка отправки сообщений и виджет списка ListView.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="info.fandroid.firebasechat.MainActivity">


    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/button2"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:inputType="textPersonName"
        android:ems="10"
        android:id="@+id/editText"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_above="@+id/listView"
        android:layout_toLeftOf="@+id/button2"
        android:layout_toStartOf="@+id/button2" />

    <Button
        android:text="Send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button2"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />
</RelativeLayout>

Теперь создадим макет разметки пункта списка item.xml.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:id="@+id/tvUser" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/tvTime"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/tvMessage"
        android:layout_below="@+id/tvUser"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />
</RelativeLayout>

Здесь три Textview для имени автора, времени и текста сообщения.

Также в папке res создадим папку menu и в ней опишем пункт меню для выхода из учетной записи.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item android:icon="@drawable/logout"
        app:showAsAction="always"
        android:id="@+id/menu_signout"/>


</menu>

У него будет иконка из папки drawable. Скачать ее можно здесь (через контекстное меню “сохранить как”). Также пропишем способ отображения в тулбаре.

Атрибут showAsAction берем из пространства имен app, добавим соответствующую декларацию для этого комбинацией Alt+Enter.

Теперь в основном пакете создадим новый класс Message. Это будет макет, или модель сообщения.

package info.fandroid.firebasechat;


import java.util.Date;

public class Message {

    private String textMessage;
    private String autor;
    private long timeMessage;

    public Message(String textMessage, String autor) {
        this.textMessage = textMessage;
        this.autor = autor;

        timeMessage = new Date().getTime();
    }

    public Message() {
    }

    public String getTextMessage() {
        return textMessage;
    }

    public void setTextMessage(String textMessage) {
        this.textMessage = textMessage;
    }

    public String getAutor() {
        return autor;
    }

    public void setAutor(String autor) {
        this.autor = autor;
    }

    public long getTimeMessage() {
        return timeMessage;
    }

    public void setTimeMessage(long timeMessage) {
        this.timeMessage = timeMessage;
    }
}

Создадим переменные textMesage, autorMessage и timeMessage. Как понятно из названий, это текст, автор и время сообщения.

Создадим конструктор с первыми двумя переменными. Используется комбинация Alt+Insert.

В этом же конструкторе будем сохранять в переменную timeMessage текущее время.

Также создадим пустой конструктор, а также геттеры и сеттеры для всех полей класса.

Основной код напишем в классе MainActivity.

package info.fandroid.firebasechat;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.text.format.DateFormat;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.firebase.ui.auth.AuthUI;
import com.firebase.ui.database.FirebaseListAdapter;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.database.FirebaseDatabase;

public class MainActivity extends AppCompatActivity {

    private static int SIGN_IN_REQUEST_CODE = 1;
    private FirebaseListAdapter<Message> adapter;
    RelativeLayout activity_main;
    Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        activity_main = (RelativeLayout)findViewById(R.id.activity_main);
        button = (Button)findViewById(R.id.button2);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                EditText input = (EditText)findViewById(R.id.editText);
                FirebaseDatabase.getInstance().getReference().push()
                        .setValue(new Message(input.getText().toString(),
                                FirebaseAuth.getInstance().getCurrentUser().getEmail()));
                input.setText("");
            }
        });

        if (FirebaseAuth.getInstance().getCurrentUser() == null) {
            startActivityForResult(AuthUI.getInstance()
                .createSignInIntentBuilder()
                .build(), SIGN_IN_REQUEST_CODE);
        } else {
            displayChat();
        }
    }

    private void displayChat() {

        ListView listMessages = (ListView)findViewById(R.id.listView);
        adapter = new FirebaseListAdapter<Message>(this, Message.class, R.layout.item, FirebaseDatabase.getInstance().getReference()) {
            @Override
            protected void populateView(View v, Message model, int position) {

                TextView textMessage, autor, timeMessage;
                textMessage = (TextView)v.findViewById(R.id.tvMessage);
                autor = (TextView)v.findViewById(R.id.tvUser);
                timeMessage = (TextView)v.findViewById(R.id.tvTime);

                textMessage.setText(model.getTextMessage());
                autor.setText(model.getAutor());
                timeMessage.setText(DateFormat.format("dd-MM-yyyy (HH:mm:ss)", model.getTimeMessage()));
            }
        };
        listMessages.setAdapter(adapter);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == SIGN_IN_REQUEST_CODE)
        {
            if (resultCode == RESULT_OK)
            {
                Snackbar.make(activity_main, "Вход выполнен", Snackbar.LENGTH_SHORT).show();
                displayChat();
            } else {
                Snackbar.make(activity_main, "Вход не выполнен", Snackbar.LENGTH_SHORT).show();
                finish();
            }
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.menu_signout)
        {
            AuthUI.getInstance().signOut(this)
                    .addOnCompleteListener(new OnCompleteListener<Void>() {
                        @Override
                        public void onComplete(@NonNull Task<Void> task) {

                            Snackbar.make(activity_main, "Выход выполнен", Snackbar.LENGTH_SHORT).show();
                            finish();

                        }
                    });
        }
        return true;
    }
}

Для начала создадим константу SIGN_IN_REQUEST_CODE со значением 1.

Далее создаем переменную класса FirebaseListAdapter – это дженерик, который обеспечивает поддержку списка сообщений. В качестве параметризированного типа у него будет наш класс Message.

О том, что такое дженерики в java, можно почитать здесь.

Далее объявляем корневой макет экрана и кнопку.

В методе onCreate находим кнопку и корневой RelativeLayout по ID, присваиваем кнопке обработчик нажатия.

В методе onClick определяем поле ввода.

Далее считываем текст из поля ввода и отправляем новый экземпляр сообщения в базу данных Firebase.

Но, прежде чем отправить сообщение. пользователь должен авторизоваться. А если пользователь не авторизован, то ему нужно показать форму авторизации, а не экран чата.

Создать экран авторизации можно с помощью метода startActivityForResult, которому мы передаем интент, создающий и настраивающий окно авторизации, а также константу, хранящую код авторизации.

Создавать окно авторизации мы будем через проверку авторизации пользователя. Обернем этот метод в блок if…else комбинацией Ctrl+Alt+T и пропишем соответствующую проверку.

Если же пользователь авторизован, будем показывать ему экран чата со списком сообщений.

Для этого мы создадим метод displayChat и будем вызывать его здесь.

В методе displayChat создаем список сообщений. Также создаем адаптер списка, используя класс FirebaseListAdapter. Передаем ему контекст, класс модели сообщения, макет пункта списка и экземпляр базы данных Firebase.

Далее в автоматически созданном методе populateView, заполняем пункты списка.

Сначала определяем поля пункта списка по ID.

Затем прописываем текст сообщения, имя пользователя.

Также устанавливаем формат даты и отображаем ее. Обратите внимание – нужно использовать именно этот класс DateFormat.

И наконец, передаем адаптер списку.

Также нам нужно будет показать окно чата после окна авторизации в случае ее успеха. Для этого мы переопределим метод onActivityResult.

Вспоминаем Урок 30 курса основ разработки в Android Studio, где мы подробно рассматриваем этот метод. В двух словах, в метод onActivityResult приходит результат вызова Activity методом startActivityForResult, которым мы вызываем здесь окно авторизации.

Сначала вызываем метод суперкласса. затем проверям, что значение requestCode равно константе SIGN_IN_REQUEST_CODE, которую мы передаем в методе startActivityForResult. Затем мы проверяем, что вызов активити прошел успешно, и отображаем окно чата после оповещения пользователя об удачном входе.

В противном случае показываем уведомление о неудаче пользователю.

И теперь нам осталось реализовать выход пользователя из чата. Сделаем это через меню.

Создаем меню в методе onCreateOptionsMenu.

И переопределяем метод onOptionsItemSelected, где проверяем выбранный пользователем пункт и реализуем выход пользователя из учетной записи чата. В случае успеха отображаем снекбар с уведомлением.

В нашем приложении будет использоваться авторизация по email. Ее нужно активировать в консоли Firebase.
В процессе запуска также возникла ошибка, которая была связана с тем, что у меня было отключено Identity Toolkit API в консоли Google разработчика. для его включения можно перейти по ссылке прямо из ошибки в консоли.
Теперь запустите приложение на разных телефонах, авторизуйтесь и обменивайтесь сообщениями в чате.

Коментарі: 10
  1. mrodonezhskiy@gmail.com

    Добрый день!
    Подскажите пожалуйста в чем может быть проблема( вставил исходный код. Все работает но сообщения не показываются)

    1. admin (автор)

      Зарегистрировали приложение в панели Firebase? Скачали файл json с настройками?

    2. mrodonezhskiy@gmail.com

      файл json с настройками где найти?
      панели Firebase приложение вижу

    3. mrodonezhskiy@gmail.com

      проверил файл google-services.json есть

    4. admin (автор)

      Это мой файл, с моими настройками. Скачайте ваш, предварительно зарегистрировавшись в консоли Firebase. Инструкция есть в одном из уроков.

  2. CoMMoN

    Еще такой вопрос, а можно вот этот адаптер FirebaseListAdapter() сделать костомным?

  3. CoMMoN

    А как сделать чтобы сообщения были левый и правый т.е я пишу у меня справо, мне пишут у меня слева, как сделать так?

  4. Игорь

    Продолжение. Создать список контактов

  5. Игорь

    Здравствуйте! Очень хороший урок. Не совсем понятно для какой цели можно использовать такое приложение? ля чата с самим собой?

    1. admin (автор)

      Чат общий (через сервер) для всех установивших ваше приложение.

Додати коментар