Vývoj mobilních aplikací na platformách Backend as a Service

Srovnání konkurenčních platform SynergyKit a Firebase na příkladu vývoje mobilní chatovací aplikace, která podporuje real-time zprávy, push notifikace a přihlášení přes facebookový účet. Přikládám ukázky kódů i odkazy na zdrojové kódy koncových aplikací.
Nálepky:
Real-time aplikace se v poslední době stávají stále populárnějšími. Vývoj takových aplikací je ale mnohem složitější, než se může na první pohled zdát. Aby se jejich implementace značně zjednodušila, vznikají nové real-time platformy. Vývojář se poté nemusí zabývat synchronizací dat se serverem a ušetří při vývoji produktů až 80 % času, který místo toho může věnovat UX/UI.
Světově nejznámější real-time platformou je pravděpodobně Firebase, který se v současnosti pyšní přes 200 tisíc registrovanými vývojáři. V české společnosti Letsgood.com s.r.o. (součást Etnetera Group) vzniká nová platforma SynergyKit aktuálně se nacházející v public beta režimu.
Disclaimer: Autoři článku se podílí na vývoji SynergyKitu.
Obstojí SynergyKit vedle Firebase?
Pro porování jsem použil velmi častý use case, který je součástí chatovací aplikace. Aplikace zobrazuje real-time zprávy od uživatelů a pokud není aplikace v popředí, zprávy jsou doručovány prostřednictvím push notifikací. Registrace a přihlášení do aplikace probíhá prostřednictvím facebookového účtu.
Vzorovou aplikaci včetně zdrojových kódů lze najít na GitHubu.
Ukázka aplikace
Vzhled aplikace je založen na ukázkové aplikaci k SynergyKitu s drobnými úpravami.
Instalace
SynergyKit
SynergyKit SDK pro Android potřebuje minimální verzi Android SDK 14.
Do souboru build.gradle
v elementu dependencies jsem přidal následující závislost.
dependencies {
...
compile 'com.letsgood:synergykit-sdk-android:2.1.7'
}
Jelikož aplikace využívá přístup k internetu, musím požádat Android o práva k využívání internetu. To provedu v souboru AndroidManifest.xml
, kam vložím tento kód do elementu manifest.
<manifest … >
<uses-permission android:name="android.permission.INTERNET" />
...
</manifest>
Dále musím inicializovat SynergyKit SDK před jeho prvním použitím. Doporučuji provést inicializaci v metodě onCreate třídy rozšiřující třídu Application.
public void onCreate() {
super.onCreate();
Synergykit.init(APPLICATION_TENANT, APPLICATION_KEY);
...
}
Firebase
Firebase SDK pro Android potřebuje minimální verzi Android SDK 10.
Do souboru build.gradle
přidám následující závislost a packigingOptions.
android {
...
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE-FIREBASE.txt'
exclude 'META-INF/NOTICE'
}
}
dependencies {
...
compile 'com.firebase:firebase-client-android:2.3.0'
}
Jelikož aplikace využívá přístup k internetu, musím požádat Android o práva k využívání internetu. To udělám v souboru AndroidManifest.xml
, kam vložím následující kód do elementu manifest.
<manifest … >
<uses-permission android:name="android.permission.INTERNET" />
...
</manifest>
Firebase potřebuje pro svůj běh Context, který je třeba předat před prvním použitím. Doporučuji ho předat v metodě onCreate třídy rozšiřující Application.
public void onCreate() {
super.onCreate();
Firebase.setAndroidContext(this);
...
}
Přihlášení uživatele
Uživatel se přihlašuje přes Facebook, odkud se získá jeho jméno, pod kterým se budou odesílat zprávy. U každého uživatele se ukládá informace o jeho online stavu.
SynergyKit
SDK nepodporuje získání jména z facebookového AccessTokenu, takže před přihlášením musím toto jméno získat. Získání jména provedu přes GraphRequest. V proměnné name se ukládá jméno uživatele.
GraphRequest request = GraphRequest.newMeRequest(AccessToken.getCurrentAccessToken(),
new GraphRequest.GraphJSONObjectCallback() {
@Override
public void onCompleted(JSONObject object, GraphResponse response) {
String name = null;
try {
name = object.getString("name"); // vytažení jména z JSONObjectu
} catch (JSONException e) {
e.printStackTrace();
}
}
});
Bundle parameters = new Bundle();
parameters.putString("fields", "name"); // požádáno o přidání jména do JSONObjectu
request.setParameters(parameters);
request.executeAsync();
V parametru user předám SynergykitUser, kde se nastaví jméno a online stav. A data budou uložena.
Synergykit.linkFacebook(user, synergykitFacebookAuthData, new UserResponseListener() {
@Override
public void doneCallback(int statusCode, SynergykitUser user) {
// prihlaseni probehlo uspesne
}
@Override
public void errorCallback(int statusCode, SynergykitError errorObject) {
// nastal error
}
});
Firebase
Přihlášení přes Firebase je jednodušší o to, že nemusím získávat jméno uživatele přes GraphRequest. Toto řeší samotné Firebase SDK pro Android.
private Firebase firebase; // přes tento atribut se budou provádět všechny změny
…
firebase = new Firebase("https://<APP_URL>.firebaseio.com"); // inicializace v onCreate
...
firebase.authWithOAuthToken("facebook", accessToken.getToken(), new Firebase.AuthResultHandler() {
@Override
public void onAuthenticated(AuthData authData) {
// přihlášení se povedlo
setOnline(true); // uložím uživatele tak, že mu nastavím online - viz. nastavení online stavu
}
@Override
public void onAuthenticationError(FirebaseError firebaseError) {
// přihlášení selhalo
}
});
Ovšem toto je pouze přihlášení uživatele, nikoliv jeho uložení. Pro uložení využiji svou funkci, která mění online stav uživatele na serveru. Funkci popisuji níže v Nastavení online stavu.
Nastavení online stavu
Uživatel se zobrazeným chatem je ve stavu online. Pokud aplikaci překryje jiná, zhasne display nebo uživatel zavře aplikaci, přejde do stavu offline. Tyto funkce jsou volány v onResume a onPause, aby nastavily správný status.
SynergyKit
Níže popsaná funkce nastaví uživateli online status. Parametr online je status uživatele. Druhý parametr parallelMode zařídí, že tato operace poběží asynchronně.
private void setOnline(boolean online, boolean parallelMode) {
if (user == null) return; // nemám získaného uživatele
user.setOnline(online); // nastaví se mu online stav
Synergykit.updateUser(user, new UserResponseListener() {
@Override
public void doneCallback(int statusCode, SynergykitUser synergykitUser) {
user = (SKUser) synergykitUser; // uživatel byl updatnut, uložím si nového do proměnné
}
@Override
public void errorCallback(int statusCode, SynergykitError synergykitError) {
// update se nepovedl, uživatel bude mít starý stav
}
}, parallelMode);
}
Firebase
Níže uvedená funkce nastaví uživateli online status. Pokud uživatel neexistuje, vytvoří ho. Firebase ukládá JSON data, proto je zde použita mapa.
private void setOnline(boolean online) {
Map<String, Object> map = new HashMap<>(); // mapa pro uložení hodnot
map.put("name", userName); // jméno uživatele
map.put("online", online); // online status uživatele
firebaseUsers.child("" + uId).setValue(map); // vytvoří uId v users a nastaví mu hodnoty z mapy
}
Posílání zpráv
Posílání zpráv je v obou případech řešeno přidáním zprávy do kolekce, nad kterou aplikace poslouchá změny, které jsou přijímány přes websockets.
SynergyKit
Byla vytvořena třída, která obsahuje všechny potřebné atributy a pro potřeby SynergyKitu rozšiřuje SynergykitObject. Následující kód přidá do kolekce zprávu.
Synergykit.createRecord(COLLECTION_MESSAGES, message, new ResponseListener() { // vytvoření záznamu
@Override
public void doneCallback(int statusCode, SynergykitObject synergykitObject) {
// zpráva odeslána
}
@Override
public void errorCallback(int statusCode, SynergykitError synergykitError) {
// při odesílání zprávy nastal error
}
}, true);
Firebase
Byla vytvořena třída, která obsahuje všechny potřebné atributy. Následující kód přidá zprávu do kolekce.
firebaseMessages.push().setValue(message, new Firebase.CompletionListener() { // přidání zprávy do kolekce
@Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
if (firebaseError != null) {
// při odesílání zprávy nastal error
} else {
// zpráva odeslána
}
}
});
Příjem zpráv
Příjem zpráv je u obou platforem řešen posloucháním eventu přidání do kolekce. Aplikace při zobrazení chatu načte posledních 100 zpráv.
SynergyKit
Následující kód získá posledních 100 záznamů z kolekce messages.
SynergykitUri synergyKitUri = UriBuilder.newInstance() // získání instance
.setResource(Resource.RESOURCE_DATA) // nastavení zdroje informací
.setCollection(COLLECTION_MESSAGES) // nastavení kolekce
.setOrderByDesc("createdAt") // seřazení podle createdAt sestupně
.setTop(prevMessageCount) // oříznout na posledních 100
.build();
SynergykitConfig config = SynergykitConfig.newInstance() // získání instance
.setParallelMode(true) // nastavení běhu na pozadní
.setType(SKMessage[].class) // nastavení formátu výstupu
.setUri(synergyKitUri); // nastavení uri z výše
Synergykit.getRecords(config, new RecordsResponseListener() {
@Override
public void doneCallback(int statusCode, SynergykitObject[] synergykitObjects) {
SKMessage[] messages = (SKMessage[]) synergykitObjects; // posledních 100 zpráv sestupně
}
@Override
public void errorCallback(int statusCode, SynergykitError synergykitError) {}
});
Násedující kód zaregistruje socket k poslouchání eventu „created“. Po přidání je zavoláno připojení na socket, které započne poslouchání.
// Poslouchání na eventu vytvoření, nad kolekcí messages
Synergykit.onSocket(EVENT_CREATED, COLLECTION_MESSAGES, new SocketEventListener() {
@Override
public void call(Object... objects) { // zavoláno při vytvoření zprávy
String data = objects[0].toString(); // JSON data
final SKMessage message = GsonWrapper.getGson().fromJson(data, SKMessage.class); // získání zprávy z dat
}
@Override
public void subscribed() {} // při započetí poslouchání
@Override
public void unsubscribed() {} // při skončení poslouchání
});
Synergykit.connectSocket(); // připojení socketu
Firebase
Query je použíto pro filtrování a oříznutí počtu zpráv. ChildEventListener umožní poslouchání změn nad tímto query (kolekcí).
Query query = firebaseMessages.orderByChild("timestamp").limitToLast(prevMessageCount);
query.addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
if (dataSnapshot == null) return; // nepřišla žádná data
FBMessage message = dataSnapshot.getValue(FBMessage.class); // získání zprávy
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {}
@Override
public void onCancelled(FirebaseError firebaseError) {}
});
Push Notifikace
Push Notifikace budou přijímány a zobrazovány, když uživatel není na obrazovce chatu. Push Notifikace bude mít tvar Odesílatel: zpráva, a při zobrazení přehraje defaultní zvuk notifikací. Informace ohledně Push Notifikací od GCM.
SynergyKit
Předpokladem je založení aplikace v Google Console. Do nastavení aplikace v SynergyKitu musím zapsat tzv. API klíč GCM, což můžu udělat v záložce Settings->Android GCM.
Novou závislost přidám do souboru build.gradle.
dependencies {
...
compile 'com.google.android.gms:play-services-gcm:7.5.0'
}
Registrační id získám registrací zařízení do služby GCM.
regid = gcm.register(SENDER_ID);
Registrační id získané výše musím uložit na server, který bude rozesílat Push Notifikace. SynergyKit ukládá tuto informaci do instance třídy SynergykitPlatform. Třída SynergykitPlatform je součástí třídy SynergykitUser nebo jejího potomka.
if (platform == null) { // Platforma neexistuje
SynergykitPlatform platform = new SynergykitPlatform(regid); // vytvoření platformy
Synergykit.addPlatform(platform, new PlatformResponseListener() {
@Override
public void doneCallback(int statusCode, SynergykitPlatform platform) {
// přidání proběhlo úspěšně
}
@Override
public void errorCallback(int statusCode, SynergykitError errorObject) {
// nastala chyba při přidávání platformy
}
}, true);
} else if (!platform.getRegistrationId().equals(regid)) { // pokud je jiné registrační id u platformy
platform.setRegistrationId(regid); // nastavit nové registrační id platformě
Synergykit.updatePlatform(platform, null, true); // update platformy bez listeneru
}
Cloud code umožňuje spouštět kód na straně serveru. Ze zařízení lze spustit nebo nastavit automatické spuštění při eventu u kolekce. Kód se píše v JavaScriptu do prohlížečového IDE, kde je možné ho i debugovat.
Nastavil jsem trigger na event „created“ v kolekci messages, tudíž bude Cloud code zavolán po přidání zprávy. Následující Cloud code získá všechny uživatele, kteří mají nastaven atribut online na false a rozešle jim notifikaci s textem zprávy.
var queryOfflineUsers = Synergykit.Query(Synergykit.User()); // zalozeni query
// najde vsechny uzivatele, ktere maji online atribut nastaven na false
queryOfflineUsers.where().attribute("online").isEqualTo(false).find({
success: function(offline_users, code) {
if (code == 200) {
var notification = Synergykit.Notification(offline_users); // vytvoreni notifikace a pridani vsech offline uzivatelu do notifikace
var text = parameters.name + ': ' + parameters.text; // ziskani textu notifikace
notification.set("alert", text); // pridani textu k notifikaci
notification.send({ // odeslani notifikace
success: function(result, statusCode) {
callback({
result: 'success',
offline_users: offline_users
});
},
error: function(error, statusCode) {
callback;
}
});
} else {
callback;
}
},
error: callback
});
Notifikace byla odeslána všem uživatelům, ovšem aplikace je nepřijímají. Musím tudíž v Androidu naimplementovat přijímání notifikací od GCM.
Následujícím kódem jsem požádal o práva v manifestu.
<manifest … >
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<permission
android:name="<PACKAGE_NAME>.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="<PACKAGE_NAME>.permission.C2D_MESSAGE" />
</manifest>
GcmReceiver přijímá zpávy od GCM a musí být přidán do manifestu.
<manifest … >
<application … >
...
<receiver
android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</receiver>
...
</application>
</manifest>
MyGcmListenerService je service, která bude přijímat zprávy od receiveru a následně vytvářet notifikaci. Service musím také přidat do manifestu.
<manifest … >
<application … >
...
<service
android:name="<PACKAGE_NAME>.MyGcmListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</service>
...
</application>
</manifest>
Třída MyGcmListenerService zobrazí uživateli notifikaci, když ji přijme od GcmReceiveru.
public class MyGcmListenerService extends GcmListenerService {
@Override
public void onMessageReceived(String from, Bundle data) {
String message = data.getString("alert"); // získání zprávy
sendNotification(message);
}
private void sendNotification(String message) {
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
… // přidat do notifikace vše potřebné
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0 /* ID notifikace */, notificationBuilder.build());
}
}
Firebase
Firebase nepodporuje posílání Push Notifikací a jeho implementace v aplikaci není jednoduchá. Na internetu však existuje značné množství tutoriálů, které umožňují tuto situaci vyřešit vlastní serverovou aplikací.
Závěr
Podle mého názoru je pro tuto aplikaci lepší použít SynergyKit, protože pro vytvoření Push Notifikací na platformě Firebase je potřeba využít produkt třetí strany. Dále také SynergyKit umožňuje přesunutí částí kódu na server, díky čemuž dosahují zařízení vyšší výdrže. Tuto serverovou část lze modifikovat v prohlížeči a tyto změny nevyžadují aktualizaci aplikace. Obě platformy umožňují jednoduché přihlášení uživatele, zápis i čtení dat.
Co se zde myslí pod pojmem real-time aplikace? Že se data zobrazují bez toho abych klikal na tlačítko?
Myslí se tím to, že aplikace komunikuje přes websockety a umožňuje poslouchat změny v datových strukturách podle určitých pravidel.
Pod pojmem real-time aplikace se myslí okamžité zobrazení změn, které nastanou v databázi. Zde například uživatel odešle zprávu, a ostatním uživatelům s otevřeným chatem se hned zobrazí (pokud mají přístup k internetu) – nemusejí aktualizovat seznam zpráv.
Což je to, co jsem si myslel, že se tím myslí .) Podle mne je to buzzword — stejně jako real-time platforma. Pardon: „Real-time programs must guarantee response within specified time constraints, often referred to as „deadlines“.
trochu nefer porovnanie kedze Firebase je v podstate DB in the cloud. ked uz sa tu snazi prezentovat (marketovat) novy produkt, tak sa malo porovnavat voci relevantnemu superovi, v tomto pripade skor Parse
Parse nenabízí komunikaci přes websockety.
Chápu, že vývojáři SynergyKitu považují za dobré to, co dělá jejich produkt. Pokud by si mysleli, že to dělá dobře konkurence, asi to nebudou dělat jinak a hůř. Nechápu, proč se to pak ale snaží neuměle maskovat něčím, co má na první pohled vypadat jako objektivní srovnání. Článek totiž vyznívá jako „vybrali jsme si pro naši aplikaci pro srovnání Firebase, načež jsme zjistili, že pro ni Firebase vlastně vůbec není vhodná“. Což vypadá nejvíc jako chyba toho, kdo tu technologii vybral – a je zvláštní, když takovou chybu udělá někdo, kdo by v dané oblasti měl mít slušný přehled.
Tak se podívejte na firebase.com, kde vám vlepují hned do očí chat. Podle mě je to u daného use case objektivní. Nevím proč mluvíte o chybě???
Není chat jako chat.
Ta Czenglish na synergykit.com nedela moc dobry prvni dojem…
diky za komentar. mohl bys uvest nejaky konkretni priklad?