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í.

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.

LoginActivitySKChatActivityFBChatActivitySKPushNotification

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.

Komentáře: 11

Přehled komentářů

ue Co je to real-time apliakce?
MarekOstry Re: Co je to real-time apliakce?
Jakub Nižaradze Re: Co je to real-time apliakce?
ue Re: Co je to real-time apliakce?
mboy nefer porovnanie
MarekOstry Re: nefer porovnanie
filip.jirsak Srovnání?
MarekOstry Re: Srovnání?
filip.jirsak Re: Srovnání?
n/a
Tomas Mistrik Re:
Zdroj: https://www.zdrojak.cz/?p=15126