Memigrasikan Aplikasi Parse pada iOS ke Firebase

Jika Anda pengguna Parse yang mencari Backend alternatif sebagai solusi Service, Firebase mungkin adalah pilihan ideal untuk aplikasi iOS Anda.

Panduan ini menjelaskan cara mengintegrasikan layanan tertentu ke dalam aplikasi Anda. Untuk mengetahui petunjuk penyiapan Firebase dasar, lihat panduan Penyiapan iOS+.

Google Analytics

Google Analytics adalah solusi pengukuran aplikasi gratis yang memberikan insight mengenai penggunaan aplikasi dan interaksi pengguna. Analytics terintegrasi dengan berbagai fitur Firebase dan menyediakan pelaporan tak terbatas hingga 500 peristiwa berbeda yang dapat Anda tentukan menggunakan Firebase SDK.

Baca dokumentasi Google Analytics untuk mempelajari lebih lanjut.

Strategi Migrasi yang Disarankan

Menggunakan berbagai penyedia analisis adalah skenario umum yang mudah diterapkan pada Google Analytics. Cukup tambahkan penyedia analisis ke aplikasi Anda untuk memanfaatkan peristiwa dan properti pengguna yang dikumpulkan Analytics secara otomatis, seperti pembukaan aplikasi untuk pertama kali, update aplikasi, model perangkat, usia.

Untuk peristiwa kustom dan properti pengguna, Anda dapat menggunakan strategi penulisan ganda menggunakan Parse Analytics dan Google Analytics untuk mencatat peristiwa dan properti ke dalam log sehingga Anda dapat meluncurkan solusi baru secara bertahap.

Perbandingan Kode

Parse Analytics

// Start collecting data
[PFAnalytics trackAppOpenedWithLaunchOptions:launchOptions];

NSDictionary *dimensions = @{
  // Define ranges to bucket data points into meaningful segments
  @"priceRange": @"1000-1500",
  // Did the user filter the query?
  @"source": @"craigslist",
  // Do searches happen more often on weekdays or weekends?
  @"dayType": @"weekday"
};
// Send the dimensions to Parse along with the 'search' event
[PFAnalytics trackEvent:@"search" dimensions:dimensions];

Google Analytics

// Obtain the AppMeasurement instance and start collecting data
[FIRApp configure];

// Send the event with your params
[FIRAnalytics logEventWithName:@"search" parameters:@{
  // Define ranges to bucket data points into meaningful segments
  @"priceRange": @"1000-1500",
  // Did the user filter the query?
  @"source": @"craigslist",
  // Do searches happen more often on weekdays or weekends?
  @"dayType": @"weekday"
}];

Firebase Realtime Database

Firebase Realtime Database adalah database NoSQL yang dihosting di cloud. Data disimpan sebagai JSON dan disinkronkan secara real-time pada setiap klien yang terhubung.

Baca dokumentasi Firebase Realtime Database untuk mempelajari lebih lanjut.

Perbedaan dengan Data Parse

Objek

Dalam Parse, Anda menyimpan PFObject, atau subclass-nya, yang berisi key-value pair dari data yang kompatibel dengan JSON. Data tersebut tidak memiliki skema, sehingga Anda tidak perlu menentukan kunci yang terdapat di setiap PFObject.

Semua data Firebase Realtime Database disimpan sebagai objek JSON, dan tidak ada yang setara dengan PFObject; Anda cukup menuliskan nilai jenis yang sesuai dengan jenis JSON yang tersedia ke hierarki JSON.

Berikut adalah contoh cara menyimpan skor tertinggi dalam sebuah game.

Parse
PFObject *gameScore = [PFObject objectWithClassName:@"GameScore"];
gameScore[@"score"] = @1337;
gameScore[@"playerName"] = @"Sean Plott";
gameScore[@"cheatMode"] = @NO;
[gameScore saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
  if (succeeded) {
    // The object has been saved.
  } else {
    // There was a problem, check error.description
  }
}];
Firebase
// Create a reference to the database
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
NSString *key = [[ref child:@"scores"] childByAutoId].key;
NSDictionary *score = @{@"score": @1337,
                        @"playerName": @"Sean Plott",
                        @"cheatMode": @NO};
[key setValue:score withCompletionBlock:^(NSError *error,  FIRDatabaseReference *ref) {
  if (error) {
    // The object has been saved.
  } else {
    // There was a problem, check error.description
  }
}];
Untuk mengetahui detail selengkapnya, baca panduan Membaca dan Menulis Data di platform Apple.

Hubungan Antardata

PFObject dapat memiliki hubungan dengan PFObject yang lain: setiap objek dapat menggunakan objek lain sebagai nilai.

Dalam Firebase Realtime Database, hubungan sebaiknya dinyatakan menggunakan struktur data rata yang membagi data ke beberapa jalur, sehingga dapat didownload secara efisien dalam panggilan yang terpisah.

Berikut adalah contoh cara membuat struktur hubungan antara postingan di aplikasi blog dan penulisnya.

Parse
// Create the author
PFObject *myAuthor = [PFObject objectWithClassName:@"Author"];
myAuthor[@"name"] = @"Grace Hopper";
myAuthor[@"birthDate"] = @"December 9, 1906";
myAuthor[@"nickname"] = @"Amazing Grace";

// Create the post
PFObject *myPost = [PFObject objectWithClassName:@"Post"];
myPost[@"title"] = @"Announcing COBOL, a New Programming Language";

// Add a relation between the Post and the Author
myPost[@"parent"] = myAuthor;

// This will save both myAuthor and myPost
[myPost saveInBackground];
Firebase
// Create a reference to the database
FIRDatabaseReference *ref = [[FIRDatabase database] reference];

// Create the author
NSString *myAuthorKey = @"ghopper";
NSDictionary *author = @{@"name": @"Grace Hopper",
                         @"birthDate": @"December 9, 1906",
                         @"nickname": @"Amazing Grace"};
// Save the author
[[ref child:myAuthorKey] setValue:author]

// Create and save the post
NSString *key = [[ref child:@"posts"] childByAutoId].key;
NSDictionary *post = @{@"author": myAuthorKey,
                       @"title": @"Announcing COBOL, a New Programming Language"};
[key setValue:post]

Tata letak data berikut ini adalah hasilnya.

{
  // 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"
    }
    ...
  }
}
Untuk mengetahui detail selengkapnya, baca panduan Membuat Struktur Database.

Membaca Data

Dalam Parse, Anda membaca data menggunakan ID dari objek Parse tertentu, atau menjalankan kueri menggunakan PFQuery.

Dalam Firebase, Anda mengambil data dengan menambahkan pemroses asinkron ke referensi database. Pemroses dipicu satu kali untuk status awal data dan dipicu kembali ketika data berubah sehingga Anda tidak perlu menambahkan kode untuk mengetahui apakah datanya berubah.

Berikut adalah contoh cara mengambil skor untuk pemain tertentu, berdasarkan contoh yang diberikan di bagian "Objek".

Parse
PFQuery *query = [PFQuery queryWithClassName:@"GameScore"];
[query whereKey:@"playerName" equalTo:@"Dan Stemkoski"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
  if (!error) {
    for (PFObject *score in objects) {
      NSString *gameScore = score[@"score"];
      NSLog(@"Retrieved: %@", gameScore);
    }
  } else {
    // Log details of the failure
    NSLog(@"Error: %@ %@", error, [error userInfo]);
  }
}];
Firebase
// Create a reference to the database
FIRDatabaseReference *ref = [[FIRDatabase database] reference];

// This type of listener is not one time, and you need to cancel it to stop
// receiving updates.
[[[[ref child:@"scores"] queryOrderedByChild:@"playerName"] queryEqualToValue:@"Dan Stemkoski"]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
  // This will fire for each matching child node.
  NSDictionary *score = snapshot.value;
  NSString gameScore = score[@"score"];
  NSLog(@"Retrieved: %@", gameScore);
}];
Untuk mengetahui detail selengkapnya mengenai jenis pemroses peristiwa yang tersedia, serta cara mengurutkan dan memfilter data, baca panduan Membaca dan Menulis Data di platform Apple.

Strategi Migrasi yang Disarankan

Memikirkan Ulang Data Anda

Firebase Realtime Database dioptimalkan untuk menyinkronkan data dalam waktu milidetik pada semua klien yang terhubung, dan struktur data yang dihasilkan berbeda dari data inti Parse. Artinya, langkah pertama saat akan melakukan migrasi adalah mempertimbangkan perubahan yang dibutuhkan data Anda, termasuk:

  • Cara memetakan objek Parse Anda ke data Firebase
  • Cara memisahkan data ke jalur yang berbeda-beda jika Anda memiliki hubungan induk-turunan, sehingga data tersebut dapat didownload secara efisien dalam panggilan yang terpisah.

Memigrasikan Data Anda

Setelah menentukan cara membuat struktur data di Firebase, Anda harus merencanakan cara menangani waktu selama aplikasi harus melakukan penulisan ke kedua database tersebut. Pilihan untuk Anda adalah:

Sinkronisasi Latar Belakang

Dalam skenario ini, Anda memiliki dua versi aplikasi: versi lama yang menggunakan Parse dan versi baru yang menggunakan Firebase. Sinkronisasi antara dua database ditangani oleh Parse Cloud Code (Parse ke Firebase), beserta kode Anda yang mendeteksi perubahan pada Firebase dan menyinkronkan perubahan tersebut dengan Parse. Sebelum dapat mulai menggunakan versi yang baru, Anda harus:

  • Mengubah Data Parse yang ada ke struktur Firebase yang baru, lalu menulisnya ke Firebase Realtime Database.
  • Menulis fungsi Parse Cloud Code yang menggunakan Firebase REST API untuk melakukan penulisan ke perubahan Firebase Realtime Database yang dibuat di Data Parse oleh klien lama.
  • Menulis dan men-deploy kode yang mendeteksi perubahan pada Firebase, lalu menyinkronkannya dengan database Parse.

Skenario ini memastikan pemisahan yang jelas antara kode baru dan kode lama, dan tetap menyederhanakan klien. Tantangan dari skenario ini adalah menangani set data besar dalam ekspor awal, dan memastikan bahwa sinkronisasi dua arah tidak menghasilkan perulangan yang tak terbatas.

Penulisan Ganda

Dalam skenario ini, Anda menulis versi baru dari aplikasi yang menggunakan Firebase dan Parse, menggunakan Parse Cloud Code untuk menyinkronkan perubahan yang dilakukan klien lama dari Data Parse ke Firebase Realtime Database. Jika ada cukup orang yang bermigrasi dari versi aplikasi khusus Parse, Anda dapat menghapus kode Parse dari versi penulisan ganda.

Skenario ini tidak membutuhkan kode sisi server apa pun. Kekurangannya adalah data yang tidak diakses tidak akan dimigrasikan, dan ukuran aplikasi Anda akan meningkat karena penggunaan kedua SDK.

Firebase Authentication

Firebase Authentication dapat melakukan autentikasi pengguna menggunakan sandi dan penyedia identitas gabungan populer seperti Google, Facebook, dan Twitter. Firebase Authentication juga menyediakan library UI yang mengurangi biaya yang diperlukan untuk menerapkan serta mengelola pengalaman autentikasi penuh bagi aplikasi Anda di seluruh platform.

Baca dokumentasi Firebase Authentication untuk mempelajari lebih lanjut.

Perbedaan dengan Parse Auth

Parse menyediakan class pengguna khusus yang disebut PFUser, yang otomatis menangani fungsionalitas yang diperlukan untuk mengelola akun pengguna. PFUser adalah subclass PFObject, yang berarti data pengguna tersedia di Data Parse dan dapat dilengkapi dengan kolom tambahan seperti PFObject lainnya.

FIRUser memiliki rangkaian properti dasar yang tetap—ID unik, alamat email utama, nama, dan URL foto—yang disimpan di database pengguna dalam project terpisah. Properti tersebut dapat diperbarui oleh pengguna. Anda tidak dapat menambahkan langsung properti lain ke objek FIRUser, tetapi Anda dapat menyimpan properti tambahan di Firebase Realtime Database.

Berikut adalah contoh cara mendaftarkan pengguna dan menambahkan kolom nomor telepon tambahan.

Parse
PFUser *user = [PFUser user];
user.username = @"my name";
user.password = @"my pass";
user.email = @"email@example.com";

// other fields can be set just like with PFObject
user[@"phone"] = @"415-392-0202";

[user signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
  if (!error) {
    // Hooray! Let them use the app now.
  } else {
    // Something went wrong
    NSString *errorString = [error userInfo][@"error"];
  }
}];
Firebase
[[FIRAuth auth] createUserWithEmail:@"email@example.com"
                           password:@"my pass"
                         completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
  if (!error) {
    FIRDatabaseReference *ref = [[FIRDatabase database] reference];
    [[[[ref child:@"users"] child:user.uid] child:@"phone"] setValue:@"415-392-0202"
  } else {
    // Something went wrong
    NSString *errorString = [error userInfo][@"error"];
  }
}];

Strategi Migrasi yang Disarankan

Memigrasikan Akun

Untuk memigrasikan akun pengguna dari Parse ke Firebase, ekspor database pengguna ke file JSON atau CSV, lalu impor file tersebut ke project Firebase menggunakan perintah auth:import Firebase CLI.

Pertama-tama, ekspor database pengguna dari konsol Parse atau database yang Anda hosting sendiri. Misalnya, file JSON yang diekspor dari konsol Parse mungkin akan terlihat seperti berikut:

{ // 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",
  ...
}

Selanjutnya, ubah file yang diekspor tersebut menjadi format yang diperlukan oleh Firebase CLI. Gunakan objectId pengguna Parse Anda sebagai localId pengguna Firebase Anda. Selain itu, enkode nilai bcryptPassword dari Parse dalam format base64, lalu gunakan di kolom passwordHash. Contoh:

{
  "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
        }
      ]
    }
  ]
}

Terakhir, impor file yang diubah tersebut dengan Firebase CLI, dengan menentukan bcrypt sebagai algoritme hash:

firebase auth:import account_file.json --hash-algo=BCRYPT

Memigrasikan Data Pengguna

Jika menyimpan data tambahan untuk pengguna, Anda dapat memigrasikannya ke Firebase Realtime Database menggunakan strategi yang dijelaskan di bagian migrasi data. Jika Anda memigrasikan akun menggunakan alur yang dijelaskan di bagian migrasi akun, akun Firebase Anda akan memiliki ID yang sama dengan akun Parse Anda, sehingga memudahkan Anda memigrasikan dan mereproduksi setiap hubungan yang dikuncikan oleh ID pengguna.

Firebase Cloud Messaging

Firebase Cloud Messaging (FCM) adalah solusi pengiriman pesan lintas platform, yang memungkinkan Anda untuk mengirim pesan dan notifikasi secara tepercaya tanpa biaya. Notifications Composer adalah layanan tanpa biaya yang dikembangkan di Firebase Cloud Messaging, yang memungkinkan developer aplikasi seluler membuat notifikasi untuk pengguna target.

Untuk mempelajari lebih lanjut, baca dokumentasi Firebase Cloud Messaging.

Perbedaan dengan Parse Push Notification

Setiap aplikasi Parse yang diinstal di perangkat yang terdaftar untuk menerima notifikasi memiliki objek Installation terkait, tempat Anda menyimpan semua data yang diperlukan untuk menargetkan notifikasi. Installation adalah subclass PFUser, yang berarti Anda dapat menambahkan data tambahan yang diinginkan ke instance Installation.

Notifications Composer menyediakan segmen pengguna bawaan berdasarkan informasi seperti aplikasi, versi aplikasi, dan bahasa perangkat. Anda dapat membangun segmen pengguna yang lebih kompleks menggunakan peristiwa dan properti Google Analytics untuk membangun audience. Baca panduan bantuan audience untuk mempelajari lebih lanjut. Informasi penargetan ini tidak terlihat dalam Firebase Realtime Database.

Strategi Migrasi yang Disarankan

Melakukan Migrasi Token Perangkat

Parse menggunakan token perangkat APN guna menargetkan penginstalan untuk notifikasi, sedangkan FCM menggunakan token pendaftaran FCM yang dipetakan ke token perangkat APN. Cukup tambahkan FCM SDK ke aplikasi Apple Anda, dan FCM SDK ini akan mengambil token FCM secara otomatis.

Memigrasikan Saluran ke Topik FCM

Jika Anda menggunakan saluran Parse untuk mengirim notifikasi, Anda dapat melakukan migrasi ke topik FCM, yang menyediakan mode penayang-pelanggan yang sama. Untuk menangani transisi dari Parse ke FCM, Anda dapat menulis versi baru aplikasi yang menggunakan Parse SDK untuk berhenti berlangganan dari saluran Parse, dan FCM SDK untuk berlangganan ke topik FCM yang terkait.

Misalnya, jika pengguna Anda berlangganan ke topik "Giants", Anda akan melakukan hal seperti:

PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation removeObject:@"Giants" forKey:@"channels"];
[currentInstallation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
  if (succedeed) {
    [[FIRMessaging messaging] subscribeToTopic:@"/topics/Giants"];
  } else {
    // Something went wrong unsubscribing
  }
}];

Dengan menggunakan strategi ini, Anda dapat mengirimkan pesan ke saluran Parse dan topik FCM yang sesuai, sehingga mendukung pengguna versi lama maupun baru. Jika sudah ada cukup pengguna yang bermigrasi dari versi aplikasi khusus Parse, Anda dapat mengakhiri masa berlaku versi tersebut dan mulai melakukan pengiriman menggunakan FCM saja.

Baca dokumentasi topik FCM untuk mempelajari lebih lanjut.

Firebase Remote Config

Firebase Remote Config adalah layanan cloud yang dapat digunakan untuk mengubah perilaku dan tampilan aplikasi tanpa mengharuskan pengguna mendownload update aplikasi. Saat menggunakan Remote Config, Anda membuat nilai default dalam aplikasi yang mengontrol perilaku dan tampilan aplikasi. Kemudian, Anda bisa menggunakan Firebase console untuk mengganti nilai default dalam aplikasi untuk semua pengguna aplikasi atau segmen basis pengguna Anda.

Firebase Remote Config dapat sangat berguna selama migrasi jika Anda ingin menguji solusi yang berbeda, dan dapat mengalihkan lebih banyak klien ke penyedia lain secara dinamis. Misalnya, jika Anda memiliki versi aplikasi yang menggunakan Firebase dan Parse untuk datanya, Anda dapat menggunakan aturan persentil acak untuk menentukan klien mana yang membaca dari Firebase, dan secara bertahap meningkatkan persentasenya.

Untuk mempelajari Firebase Remote Config lebih lanjut, baca Pengantar Remote Config.

Perbedaan dengan Parse Config

Dengan konfigurasi Parse, Anda dapat menambahkan key-value pair ke aplikasi di Parse Config Dashboard, lalu mengambil PFConfig pada klien. Setiap instance PFConfig yang Anda dapatkan selalu bersifat tidak dapat diubah. Jika di masa mendatang Anda mengambil PFConfig baru dari jaringan, instance PFConfig yang ada tidak akan berubah. Namun, instance baru akan dibuat dan tersedia melalui currentConfig

Dengan Firebase Remote Config, Anda membuat key-value pair default dalam aplikasi yang dapat diganti dari Firebase console. Anda juga dapat menggunakan aturan dan kondisi untuk memberikan variasi pengalaman pengguna aplikasi ke berbagai segmen basis pengguna. Firebase Remote Config menerapkan class singleton yang membuat key-value pair tersedia bagi aplikasi Anda. Awalnya singleton menampilkan nilai default yang Anda tentukan dalam aplikasi. Anda dapat mengambil kumpulan nilai baru dari server kapan saja sesuai waktu yang paling tepat untuk aplikasi Anda. Setelah kumpulan baru berhasil diambil, Anda dapat memilih kapan harus mengaktifkannya untuk membuat nilai baru tersedia untuk aplikasi.

Strategi Migrasi yang Disarankan

Anda dapat melakukan pemindahan ke Firebase Remote Config dengan menyalin key-value pair konfigurasi Parse Anda ke Firebase console, kemudian men-deploy versi baru aplikasi yang menggunakan Firebase Remote Config.

Jika ingin bereksperimen dengan Parse Config dan Firebase Remote Config, Anda dapat men-deploy versi baru aplikasi yang menggunakan kedua SDK tersebut hingga ada cukup pengguna yang bermigrasi dari versi khusus Parse.

Perbandingan Kode

Parse

[PFConfig getConfigInBackgroundWithBlock:^(PFConfig *config, NSError *error) {
  if (!error) {
    NSLog(@"Yay! Config was fetched from the server.");
  } else {
    NSLog(@"Failed to fetch. Using Cached Config.");
    config = [PFConfig currentConfig];
  }

  NSString *welcomeMessage = config[@"welcomeMessage"];
  if (!welcomeMessage) {
    NSLog(@"Falling back to default message.");
    welcomeMessage = @"Welcome!";
  }
}];

Firebase

FIRRemoteConfig remoteConfig = [FIRRemoteConfig remoteConfig];
// Set defaults from a plist file
[remoteConfig setDefaultsFromPlistFileName:@"RemoteConfigDefaults"];

[remoteConfig fetchWithCompletionHandler:^(FIRRemoteConfigFetchStatus status, NSError *error) {
  if (status == FIRRemoteConfigFetchStatusSuccess) {
    NSLog(@"Yay! Config was fetched from the server.");
    // Once the config is successfully fetched it must be activated before newly fetched
    // values are returned.
    [self.remoteConfig activateFetched];
  } else {
    NSLog(@"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.
NSString welcomeMessage = remoteConfig[@"welcomeMessage"].stringValue;