По следам Google I-O 2016 — новый Firebase: интеграция с Android

в 16:40, , рубрики: android, firebase, Блог компании Google, разработка мобильных приложений, Разработка под android, разработка под iOS, Тестирование мобильных приложений

Привет! Мы продолжаем цикл статей по технологиям, представленным на нашем ежегодном мероприятии Google I/O. Сегодня у нас в гостях Александр Денисов, и он расскажет про своё знакомство с базовыми возможностями обновлённого Firebase.

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 1

Привет, Я — Александр Денисов, работают в Netcracker на должности Senior Software Developer и возглавляю GDG Нижнего Новгорода. Я давно слежу за технологиями, которые развивает Google, и в этот раз просто не мог пройти мимо Firebase. Изменений много, сами по себе они концептуальные, и, наконец, превращают сам Firebase в один большой сервис по построению мобильного бэкэнда.

Я был сильно впечатлён презентацией, поэтому решил лично попробовать поработать с новыми возможостями и поделиться с вами, как сделать первые шаги в этот потрясающий мир удобной и быстрой разработки под Android.

Предыстория

Совместная история Google и Firebase началась в далеком 2014-м году: тогда команда Firebase в полном составе просто перешла в Google и продолжила работу над своим продуктом, получив доступ к облачным возможностям Google Cloud Platform и другим технологиям Google. В те временасам Firebase представлял собой, по сути, набор REST сервисов для управления облачной NoSQL базой данных, предназначенной для хранения и синхронизации данных между несколькими клиентами, и связанные с ними сервисы аутентификации и хостинга. Выглядело это примерно так:

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 2

В 2016 году проходит юбилейный, десятый Google I/O, и на нём представляют новые возможности Firebase. Он вырос, повзрослел иразросся до набора из 15 продуктов, предназначенных не только для девелопмента, но также и для облегчения продвижения и монетизации приложений. Эти продукты можно использовать как по отдельности, так и в любых сочетаниях — в зависимости от ваших потребностей.

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 3

По-хорошему, каждому из этих продуктов надо быпосвятить отдельную статью, с большим количеством примера и кодов. Но на это нужно время, так что пока мы просто попробуем создать небольшое приложение на основе Firebase, познакомимся с некоторыми (наиболее интересными, на мой взгляд) возможностями и просто постараемся прочувствовать «вкус» технологии… В качестве примера я буду создавать простенький чат, сообщения и информация о собеседниках которого будет храниться в облаке. Вы не поверите, насколько просто стало разрабатывать такие штуки.

1. Firebase — первые шаги

Подготовительные работы, регистрация и авторизация

Создадим простой проект (назовем его com.google.simplefirebasechat) с пустой разметкой. Начало положено, теперь надо связать его с Firebase. Для этого переходим в основной инструмент для управления Firebase — консоль и кликаем «Создать новый проект»:

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 4

Поскольку работать мы собираемся с Android-приложением, из предложенных вариантов выбрать нужно именно его.

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 5

Система предложит нам указать название нашего приложения и ключ безопасности формата SHA1

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 6

Прим.: Если вы выдруг забыли, какой у вас ключ — откройте консоль 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 для хранения сообщений. Интерфейс консоли легко позволяет сделать это.

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 7

Теперь прочитаем занесённые данные, запросив их из нашего приложения… Чтобы получить к ним доступ и иметь возможность отображать их в нашем интерфейсе, добавим несколько соответствующих зависимостей в 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. Для этого заходим на вкладку «Правила» и меняем настройки вот таким образом:

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 8

Ну и настало время проверить, что из всего этого вышло. Запускаем наше приложение и вот что видим: запись из базы данных появилась как ранее отправленное сообщение.

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 9

Теперь мы можем проверить ещё одну штуку. Попробуйте добавить в базу еще несколько сообщений. Если всё сделано правильно, эти сообщенияв реальном времени отобразятся в нашем чате. К сожалению, ответить на них из приложения мы не можем, поэтому заканчиваем развлекаться и приступаем к доработке функциональности приложения.

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

Первым делом мы забираем доступ к БД у неавторизованных пользователей. Включали-то мы его только для тестов. Переходим в консоль, в раздел «Правила» и выставляем всё как было:

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 10

После этого переходим на вкладку Auth и выбираем способ авторизации — в данном случае через Google:

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 11

Снова обновим 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-аккаунт.

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 12

Cообщения должны иметь следующий вид:

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 13

А в консоли Firebase, во вкладке Auth, выможете посмотреть пользователей, которые были авторизованы в нашем приложении:

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 14

Итак, у нас есть простое приложение, бэкенд которого полностью сделан на 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, и отправляем долгожданное сообщение. Результат моментально отобразится на дисплее смартфона или в эмуляторе:

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 15
По следам Google I-O 2016 — новый Firebase: интеграция с Android - 16

Ознакомится с результатами рассылки уведомлений можно в соответствующем разделе консоли Firebase

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 17

5. Firebase Remote Config — изменяем приложение на лету

Firebase Remote Config позволяет вам задавать в приложениях ряд параметров, которые вы сможете обновить удалённо, с сервера. Таким образом вы можете меня возможности вашего приложения (оформление, например, или перевод) не обновляя весь .apk и не дожидаясь выгрузки его в магазин приложений, а потом ещё и обновления у пользователя на устройстве. В качестве примера давайте попробуем изменять надпись на кнопке отправки сообщения, используя эту фичу.

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 18

Добавляем функциональность

Для начала зайдем на вкладку Remote Config и создадим переменную button_name

По следам Google I-O 2016 — новый Firebase: интеграция с Android - 19

Теперь надо сконфигурировать приложение.
Как и в предыдущие разы, начинаем опять с добавления соответствующей зависимости в 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 I-O 2016 — новый Firebase: интеграция с Android - 20

Всем спасибо за внимание! Пост получился очень большой, все мы люди, так что если вы нашли опечатку или ошибку — пишите в ЛС, оперативно поправим. А вопросы оставляйте в комментариях. :)

Автор: Google

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js