Это очередное видео из серии «как создать 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 разработчика. для его включения можно перейти по ссылке прямо из ошибки в консоли.
Теперь запустите приложение на разных телефонах, авторизуйтесь и обменивайтесь сообщениями в чате.
Добрый день!
Подскажите пожалуйста в чем может быть проблема( вставил исходный код. Все работает но сообщения не показываются)
Зарегистрировали приложение в панели Firebase? Скачали файл json с настройками?
файл json с настройками где найти?
панели Firebase приложение вижу
проверил файл google-services.json есть
Это мой файл, с моими настройками. Скачайте ваш, предварительно зарегистрировавшись в консоли Firebase. Инструкция есть в одном из уроков.
Еще такой вопрос, а можно вот этот адаптер FirebaseListAdapter() сделать костомным?
А как сделать чтобы сообщения были левый и правый т.е я пишу у меня справо, мне пишут у меня слева, как сделать так?
Продолжение. Создать список контактов
Здравствуйте! Очень хороший урок. Не совсем понятно для какой цели можно использовать такое приложение? ля чата с самим собой?
Чат общий (через сервер) для всех установивших ваше приложение.