Это очередное видео из серии “как создать 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() сделать костомным?
А как сделать чтобы сообщения были левый и правый т.е я пишу у меня справо, мне пишут у меня слева, как сделать так?
Продолжение. Создать список контактов
Здравствуйте! Очень хороший урок. Не совсем понятно для какой цели можно использовать такое приложение? ля чата с самим собой?
Чат общий (через сервер) для всех установивших ваше приложение.