Как создать андроид приложение для чата с помощью 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
    mrodonezhskiy@gmail.com

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

    1. Виталий Непочатов
      admin (автор)

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

    2. mrodonezhskiy@gmail.com
      mrodonezhskiy@gmail.com

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

    3. mrodonezhskiy@gmail.com
      mrodonezhskiy@gmail.com

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

    4. Виталий Непочатов
      admin (автор)

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

  2. CoMMoN
    CoMMoN

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

  3. CoMMoN
    CoMMoN

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

  4. Игорь
    Игорь

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

  5. Игорь
    Игорь

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

    1. Виталий Непочатов
      admin (автор)

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

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