Привет! Мы продолжаем цикл статей по технологиям, представленным на нашем ежегодном мероприятии Google I/O. Сегодня у нас в гостях Александр Денисов, и он расскажет про своё знакомство с базовыми возможностями обновлённого Firebase.
Привет, Я — Александр Денисов, работают в Netcracker на должности Senior Software Developer и возглавляю GDG Нижнего Новгорода. Я давно слежу за технологиями, которые развивает Google, и в этот раз просто не мог пройти мимо Firebase. Изменений много, сами по себе они концептуальные, и, наконец, превращают сам Firebase в один большой сервис по построению мобильного бэкэнда.
Я был сильно впечатлён презентацией, поэтому решил лично попробовать поработать с новыми возможостями и поделиться с вами, как сделать первые шаги в этот потрясающий мир удобной и быстрой разработки под Android.
Предыстория
Совместная история Google и Firebase началась в далеком 2014-м году: тогда команда Firebase в полном составе просто перешла в Google и продолжила работу над своим продуктом, получив доступ к облачным возможностям Google Cloud Platform и другим технологиям Google. В те временасам Firebase представлял собой, по сути, набор REST сервисов для управления облачной NoSQL базой данных, предназначенной для хранения и синхронизации данных между несколькими клиентами, и связанные с ними сервисы аутентификации и
В 2016 году проходит юбилейный, десятый Google I/O, и на нём представляют новые возможности Firebase. Он вырос, повзрослел иразросся до набора из 15 продуктов, предназначенных не только для девелопмента, но также и для облегчения продвижения и монетизации приложений. Эти продукты можно использовать как по отдельности, так и в любых сочетаниях — в зависимости от ваших потребностей.
По-хорошему, каждому из этих продуктов надо быпосвятить отдельную статью, с большим количеством примера и кодов. Но на это нужно время, так что пока мы просто попробуем создать небольшое приложение на основе Firebase, познакомимся с некоторыми (наиболее интересными, на мой взгляд) возможностями и просто постараемся прочувствовать «вкус» технологии… В качестве примера я буду создавать простенький чат, сообщения и информация о собеседниках которого будет храниться в облаке. Вы не поверите, насколько просто стало разрабатывать такие штуки.
1. Firebase — первые шаги
Подготовительные работы, регистрация и авторизация
Создадим простой проект (назовем его com.google.simplefirebasechat) с пустой разметкой. Начало положено, теперь надо связать его с Firebase. Для этого переходим в основной инструмент для управления Firebase — консоль и кликаем «Создать новый проект»:
Поскольку работать мы собираемся с Android-приложением, из предложенных вариантов выбрать нужно именно его.
Система предложит нам указать название нашего приложения и ключ безопасности формата SHA1
Прим.: Если вы выдруг забыли, какой у вас ключ — откройте консоль Windows (Win+R, CMD, Enter) и извлеките его из debug.keystore. Для этого введите следующие команды:
keytool -exportcert -alias androiddebugkey -keystore %USERPROFILE%/.android/debug.keystore -list -v
И авторизуйтесь паролем от вашего отладочного репозитория, (по умолчанию «android»)
После того, как вы предоставите все необходимые данные, браузер автоматически загрузит сгенерированный Firebase конфигурационный json с метаданными, необходимыми для работы. Полученный файл следует положить в каталог app нашего приложения. Теперь в build.gradle уровня проекта необходимо добавить зависимость от соответствующей библиотеки
classpath 'com.google.gms:google-services:3.0.0'
Также следует дописать в конец build.gradle в каталоге app следующую строчку
apply plugin: 'com.google.gms.google-services'
которая подключает к проекту Google Services плагин, необходимый для процессинга конфигурационного JSONa и подключения основных библиотек com.google.gms:google-services.
Вуаля! Firebase связан с приложением, которое можно попробовать собрать!
Проверяем работоспособность Firebase
Следующий шаг — проверить, что всё в порядке, и мы действительно подключились. Для этого мы добавим в базу пару записей, а затем прочитаем их в приложении.
Открываем консоль Firebase, ищем вкладкуDatabase, и создадём в ней простой JSON для хранения сообщений. Интерфейс консоли легко позволяет сделать это.
Теперь прочитаем занесённые данные, запросив их из нашего приложения… Чтобы получить к ним доступ и иметь возможность отображать их в нашем интерфейсе, добавим несколько соответствующих зависимостей в build.gradle
compile 'com.google.firebase:firebase-database:9.0.0'
compile 'com.firebaseui:firebase-ui-database:0.4.0'
compile 'de.hdodenhof:circleimageview:1.3.0'
compile 'com.github.bumptech.glide:glide:3.6.1'
Ещё мы добавим новый класс для сообщения в чате и разметку для его отображения. С необходимыми приготовлениями закончено, теперь займёмся отображением контента, полученного из БД.
Для этого нам потребуется создать основную activity и разместить на ней элементы интерфейса, которые помогут нам проверить работу Firebase. Добавляем поле для ввода сообщения, кнопку Send, ProgressBar на время загрузки данных и RecyclerView для отображения данных из БД, а в сам класс допишем пару строк кода.
Итак, что у нас получилось:
package com.google.simplefirechat;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.InputFilter;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.firebase.database.DatabaseReference;
import com.firebase.ui.database.FirebaseRecyclerAdapter;
import com.google.firebase.database.FirebaseDatabase;
import de.hdodenhof.circleimageview.CircleImageView;
import com.bumptech.glide.Glide;
public class MainActivity extends AppCompatActivity {
private DatabaseReference mSimpleFirechatDatabaseReference;
private FirebaseRecyclerAdapter<ChatMessage, FirechatMsgViewHolder>
mFirebaseAdapter;
private RecyclerView mMessageRecyclerView;
private LinearLayoutManager mLinearLayoutManager;
private ProgressBar mProgressBar;
public static class FirechatMsgViewHolder extends RecyclerView.ViewHolder {
public TextView msgTextView;
public TextView userTextView;
public CircleImageView userImageView;
public FirechatMsgViewHolder(View v) {
super(v);
msgTextView = (TextView) itemView.findViewById(R.id.msgTextView);
userTextView = (TextView) itemView.findViewById(R.id.userTextView);
userImageView = (CircleImageView) itemView.findViewById(R.id.userImageView);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
mMessageRecyclerView = (RecyclerView) findViewById(R.id.messageRecyclerView);
mLinearLayoutManager = new LinearLayoutManager(this);
mLinearLayoutManager.setStackFromEnd(true);
mMessageRecyclerView.setLayoutManager(mLinearLayoutManager);
mSimpleFirechatDatabaseReference = FirebaseDatabase.getInstance().getReference();
mFirebaseAdapter = new FirebaseRecyclerAdapter<ChatMessage,
FirechatMsgViewHolder>(
ChatMessage.class,
R.layout.chat_message,
FirechatMsgViewHolder.class,
mSimpleFirechatDatabaseReference.child("messages")) {
@Override
protected void populateViewHolder(FirechatMsgViewHolder viewHolder, ChatMessage friendlyMessage, int position) {
mProgressBar.setVisibility(ProgressBar.INVISIBLE);
viewHolder.msgTextView.setText(friendlyMessage.getText());
viewHolder.userTextView.setText(friendlyMessage.getName());
if (friendlyMessage.getPhotoUrl() == null) {
viewHolder.userImageView
.setImageDrawable(ContextCompat
.getDrawable(MainActivity.this,
R.drawable.ic_account_circle_black_36dp));
} else {
Glide.with(MainActivity.this)
.load(friendlyMessage.getPhotoUrl())
.into(viewHolder.userImageView);
}
}
};
mFirebaseAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
int chatMessageCount = mFirebaseAdapter.getItemCount();
int lastVisiblePosition =
mLinearLayoutManager.findLastCompletelyVisibleItemPosition();
if (lastVisiblePosition == -1 ||
(positionStart >= (chatMessageCount - 1) &&
lastVisiblePosition == (positionStart - 1))) {
mMessageRecyclerView.scrollToPosition(positionStart);
}
}
});
mMessageRecyclerView.setLayoutManager(mLinearLayoutManager);
mMessageRecyclerView.setAdapter(mFirebaseAdapter);
}
}
На данном этапе мы не добавляли в приложение инструменты по авторизации, поэтому просто предоставимправа на неавторизованное чтение из БД в консоли Firebase. Для этого заходим на вкладку «Правила» и меняем настройки вот таким образом:
Ну и настало время проверить, что из всего этого вышло. Запускаем наше приложение и вот что видим: запись из базы данных появилась как ранее отправленное сообщение.
Теперь мы можем проверить ещё одну штуку. Попробуйте добавить в базу еще несколько сообщений. Если всё сделано правильно, эти сообщенияв реальном времени отобразятся в нашем чате. К сожалению, ответить на них из приложения мы не можем, поэтому заканчиваем развлекаться и приступаем к доработке функциональности приложения.
2. Запись в Firebase
Здесь всё просто. Добавляем в MainActivity обработчик нажатия на кнопку Send:
mSendButton = (Button) findViewById(R.id.sendButton);
mSendButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ChatMessage friendlyMessage = new
ChatMessage(mMsgEditText.getText().toString(),
mUsername,
mPhotoUrl);
mSimpleFirechatDatabaseReference.child("messages")
.push().setValue(friendlyMessage);
mMsgEditText.setText("");
}
});
И это все! Можно снова запускать приложение и баловаться. Пишете текст, нажимаете на кнопку Send, он отображается в чате, и соответствующая запись моментально появляется в БД, которую мы можем посмотреть через консоль Firebase. Осталось добавить последнюю нужную фичу — авторизацию пользователей.
3. Авторизация в Firebase
Первым делом мы забираем доступ к БД у неавторизованных пользователей. Включали-то мы его только для тестов. Переходим в консоль, в раздел «Правила» и выставляем всё как было:
После этого переходим на вкладку Auth и выбираем способ авторизации — в данном случае через Google:
Снова обновим build.gradle, дописав зависимости от авторизационных библиотек
compile 'com.google.firebase:firebase-auth:9.0.0'
compile 'com.google.android.gms:play-services-auth:9.0.0'
Ещё нам понадобится дополнительная Activity для входа в чат, с соответствующей разметкой (AuthorizationActivity.class и activiti_auth.xml). Только не забудьте прописать её в манифесте. Также неплохо было бы иметь меню, чтобы нам было куда добавить опцию выхода из своего аккаунта (main_menu.xml). Ну в качестве завершающего штриха мынемного поправим MainActivity.
Подключаем авторизацию
Во-первых, надо унаследовать Activity от интерфейса GoogleApiClient.OnConnectionFailedListener, ну и реализовать сам этот интерфейс:
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
Toast.makeText(this, "Google Play Services error.", Toast.LENGTH_SHORT).show();
}
Во-вторых, добавить сущности для работы с авторизацией:
этот интерфейс:
private GoogleApiClient mGoogleApiClient;
private FirebaseAuth mFirebaseAuth;
private FirebaseUser mFirechatUser;
И их инициализацию в onCreate:
mGoogleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this /* FragmentActivity */, this /* OnConnectionFailedListener */)
.addApi(Auth.GOOGLE_SIGN_IN_API)
.build();
mFirebaseAuth = FirebaseAuth.getInstance();
mFirechatUser = mFirebaseAuth.getCurrentUser();
if (mFirechatUser == null) {
startActivity(new Intent(this, AuthorizationActivity.class));
finish();
return;
} else {
mUsername = mFirechatUser.getDisplayName();
if (mFirechatUser.getPhotoUrl() != null) {
mPhotoUrl = mFirechatUser.getPhotoUrl().toString();
}
}
В-третьих, добавитьвызов из меню для того, чтобы пользователь мог разлогиниться:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.sign_out_menu:
mFirebaseAuth.signOut();
Auth.GoogleSignInApi.signOut(mGoogleApiClient);
mUsername = DEFAULT_NAME;
startActivity(new Intent(this, AuthorizationActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
Теперь можно запускать и тестировать приложение с авторизацией через Google-аккаунт.
А в консоли Firebase, во вкладке Auth, выможете посмотреть пользователей, которые были авторизованы в нашем приложении:
Итак, у нас есть простое приложение, бэкенд которого полностью сделан на Firebase. Эти возможносты были доступны и ранее, сейчас мы просто познакомились с инструментами Firebase, теперь перейдём к более интересным штукам.
4. Уведомления с помощью Firebase Notifications
Firebase Notifications позволяет отправлять уведомления на пользовательские устройства прямо из консоли Firebase. Причём вы можете выбрать — отправить всем пользователям, какой-то конкретной группе (здесь также работает интеграция в новый инструмент Firebase Аудитории) или вообще пользователям конкретных устройств. Но для работы с этими уведомлениями нужно научить приложение принимать их, а для этого надо сконфигурировать соотвествующий сервис.
Подключаем Firebase Notifications
Для начала добавим в build.gradle соответствующую … (зависимость, команду, whatever)
compile 'com.google.firebase:firebase-messaging:9.0.0'
Далее, создадим сервис SimpleFirechatMessagingService который будем использовать для управления входящими FCM-сообщениями (Firebase Clouds Messages). Нам достаточно переопределить метод onMessageReceived(RemoteMessage remoteMessage), чтобы каким-либо образом обработать входящее уведомление.
Ещё нам потребуется создать сервис SimpleFirechatInstanceIdService для управления FCM логикой. Обычно он используется для оповещении приложения о том, что сгенерирован новый токен, а также для получения этого токена. Просто переопределим метод onTokenRefresh()
Всё что нам осталось — зарегистрировать сервисы в манифесте, после этого всё должно заработать.
<service
android:name=".SimpleFirechatMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service
android:name=".SimpleFirechatInstanceIdService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
Тестируем уведомления
Запускаем приложение, после этого заходим в консоль Firebase, во вкладку Notifications, и отправляем долгожданное сообщение. Результат моментально отобразится на дисплее смартфона или в эмуляторе:
Ознакомится с результатами рассылки уведомлений можно в соответствующем разделе консоли Firebase
5. Firebase Remote Config — изменяем приложение на лету
Firebase Remote Config позволяет вам задавать в приложениях ряд параметров, которые вы сможете обновить удалённо, с сервера. Таким образом вы можете меня возможности вашего приложения (оформление, например, или перевод) не обновляя весь .apk и не дожидаясь выгрузки его в магазин приложений, а потом ещё и обновления у пользователя на устройстве. В качестве примера давайте попробуем изменять надпись на кнопке отправки сообщения, используя эту фичу.
Добавляем функциональность
Для начала зайдем на вкладку Remote Config и создадим переменную button_name
Теперь надо сконфигурировать приложение.
Как и в предыдущие разы, начинаем опять с добавления соответствующей зависимости в build.gradle
compile 'com.google.firebase:firebase-messaging:9.0.0'
… и добавляем метод fetchconfig, в котором мы будем забирать конфигурацию с сервера и применять ее к имени кнопки.
public void fetchConfig() {
long cacheExpiration = 3600; // 1 hour in seconds
// If developer mode is enabled reduce cacheExpiration to 0 so that
// each fetch goes to the server. This should not be used in release
// builds.
if (mFirebaseRemoteConfig.getInfo().getConfigSettings()
.isDeveloperModeEnabled()) {
cacheExpiration = 0;
}
mFirebaseRemoteConfig.fetch(cacheExpiration)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
// Make the fetched config available via
// FirebaseRemoteConfig get<type> calls.
mFirebaseRemoteConfig.activateFetched();
mSendButton.setText(mFirebaseRemoteConfig.getString("button_name"));
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
mSendButton.setText(mFirebaseRemoteConfig.getString("button_name"));
}
});
}
Вызывать этот методы мы будем в onCreate и в созданном для этого пункте меню. Для этого добавляем в main_menu.xml следующие строки:
<item android:id="@+id/reconfig"
android:title="Reconfig"
app:showAsAction="never"/>
В onCreate:
mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
FirebaseRemoteConfigSettings firebaseRemoteConfigSettings =
new FirebaseRemoteConfigSettings.Builder()
.setDeveloperModeEnabled(true)
.build();
Map<String, Object> defaultConfigMap = new HashMap<>();
defaultConfigMap.put("button_name", "Send");
… и в onOptionsItemSelected:
case R.id.reconfig:
fetchConfig();
return true;
Проверяем результат
Алгоритм испытаний аналогичен прошлым. Запускаем приложение, смотрим на текстна кнопке, идём в консоль Firebase, меняем её значение переменной button_name., и вызываем reconfig из меню.
Вот так просто за полчаса мы создали чат с авторизацией, облачными уведомлениями и удалённой конфигурацией на базе возможностей Firebase.
P.S.: скачать проект и поковыряться в нём самому можно вот здесь.
Думаю, на сегодня достаточно практики, время подвести итоги.
Что мы получили в результате
Для хранения сообщений мы использовали Realtime Database — сервисы для управления облачной NoSQL базой данных. Они позволяют сохранять и синхронизировать данные со всеми подключенными устройствами за считанные миллисекунды. В обновлённой версии данные сохраняются в память или на диск устройства, чтобы приложение оставалось работоспособным при потере соединения с сетью, и, само собой, данные синхронизируются после восстановления соединения.
Для авторизации мы использовали сервис Authentication — сервис для идентификации и управления пользователями. Поддерживает аутентификацию через пару Е-mail и пароль, социальные сети (Facebook, Twitter, GitHub, Google+) и может быть интегрирован с другими существующими системами авторизации
Для тестирования уведомлений мы использовали Notifications — сервис, предоставляющий новый UI, доступный через встроенную консоль и основанный на Firebase Cloud Messaging. Он пришел на замену технологии Google Cloud Messaging и даётотправлять уведомления пользователям, не написав ни строки кода. Интеграция с Firebase Analytics позволяетотправлять уведомления заранее настроенным группам пользователей
Его технологии тесно связаны с проектом Cloud Messaging — сервисом, объединяющим Firebase с самым популярным сервисом обмена сообщениями — Google Cloud Messaging. Cloud Messaging кросс-платформенный и работает на iOS, Android и в Web-приложениях.
И, наконец, для удаленного конфигурирования мы использовали Remote Config — сервис, который позволяет изменять и обновлять части приложений, не прибегая к полному обновлению пакета и прохождению сравнительно долгих проверок в магазинах приложений. Для безопасности и простоты работы вам потребуется заранее подумать, что вы хотите обновлять удалённо и задать соответствующие параметры в видепары ключ — значение. Ну а дальше вы сможете вносить изменения в работу и внешний вид приложения путем замены значений данных параметров на стороне сервера. Например, можно обновить оформление приложения к празднику, а потом вернуть всё «как было». И никаких длительных ревью. Как и многие другие элементы Firebase, Notifications интегрируются в Firebase Analytics. То есть вы можете выделять группы различных пользователей (сформированных автоматически по десяткам и сотням параметров) и отправлять обновления именно им…
В заключение хочется сказать, что из сервиса для хранения данных Firebase превратился в универсальную платформу для мобильных разработчиков, с удобным, дружелюбным интерфейсом и максимально упрощенной интеграцией в приложения (как вы видели, добавить его в свое приложение можно фактически парой кликов). Я считаю, что было бы большой ошибкой не обратить внимание на выход такого полезного и интересного инструмента. Попробуйте. Он реально очень и очень крут…
На сегодня с вас хватит. Но мы обязательно вернёмся к этой теме и рассмотрим оставшиеся возможности нового Firebase: инструменты хранения данных, тестирования приложений, сервисы монетизации и рекламы, а также приятные удобные плюшки типа инвайтов или надёжных ссылок. И, разумеется, самую крутую штуку ever — мощнейшую аналитику и группировку пользователей, которая тесно интегрирована во все остальные возможности.
Всем спасибо за внимание! Пост получился очень большой, все мы люди, так что если вы нашли опечатку или ошибку — пишите в ЛС, оперативно поправим. А вопросы оставляйте в комментариях. :)
Автор: Google