Если вы являетесь пользователем Parse и ищете альтернативное решение Backend as a Service, Firebase может стать идеальным выбором для вашего приложения для Android.
В этом руководстве описывается, как интегрировать определенные сервисы в ваше приложение. Основные инструкции по настройке Firebase см. в руководстве по настройке Android .
Google Analytics
Google Analytics — это бесплатное решение для измерения приложений, которое дает представление об использовании приложений и взаимодействии с пользователями. Analytics интегрируется со всеми функциями Firebase и предоставляет вам неограниченные отчеты по 500 различным событиям, которые вы можете определить с помощью Firebase SDK.
Дополнительную информацию см. в документации Google Analytics .
Предлагаемая стратегия миграции
Использование разных поставщиков аналитики — распространенный сценарий, который легко применим к Google Analytics . Просто добавьте его в свое приложение, чтобы получать выгоду от событий и свойств пользователя, которые автоматически собирает Analytics , таких как первое открытие, обновление приложения, модель устройства, возраст.
Для пользовательских событий и свойств пользователя вы можете использовать стратегию двойной записи, используя как Parse Analytics, так и Google Analytics для регистрации событий и свойств, что позволяет постепенно развертывать новое решение.
Сравнение кода
Анализ аналитики
// Start collecting data
ParseAnalytics.trackAppOpenedInBackground(getIntent());
Map<String, String> dimensions = new HashMap<String, String>();
// Define ranges to bucket data points into meaningful segments
dimensions.put("priceRange", "1000-1500");
// Did the user filter the query?
dimensions.put("source", "craigslist");
// Do searches happen more often on weekdays or weekends?
dimensions.put("dayType", "weekday");
// Send the dimensions to Parse along with the 'search' event
ParseAnalytics.trackEvent("search", dimensions);
Google Analytics
// Obtain the FirebaseAnalytics instance and start collecting data
mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);
Bundle params = new Bundle();
// Define ranges to bucket data points into meaningful segments
params.putString("priceRange", "1000-1500");
// Did the user filter the query?
params.putString("source", "craigslist");
// Do searches happen more often on weekdays or weekends?
params.putString("dayType", "weekday");
// Send the event
mFirebaseAnalytics.logEvent("search", params);
Firebase Realtime Database
Firebase Realtime Database — это база данных NoSQL, размещенная в облаке. Данные хранятся в формате JSON и синхронизируются в режиме реального времени с каждым подключенным клиентом.
Дополнительную информацию см. в документации Firebase Realtime Database .
Различия с анализом данных
Объекты
В Parse вы храните ParseObject
или его подкласс, который содержит пары ключ-значение JSON-совместимых данных. Данные не имеют схемы, что означает, что вам не нужно указывать, какие ключи существуют для каждого ParseObject
.
Все данные Firebase Realtime Database хранятся в виде объектов JSON, и для ParseObject
не существует эквивалента; вы просто записываете в дерево JSON значения типов, которые соответствуют доступным типам JSON. Вы можете использовать объекты Java для упрощения чтения и записи из базы данных.
Ниже приведен пример того, как можно сохранить рекорды в игре.
Разобрать
@ParseClassName("GameScore")
public class GameScore {
public GameScore() {}
public GameScore(Long score, String playerName, Boolean cheatMode) {
setScore(score);
setPlayerName(playerName);
setCheatMode(cheatMode);
}
public void setScore(Long score) {
set("score", score);
}
public Long getScore() {
return getLong("score");
}
public void setPlayerName(String playerName) {
set("playerName", playerName);
}
public String getPlayerName() {
return getString("playerName");
}
public void setCheatMode(Boolean cheatMode) {
return set("cheatMode", cheatMode);
}
public Boolean getCheatMode() {
return getBoolean("cheatMode");
}
}
// Must call Parse.registerSubclass(GameScore.class) in Application.onCreate
GameScore gameScore = new GameScore(1337, "Sean Plott", false);
gameScore.saveInBackground();
Огневая база
// Assuming we defined the GameScore class as:
public class GameScore {
private Long score;
private String playerName;
private Boolean cheatMode;
public GameScore() {}
public GameScore(Long score, String playerName, Boolean cheatMode) {
this.score = score;
this.playerName = playerName;
this.cheatMode = cheatMode;
}
public Long getScore() {
return score;
}
public String getPlayerName() {
return playerName;
}
public Boolean getCheatMode() {
return cheatMode;
}
}
// We would save it to our list of high scores as follows:
DatabaseReference mFirebaseRef = FirebaseDatabase.getInstance().getReference();
GameScore score = new GameScore(1337, "Sean Plott", false);
mFirebaseRef.child("scores").push().setValue(score);
Отношения между данными
ParseObject
может иметь связь с другим ParseObject
: любой объект может использовать другие объекты в качестве значений.
В Firebase Realtime Database отношения лучше выражаются с помощью плоских структур данных, которые разбивают данные на отдельные пути, чтобы их можно было эффективно загружать отдельными вызовами.
Ниже приведен пример того, как вы можете структурировать отношения между сообщениями в приложении для ведения блога и их авторами.
Разобрать
// Create the author
ParseObject myAuthor = new ParseObject("Author");
myAuthor.put("name", "Grace Hopper");
myAuthor.put("birthDate", "December 9, 1906");
myAuthor.put("nickname", "Amazing Grace");
// Create the post
ParseObject myPost = new ParseObject("Post");
myPost.put("title", "Announcing COBOL, a New Programming Language");
// Add a relation between the Post and the Author
myPost.put("parent", myAuthor);
// This will save both myAuthor and myPost
myPost.saveInBackground();
Огневая база
DatabaseReference firebaseRef = FirebaseDatabase.getInstance().getReference();
// Create the author
Map<String, String> myAuthor = new HashMap<String, String>();
myAuthor.put("name", "Grace Hopper");
myAuthor.put("birthDate", "December 9, 1906");
myAuthor.put("nickname", "Amazing Grace");
// Save the author
String myAuthorKey = "ghopper";
firebaseRef.child('authors').child(myAuthorKey).setValue(myAuthor);
// Create the post
Map<String, String> post = new HashMap<String, String>();
post.put("author", myAuthorKey);
post.put("title", "Announcing COBOL, a New Programming Language");
firebaseRef.child('posts').push().setValue(post);
Результатом является следующая структура данных.
{ // Info about the authors "authors": { "ghopper": { "name": "Grace Hopper", "date_of_birth": "December 9, 1906", "nickname": "Amazing Grace" }, ... }, // Info about the posts: the "author" fields contains the key for the author "posts": { "-JRHTHaIs-jNPLXOQivY": { "author": "ghopper", "title": "Announcing COBOL, a New Programming Language" } ... } }
Чтение данных
В Parse вы читаете данные, используя либо идентификатор конкретного объекта Parse, либо выполняя запросы с помощью ParseQuery
.
В Firebase вы получаете данные, присоединяя асинхронный прослушиватель к ссылке на базу данных. Прослушиватель запускается один раз при начальном состоянии данных и еще раз при изменении данных, поэтому вам не нужно будет добавлять какой-либо код, чтобы определить, изменились ли данные.
Ниже приведен пример того, как можно получить результаты для конкретного игрока на основе примера, представленного в разделе «Объекты» .
Разобрать
ParseQuery<ParseObject> query = ParseQuery.getQuery("GameScore");
query.whereEqualTo("playerName", "Dan Stemkoski");
query.findInBackground(new FindCallback<ParseObject>() {
public void done(List<ParseObject> scoreList, ParseException e) {
if (e == null) {
for (ParseObject score: scoreList) {
Log.d("score", "Retrieved: " + Long.toString(score.getLong("score")));
}
} else {
Log.d("score", "Error: " + e.getMessage());
}
}
});
Огневая база
DatabaseReference mFirebaseRef = FirebaseDatabase.getInstance().getReference();
Query mQueryRef = mFirebaseRef.child("scores").orderByChild("playerName").equalTo("Dan Stemkoski");
// This type of listener is not one time, and you need to cancel it to stop
// receiving updates.
mQueryRef.addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot snapshot, String previousChild) {
// This will fire for each matching child node.
GameScore score = snapshot.getValue(GameScore.class);
Log.d("score", "Retrieved: " + Long.toString(score.getScore());
}
});
Предлагаемая стратегия миграции
Переосмыслите свои данные
Firebase Realtime Database оптимизирована для синхронизации данных за миллисекунды между всеми подключенными клиентами, а результирующая структура данных отличается от основных данных Parse. Это означает, что первым шагом вашей миграции является рассмотрение того, какие изменения потребуются вашим данным, в том числе:
- Как ваши объекты Parse должны сопоставляться с данными Firebase
- Если у вас есть отношения родитель-потомок, как разделить ваши данные по разным путям, чтобы их можно было эффективно загружать отдельными вызовами.
Перенесите ваши данные
После того, как вы решите, как структурировать свои данные в Firebase, вам необходимо спланировать, как обрабатывать период, в течение которого вашему приложению необходимо выполнять запись в обе базы данных. Ваш выбор:
Фоновая синхронизация
В этом сценарии у вас есть две версии приложения: старая версия, использующая Parse, и новая версия, использующая Firebase. Синхронизация между двумя базами данных осуществляется с помощью Parse Cloud Code (Parse to Firebase), при этом ваш код прослушивает изменения в Firebase и синхронизирует эти изменения с Parse. Прежде чем вы сможете начать использовать новую версию, вам необходимо:
- Преобразуйте существующие данные анализа в новую структуру Firebase и запишите их в Firebase Realtime Database .
- Напишите функции синтаксического облачного кода, которые используют REST API Firebase для записи в Firebase Realtime Database изменений, внесенных в данные синтаксического анализа старыми клиентами.
- Напишите и разверните код, который прослушивает изменения в Firebase и синхронизирует их с базой данных Parse.
Этот сценарий обеспечивает четкое разделение старого и нового кода и упрощает работу клиентов. Проблемы этого сценария заключаются в обработке больших наборов данных при первоначальном экспорте и обеспечении того, чтобы двунаправленная синхронизация не приводила к бесконечной рекурсии.
Двойная запись
В этом сценарии вы пишете новую версию приложения, которая использует как Firebase, так и Parse, используя Parse Cloud Code для синхронизации изменений, внесенных старыми клиентами, из данных анализа в Firebase Realtime Database . Когда достаточное количество людей перейдет с версии приложения только для синтаксического анализа, вы можете удалить код синтаксического анализа из версии с двойной записью.
Этот сценарий не требует кода на стороне сервера. Его недостатки заключаются в том, что данные, к которым нет доступа, не переносятся, а размер вашего приложения увеличивается за счет использования обоих SDK.
Firebase Authentication
Firebase Authentication может аутентифицировать пользователей с помощью паролей и популярных поставщиков федеративных удостоверений, таких как Google, Facebook и Twitter. Он также предоставляет библиотеки пользовательского интерфейса, позволяющие сэкономить значительные инвестиции, необходимые для реализации и поддержки полной аутентификации вашего приложения на всех платформах.
Дополнительную информацию см. в документации Firebase Authentication .
Различия с аутентификацией синтаксического анализа
Parse предоставляет специализированный пользовательский класс ParseUser
, который автоматически выполняет функции, необходимые для управления учетными записями пользователей. ParseUser
— это подкласс ParseObject
, что означает, что пользовательские данные доступны в Parse Data и могут быть расширены дополнительными полями, как и любой другой ParseObject
.
FirebaseUser
имеет фиксированный набор основных свойств — уникальный идентификатор, основной адрес электронной почты, имя и URL-адрес фотографии — которые хранятся в базе данных пользователей отдельного проекта; эти свойства могут быть обновлены пользователем. Вы не можете напрямую добавлять другие свойства к объекту FirebaseUser
; вместо этого вы можете сохранить дополнительные свойства в Firebase Realtime Database .
Ниже приведен пример того, как можно зарегистрировать пользователя и добавить дополнительное поле номера телефона.
Разобрать
ParseUser user = new ParseUser();
user.setUsername("my name");
user.setPassword("my pass");
user.setEmail("email@example.com");
// other fields can be set just like with ParseObject
user.put("phone", "650-253-0000");
user.signUpInBackground(new SignUpCallback() {
public void done(ParseException e) {
if (e == null) {
// Hooray! Let them use the app now.
} else {
// Sign up didn't succeed. Look at the ParseException
// to figure out what went wrong
}
}
});
Огневая база
FirebaseAuth mAuth = FirebaseAuth.getInstance();
mAuth.createUserWithEmailAndPassword("email@example.com", "my pass")
.continueWithTask(new Continuation<AuthResult, Task<Void>> {
@Override
public Task<Void> then(Task<AuthResult> task) {
if (task.isSuccessful()) {
FirebaseUser user = task.getResult().getUser();
DatabaseReference firebaseRef = FirebaseDatabase.getInstance().getReference();
return firebaseRef.child("users").child(user.getUid()).child("phone").setValue("650-253-0000");
} else {
// User creation didn't succeed. Look at the task exception
// to figure out what went wrong
Log.w(TAG, "signInWithEmail", task.getException());
}
}
});
Предлагаемая стратегия миграции
Перенос учетных записей
Чтобы перенести учетные записи пользователей из Parse в Firebase, экспортируйте свою базу данных пользователей в файл JSON или CSV, а затем импортируйте файл в свой проект Firebase с помощью команды auth:import
Firebase CLI.
Сначала экспортируйте свою пользовательскую базу данных из консоли Parse или из собственной базы данных. Например, файл JSON, экспортированный из консоли Parse, может выглядеть следующим образом:
{ // Username/password user "bcryptPassword": "$2a$10$OBp2hxB7TaYZgKyTiY48luawlTuYAU6BqzxJfpHoJMdZmjaF4HFh6", "email": "user@example.com", "username": "testuser", "objectId": "abcde1234", ... }, { // Facebook user "authData": { "facebook": { "access_token": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "expiration_date": "2017-01-02T03:04:05.006Z", "id": "1000000000" } }, "username": "wXyZ987654321StUv", "objectId": "fghij5678", ... }
Затем преобразуйте экспортированный файл в формат, требуемый интерфейсом командной строки Firebase. Используйте objectId
ваших пользователей Parse в качестве localId
ваших пользователей Firebase. Кроме того, base64 кодирует значения bcryptPassword
из Parse и использует их в поле passwordHash
. Например:
{ "users": [ { "localId": "abcde1234", // Parse objectId "email": "user@example.com", "displayName": "testuser", "passwordHash": "JDJhJDEwJE9CcDJoeEI3VGFZWmdLeVRpWTQ4bHVhd2xUdVlBVTZCcXp4SmZwSG9KTWRabWphRjRIRmg2", }, { "localId": "fghij5678", // Parse objectId "displayName": "wXyZ987654321StUv", "providerUserInfo": [ { "providerId": "facebook.com", "rawId": "1000000000", // Facebook ID } ] } ] }
Наконец, импортируйте преобразованный файл с помощью Firebase CLI, указав bcrypt в качестве алгоритма хеширования:
firebase auth:import account_file.json --hash-algo=BCRYPT
Перенос пользовательских данных
Если вы храните дополнительные данные для своих пользователей, вы можете перенести их в Firebase Realtime Database используя стратегии, описанные в разделе переноса данных . Если вы переносите учетные записи, используя процедуру, описанную в разделе «Миграция учетных записей» , ваши учетные записи Firebase будут иметь те же идентификаторы, что и ваши учетные записи Parse, что позволяет вам легко переносить и воспроизводить любые связи, связанные с идентификатором пользователя.
Firebase Cloud Messaging
Firebase Cloud Messaging ( FCM ) — это кроссплатформенное решение для обмена сообщениями, которое позволяет надежно и бесплатно доставлять сообщения. Компоновщик уведомлений – это бесплатная служба, созданная на базе Firebase Cloud Messaging , которая позволяет отправлять целевые пользовательские уведомления разработчикам мобильных приложений.
Дополнительную информацию см . в документации Firebase Cloud Messaging .
Различия с анализом push-уведомлений
Каждое приложение Parse, установленное на устройстве, зарегистрированном для уведомлений, имеет связанный объект Installation
, в котором хранятся все данные, необходимые для таргетирования уведомлений. Installation
является подклассом ParseUser
, что означает, что вы можете добавлять любые дополнительные данные в свои экземпляры Installation
.
Компоновщик уведомлений предоставляет предварительно определенные сегменты пользователей на основе такой информации, как приложение, версия приложения и язык устройства. Вы можете создавать более сложные сегменты пользователей, используя события и свойства Google Analytics для создания аудиторий. Чтобы узнать больше, ознакомьтесь со справочным руководством по аудиториям . Эта информация о таргетинге не отображается в Firebase Realtime Database .
Предлагаемая стратегия миграции
Миграция токенов устройств
На момент написания Parse Android SDK использует более старую версию регистрационных токенов FCM, несовместимую с функциями, предлагаемыми композитором уведомлений.
Вы можете получить новый токен, добавив FCM SDK в свое приложение; однако это может сделать недействительным токен, используемый Parse SDK для получения уведомлений. Если вы хотите избежать этого, вы можете настроить Parse SDK на использование как идентификатора отправителя Parse, так и вашего идентификатора отправителя. Таким образом вы не сделаете недействительным токен, используемый Parse SDK, но имейте в виду, что этот обходной путь перестанет работать, когда Parse закроет свой проект.
Перенос каналов в темы FCM
Если вы используете каналы Parse для отправки уведомлений, вы можете перейти на темы FCM, которые предоставляют ту же модель издатель-подписчик. Чтобы обработать переход от Parse к FCM, вы можете написать новую версию приложения, которая использует Parse SDK для отказа от подписки на каналы Parse и FCM SDK для подписки на соответствующие темы FCM. В этой версии приложения вам следует отключить получение уведомлений в Parse SDK, удалив из манифеста вашего приложения следующее:
<service android:name="com.parse.PushService" />
<receiver android:name="com.parse.ParsePushBroadcastReceiver"
android:exported="false">
<intent-filter>
<action android:name="com.parse.push.intent.RECEIVE" />
<action android:name="com.parse.push.intent.DELETE" />
<action android:name="com.parse.push.intent.OPEN" />
</intent-filter>
</receiver>
<receiver android:name="com.parse.GcmBroadcastReceiver"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<!--
IMPORTANT: Change "com.parse.starter" to match your app's package name.
-->
<category android:name="com.parse.starter" />
</intent-filter>
</receiver>
<!--
IMPORTANT: Change "YOUR_SENDER_ID" to your GCM Sender Id.
-->
<meta-data android:name="com.parse.push.gcm_sender_id"
android:value="id:YOUR_SENDER_ID" />;
Например, если ваш пользователь подписан на тему «Гиганты», вы должны сделать что-то вроде:
ParsePush.unsubscribeInBackground("Giants", new SaveCallback() {
@Override
public void done(ParseException e) {
if (e == null) {
FirebaseMessaging.getInstance().subscribeToTopic("Giants");
} else {
// Something went wrong unsubscribing
}
}
});
Используя эту стратегию, вы можете отправлять сообщения как на канал Parse, так и на соответствующую тему FCM , поддерживая пользователей как старых, так и новых версий. Когда достаточное количество пользователей перейдет из версии приложения только для Parse, вы можете закрыть эту версию и начать отправку только с использованием FCM .
Дополнительную информацию см . в документации по темам FCM .
Firebase Remote Config
Firebase Remote Config — это облачный сервис, который позволяет изменять поведение и внешний вид вашего приложения, не требуя от пользователей загрузки обновления приложения. При использовании Remote Config вы создаете в приложении значения по умолчанию, которые управляют поведением и внешним видом вашего приложения. Затем вы можете позже использовать консоль Firebase, чтобы переопределить значения по умолчанию в приложении для всех пользователей приложения или для сегментов вашей пользовательской базы.
Firebase Remote Config может быть очень полезен во время миграции в тех случаях, когда вы хотите протестировать различные решения и иметь возможность динамически переводить больше клиентов к другому провайдеру. Например, если у вас есть версия вашего приложения, которая использует для данных как Firebase, так и Parse, вы можете использовать правило случайного процентиля, чтобы определить, какие клиенты читают из Firebase, и постепенно увеличивать процент.
Чтобы узнать больше о Firebase Remote Config , см. введение Remote Config .
Различия с конфигурацией синтаксического анализа
С помощью конфигурации Parse вы можете добавить пары ключ/значение в свое приложение на панели управления Parse Config, а затем получить ParseConfig
на клиенте. Каждый экземпляр ParseConfig
, который вы получаете, всегда неизменяем. Когда вы в будущем получите новый ParseConfig
из сети, он не изменит ни один существующий экземпляр ParseConfig
, а вместо этого создаст новый и сделает его доступным через getCurrentConfig()
.
С помощью Firebase Remote Config вы создаете в приложении значения по умолчанию для пар ключ/значение, которые можно переопределить из консоли Firebase, а также можете использовать правила и условия, чтобы предоставлять варианты взаимодействия с пользователем вашего приложения для разных сегментов вашей пользовательской базы. Firebase Remote Config реализует одноэлементный класс, который делает пары ключ/значение доступными для вашего приложения. Первоначально синглтон возвращает значения по умолчанию, которые вы определяете в приложении. Вы можете получить новый набор значений с сервера в любой удобный для вашего приложения момент; после того, как новый набор будет успешно получен, вы можете выбрать, когда его активировать, чтобы новые значения были доступны приложению.
Предлагаемая стратегия миграции
Вы можете перейти к Firebase Remote Config , скопировав пары ключ/значение вашей конфигурации Parse в консоль Firebase, а затем развернув новую версию приложения, которое использует Firebase Remote Config .
Если вы хотите поэкспериментировать как с Parse Config, так и Firebase Remote Config , вы можете развернуть новую версию приложения, использующую оба SDK, до тех пор, пока достаточное количество пользователей не перейдет с версии только с Parse.
Сравнение кода
Разобрать
ParseConfig.getInBackground(new ConfigCallback() {
@Override
public void done(ParseConfig config, ParseException e) {
if (e == null) {
Log.d("TAG", "Yay! Config was fetched from the server.");
} else {
Log.e("TAG", "Failed to fetch. Using Cached Config.");
config = ParseConfig.getCurrentConfig();
}
// Get the message from config or fallback to default value
String welcomeMessage = config.getString("welcomeMessage", "Welcome!");
}
});
Огневая база
mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
// Set defaults from an XML resource file stored in res/xml
mFirebaseRemoteConfig.setDefaults(R.xml.remote_config_defaults);
mFirebaseRemoteConfig.fetch()
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
Log.d("TAG", "Yay! Config was fetched from the server.");
// Once the config is successfully fetched it must be activated before newly fetched
// values are returned.
mFirebaseRemoteConfig.activateFetched();
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception exception) {
Log.e("TAG", "Failed to fetch. Using last fetched or default.");
}
})
// ...
// When this is called, the value of the latest fetched and activated config is returned;
// if there's none, the default value is returned.
String welcomeMessage = mFirebaseRemoteConfig.getString("welcomeMessage");