How to migrate your iOS app from Parse to Syncano

Mariusz WisniewskiMariusz Wisniewski

In this tutorial, you're going to learn how to migrate your app from Parse to Syncano, and you’ll learn about the new features Syncano offers.

Moving data from Parse to Syncano

We recently launched our tool to transfer data from Parse to the Syncano platform. You can find it on GitHub.

To install it, type the following in your console

pip install parse2syncano  

Next, to configure it, run

parse2syncano configure  

You will be asked for a couple of settings/keys, including:

Parse Master Key

Parse Application ID

Syncano Account Key

You can find your Instance name under Instances listing, or if you'd like to add a new one, use the same link and create new Instance using the Add button.

Add new instance

After providing the Instance name, configuration is finished. If you'd like to see your configuration to make sure it's correct, run the following in Terminal

parse2syncano configure  

to display all settings. If anything is wrong and you'd like to make changes, you can do so by providing all settings from the beginning using

parse2syncano configure -f  

or by editing the .syncano file located in your home directory.

If you're ready to move your data to Syncano, type

parse2syncano sync  

This process may take a while if you have a lot of data, so you’ll want to wait a bit, make yourself a coffee, and come back after a while :)

For possible issues and troubleshooting, check our GitHub.

Using Syncano

Installing Syncano Library

First -- you need to install our library. Right now it's available only on CocoaPods.

If you don't know what CocoaPods are, or how to install them, learn how to do it from their guide, or watch a video guide from Google.

If you already have CocoaPods in your system, move to the next step.

Open your favorite terminal app and navigate to your XCode project folder. If your project doesn't use CocoaPods yet, you can initialize CocoaPods while you are still in your project’s folder by running:

pod init  

When it's done, or if your project already uses CocoaPods, just open Podfile and edit it to add the Syncano library.

open Podfile  

Add a line with Syncano library to it, so your Podfile looks similar to this:

# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# Uncomment this line if you're using Swift
use_frameworks!

target 'iOS' do  
pod 'syncano-ios'  
end  

Make sure to uncomment the line with use_frameworks! statement -- it will make it easier to use the library in Swift.

Save and close the Podfile and install our library. If you haven't used CocoaPods before in your project, run:

pod install  

or, if your project already was using CocoaPods, just update them:

pod update  

The Syncano framework is now installed and you can start using it in your app.

Connecting to Syncano

To connect to Syncano, you need to pass an Instance name and an API Key.

An Instance name is a unique identifier of your own version of Syncano configuration and is similar to an App on Parse. It has to be unique and, when connecting to Syncano, it is used in place of Parse's application id.

An API Key is similar to Keys on Parse. You can use an Account Key, which is equivalent to Parse's Master Key -- but it can also be used on mobile devices.

You also have a standard Instance API Key, which is equivalent to Parse's Client, JavaScript or REST Key -- but it's up to you to create as many keys as needed.

You could have one Instance API Key for all mobile and web apps, or you might have different API Keys for Android, iOS, Windows Phone and Web App -- we don't impose any limits on the number of API Keys and you can work with them in a way that's best for your app.

We do not recommend embedding and shipping an Account Key with your mobile or web app, but it might be useful for testing during the development period.

For this tutorial, feel free to use an Account Key or an API Key with ignore ACL setting -- it will make things a bit simpler.

Import the Syncano library to your project:

Swift:

import syncano_ios  

Obj-C:

#import <syncano_ios/Syncano.h>

On iOS, you need only one line of code to connect to Syncano,

Swift:

Syncano.sharedInstanceWithApiKey("API KEY", instanceName: "INSTANCE NAME")  

Obj-C:

[Syncano sharedInstanceWithApiKey:@"API KEY" instanceName:@"INSTANCE NAME"];

It needs to be invoked only once, and a good place for it is, for example, in the application:didFinishLaunchingWithOptions: method of your AppDelegate.

Manipulating your data

Dashboard - Classes

On Syncano, similar to the way it is in Parse, different data types are divided into Classes. For example, if you want to store information about cars, you would create a Class called Car and define it like this:

class Car :  
   model : String
   year: Int
   color: String
   brand: String
   registered: Boolean
   mileage: Float

The best way to add a new Class is through our Dashboard. Log in to your account, go inside your Instance and into Classes view. From here you can edit existing Classes and add new ones.

Classes view

To edit/add Class view is very simple. All you need to do is:

Add a Class

Once you finish creating your Class, or if you migrated your data from Parse and already have some Classes and Objects inside your Instance, we can go back to coding.

Coding - Classes

In Parse, there was a special Class designed to be an equivalent of a row in a database table - PFObject.

Syncano offers a similar Class and in iOS it's called SCDataObject.

Unlike in Parse, in Syncano you need to subclass SCDataObject  
and list all properties inside your Class.  

Lets add code for our Car Class. Remember to import the library at the beginning of your Class.

Import Syncano library to your project:

Swift:

import syncano_ios  

Obj-C:

#import <syncano_ios/Syncano.h>

and declare your Syncano Class

Swift:

class Car : SCDataObject {  
    var model = ""
    var year = 0
    var color = ""
    var brand = ""
    var registered = false
    var mileage = 0.0
}

Obj-C:

@interface Car : SCDataObject
@property (strong) NSString *model;
@property (strong) NSNumber *year;
@property (strong) NSString *color;
@property (strong) NSString *brand;
@property (strong) NSNumber *registered;
@property (strong) NSNumber *mileage;
@end

@implementation Car
@end

Coding - Creating new objects

To create a new object on Syncano, make a new Instance of your just-defined Class, fill properties that you need, and save the object.

Swift:

let car = Car()  
car.model = "i350"  
car.year = 2030  
car.color = "invisible"  
car.brand = "Vesla"  
car.registered = true  
car.mileage = 10.0  
car.saveWithCompletionBlock { error in  
   if error == nil {
      print("Object saved without any issues")
   } else {
      print("We encountered error saving your object: \(error)")
   }
}

Obj-C:

Car *car = [[Car alloc] init];  
car.model = @"i350";  
car.year = @2030;  
car.color = @"invisible";  
car.brand = @"Vesla";  
car.registered = @YES;  
car.mileage = @10.0;  
[car saveWithCompletionBlock:^(NSError *error){
   if (error == nil) {
      NSLog(@"Object saved without any issues");
   } else {
      NSLog(@"We encountered error saving your object: %@", error);
   }
}];

Coding - downloading objects

To download objects, use the Please manager available on every SCDataObject Class (and its subclasses).

Swift:

Car.please().giveMeDataObjectsWithCompletion { objects, error in  
   guard error == nil, let objects = objects as? [Car] else {
      print("Error downloading objects: \(error)")
      return
   }
   for car in objects {
      print(car)
   }
}

Obj-C:

[[Car please] giveMeDataObjectsWithCompletion:^(NSArray * _Nullable objects, NSError * _Nullable error) {
   for (Car *car in objects) {
      NSLog(@"Car: %@",car);
   }
}];

This is the basic method that downloads the first 100 objects from Syncano (100 is the limit for how many objects you can download at once from Syncano -- learn more about our limits).

Coding - downloading objects, paging manually

If you need to download more objects, use paging. Every response from Syncano has a link to the next page of results and you can take advantage of that using the Please manager.

First, save an Instance of the Please manager in a variable:

Swift:

let carsPlease = Car.please()  

Obj-C:

SCPlease *carsPlease = [Car please];  

Now, you can ask for the first page, as well as the next and/or previous page of results.

Swift:

carsPlease.giveMeDataObjectsWithCompletion { objects, error in  
   //use cars
}
carsPlease.giveMeNextPageOfDataObjectsWithCompletion { objects, error in  
   //use cars
}
carsPlease.giveMePreviousPageOfDataObjectsWithCompletion { objects, error in  
   //use cars
}

Obj-C:

[carsPlease giveMeDataObjectsWithCompletion:^(NSArray * _Nullable objects, NSError * _Nullable error) {
   //use cars
}];
[carsPlease giveMeNextPageOfDataObjectsWithCompletion:^(NSArray * _Nullable objects, NSError * _Nullable error) {
   //use cars
}];
[carsPlease giveMePreviousPageOfDataObjectsWithCompletion:^(NSArray * _Nullable objects, NSError * _Nullable error) {
   //use cars
}];

Coding - downloading objects, enumerating pages

If you don't want to manually ask for the next pages (e.g., because you don't know how many pages you will need, or if you need all of the pages), you can use the enumerate method.

The ‘enumerate’ method goes through all available pages of data until it reaches the end, or until you explicitly tell it to stop.

Swift:

Car.please().enumaratePagesWithPredicate(nil, parameters: nil) { stop, objects, error in  
   //use cars
   //if you want to stop enumerating next pages, e.g. because you have enough data
   //just set stop.memory = true
   stop.memory = true
}

Obj-C:

[[Car please] enumaratePagesWithPredicate:nil parameters:nil withBlock:^(BOOL * _Nonnull stop, NSArray * _Nullable objects, NSError * _Nullable error) {
   //use cars
   //if you want to stop enumerating next pages,, e.g. because you have enough data
   //just set *stop = YES
   *stop = YES;
}];

Coding - filtering objects

If you need to get from Syncano only specific objects, matching given criteria, you can use filtering.

To filter objects in iOS, use the SCPredicate Class. It provides helper methods that allow for setting rules like:

There are many more operators available; you can learn more about Filtering and Ordering in our docs.

Let’s start with a simple example. I filled my Class with a number of cars from different years and with different mileages.

Sample cars

Download all cars from year 2030:

Swift:

let predicateYear2030 = SCPredicate.whereKey("year", isEqualToNumber: 2030)  
Car.please().giveMeDataObjectsWithPredicate(predicateYear2030, parameters: nil) { objects, error in  
   print("Cars: \(objects)")
   //use cars from year 2030, as stated with predicate
   //will return empty array if no objects match the criteria
   //will return error if you try to filter on a field without an index
}

Obj-C:

SCPredicate *predicateYear2030 = [SCPredicate whereKey:@"year" isEqualToNumber:@2030];  
[[Car please] giveMeDataObjectsWithPredicate:predicateYear2030 parameters:nil completion:^(NSArray * _Nullable objects, NSError * _Nullable error) {
   NSLog(@"Cars: %@", objects);
   //use cars from year 2030, as stated with predicate
   //will return empty array if no objects match the criteria
   //will return error if you try to filter on a field without an index
}];

It will look similar if you want to filter for cars from a year larger than year 2000:

Swift:

let predicateYearMoreThan2000 = SCPredicate.whereKey("year", isGreaterThanNumber:2000)   Car.please().giveMeDataObjectsWithPredicate(predicateYearMoreThan2000, parameters: nil) { objects, error in  
   //use cars
}

Obj-C:

SCPredicate *predicateYearMoreThan2000 = [SCPredicate whereKey:@"year" isGreaterThanNumber:@2000];  
[[Car please] giveMeDataObjectsWithPredicate:predicateYearMoreThan2000 parameters:nil completion:^(NSArray * _Nullable objects, NSError * _Nullable error) {
   //use cars
}];

You can also combine multiple predicates into one (e.g., get all cars from a year between 2030 and 2040):

Swift:

let predicateBetween2030and2040 = SCCompoundPredicate(predicates: [  
   SCPredicate.whereKey("year", isGreaterThanNumber: 2030),
   SCPredicate.whereKey("year", isLessThanNumber: 2040)
])        Car.please().giveMeDataObjectsWithPredicate(predicateBetween2030and2040, parameters: nil) { objects, error in
   //use cars
   print("Cars between 2030 and 2040: \(objects)")
}

Obj-C:

SCCompoundPredicate *predicateBetween2030and2040 = [[SCCompoundPredicate alloc] initWithPredicates:@[  
   [SCPredicate whereKey:@"year" isGreaterThanNumber: @2030],
   [SCPredicate whereKey:@"year" isLessThanNumber: @2040]                                                                                              ]];
[[Car please] giveMeDataObjectsWithPredicate:predicateBetween2030and2040 parameters:nil completion:^(NSArray * _Nullable objects, NSError * _Nullable error) {
   //use cars
   NSLog(@"Cars between 2030 and 2040: %@",objects);
}];

Coding - ordering objects

Sometimes you may need to get objects from Syncano in a sorted fashion (e.g., first get objects created earlier, or start with cars with lowest mileage, or get oldest cars first).

To define the order of objects downloaded from Syncano, use SCPleaseParameterOrderBy parameter and provide the name of the property.

If you want to reverse the order (from ascending to descending) add - before name of the property. For example:

Swift:

let parametersAscendingOrder = [ SCPleaseParameterOrderBy : "mileage" ]  
Car.please().giveMeDataObjectsWithParameters(parametersAscendingOrder) { objects, error in  
   //use cars sorted by mileage
   //if `mileage` doesn't have an `order` filter set on, this method will fail and provide error with a reason of the failure
}

Obj-C:

NSDictionary *parametersAscendingOrder = @{ SCPleaseParameterOrderBy : @"mileage" };  
[[Car please] giveMeDataObjectsWithParameters:parametersAscendingOrder completion:^(NSArray * _Nullable objects, NSError * _Nullable error) {
   //use cars sorted by mileage
   //if `mileage` doesn't have an `order` filter set on, this method will fail and provide error with a reason of failure
}];

Coding - ordering and filtering in one call

If you want, you can also combine filtering and ordering in one call to Syncano. To do that, just provide both parameters and predicate:

Swift:

let parametersDescendingOrder = [SCPleaseParameterOrderBy : "-mileage" ]  
let predicateYear2030 = SCPredicate.whereKey("year", isEqualToNumber:2030)  
Car.please().giveMeDataObjectsWithPredicate(predicateYear2030, parameters: parametersDescendingOrder) { objects, error in  
   //use filtered and sorted cars
}

Obj-C:

NSDictionary *parametersDescendingOrder = @{ SCPleaseParameterOrderBy : @"-mileage" };  
SCPredicate *predicateYear2030 = [SCPredicate whereKey:@"year" isEqualToNumber:@2030];  
[[Car please] giveMeDataObjectsWithPredicate:predicateYear2030 parameters:parametersDescendingOrder completion:^(NSArray * _Nullable objects, NSError * _Nullable error) {
   //use filtered and sorted cars
}];

User Management

Syncano also offers the ability to register and log in your users (as did Parse). To do that, you can use:

At this point, we do not have the ability to log in with Twitter for mobile devices, but it is something we are currently working on.

Signing up new users is really easy. First, make sure the API Key you use in your app allows for user creation (it must have "allow_user_create" flag).

Swift:

let username = "username"  
let password = "password"  
SCUser.registerWithUsername(username, password: password) { error in  
   //handle error if != nil
}

Obj-C:

NSString *username = @"username";  
NSString *password = @"password";  
[SCUser registerWithUsername:username password:password completion:^(NSError * _Nullable error) {
   //handle error if != nil
}];

Knowing an existing user’s username and password, you can log them in at any point.

Swift:

let username = "username"  
let password = "password"  
SCUser.loginWithUsername(username, password: password) { error in  
   //handle error if != nil
}

Obj-C:

NSString *username = @"username";  
NSString *password = @"password";  
[SCUser loginWithUsername:username password:password completion:^(NSError * _Nullable error) {
   //handle error if != nil
}];

If you use our mobile library, that's all you need to do. It will handle passing a user_key for you and, right after sign-up/login, all calls to Syncano will be sent as a user (which means, for example, that all objects you create from now on will be owned by this user).

In case you want to use authentication with Facebook, for example, there is only one method for both sign-up and login.

Swift:

let token = "token" //get your fb or google token in here  
SCUser.loginWithSocialBackend(.Facebook, authToken: token) { error in  
   //handle error if != nil
}

Obj-C:

NSString *token = @"token"; //get your facebook token in here  
[SCUser loginWithSocialBackend:SCSocialAuthenticationBackendFacebook authToken:token completion:^(NSError * _Nullable error) {
   //handle error if != nil
}];

If you need to get info if a user is logged in, ask the library for the current user. If it's nil, it means the user is not logged in. If it's not nil, the user is logged in and you can get some extra information about him, including his profile or his user_key if you ever needed to use it directly.

Swift:

if let currentUser = SCUser.currentUser() {  
   //currentUser != nil -- user is logged in
   let profile = currentUser.profile
   let user_key = currentUser.userKey;
}

Obj-C:

SCUser *currentUser = [SCUser currentUser];  
if (currentUser) {  
   //currentUser != nil -- user is logged in
   SCUserProfile *profile = [SCUser currentUser].profile;
   NSString *user_key = [SCUser currentUser].userKey;
}

IMPORTANT: User data will remain consistent for your app across launches. That means if you log a user in, quit the app, and then launch it again, he will still be logged in. To log him out, you need to call one line of code.

Swift:

SCUser.currentUser()?.logout()  

Obj-C:

[[SCUser currentUser] logout];

You can also subclass the user profile and user Class -- useful in case you wanted to add to a user profile some custom fields, such as first name, last name, phone number, etc. To learn more, read our guide on subclassing a user profile in our iOS Quickstart.

Push Notifications (APNS)

Configuring Push Notifications from Dashboard

To configure your push notifications in Parse, you go to App Settings -> Push screen.

On Syncano, you do that using Push Sockets -- in this case, using APNS Push Socket. Syncano uses the same .p12 files as Parse does, so the transition should be fairly smooth.

If you don't have your certificate and/or .p12 file yet, or your app isn't configured for using Push Notifications, I recommend that you go through our short tutorial and learn how to do that.

When you have your .p12 file, go into sockets to add a new one.

Add new socket

You will see new screen with multiple sockets to chose from - click Add next to the APNS Push Notifications socket.

Add new push socket

You will see a screen where you will be able to upload your production and development certificates. You can drag and drop them into designated fields or click on the UPLOAD .p12 CERTIFICATE buttons.

Upload certificates

Coding - Registering your iOS Device from your app

First, make sure your app has push notifications and receiving remote notifications enabled. Go into your app settings in XCode and switch them to the on position.

Enable push in XCode

Add a function that will take a device token in NSData * format and send it as a new device to Syncano. A good place for that function would be in the AppDelegate Class.

Swift:

func registerDeviceOnSyncano(deviceToken token: NSData) {  
   let device = SCDevice(tokenFromData: token)
   device.label = "My test app device"
   device.saveWithCompletionBlock { error in
      guard error == nil else {
         //if error says device was already registered (exists) - it's ok
         //it probably means we launched an app for the second time
         print(error!.userInfo["com.Syncano.response.error"]?.description)
         return
      }
   }
}

Obj-C:

- (void)registerDeviceOnSyncano:(NSData *)deviceToken {
    SCDevice *device = [[SCDevice alloc] initWithTokenFromData:deviceToken];
    device.label = @"My test app device";
    [device saveWithCompletionBlock:^(NSError * _Nullable error) {
        if (error != nil) {
            //if error says device was already registered (exists) - it's ok
            //it probably means we launched an app for the second time
            NSLog(@"%@",[error.userInfo[@"com.Syncano.response.error"] description]);
            return;
        }
    }];
}

Add functions that will register your device with Apple, so that you can receive push notifications (again, the best place for them is in your AppDelegate). If you're migrating your existing app from Parse, you probably already have some of these functions. In this case, just make sure you call the registerDeviceOnSyncano method after obtaining a deviceToken from Apple.

Swift:

func initializeNotificationServices() {  
   let settings = UIUserNotificationSettings(forTypes: [.Sound, .Alert, .Badge], categories: nil)
   UIApplication.sharedApplication().registerUserNotificationSettings(settings)
   UIApplication.sharedApplication().registerForRemoteNotifications()
}

func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {  
   registerDeviceOnSyncano(deviceToken: deviceToken)
}

func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {  
   print("Register for remote notifications failed: \(error.description)")
}

Obj-C:

- (void)initializeNotificationServices {
    UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeSound|UIUserNotificationTypeAlert|UIUserNotificationTypeBadge categories:nil];
    [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
    [[UIApplication sharedApplication] registerForRemoteNotifications];
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(nonnull NSData *)deviceToken {
    [self registerDeviceOnSyncano:deviceToken];
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError *)error {
    NSLog(@"Register for remote notifications failed: %@", error.description);
}

Add a function that will log to the console the content of received notifications.

Swift:

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {  
   print("Received notification: \(userInfo)")
   completionHandler(UIBackgroundFetchResult.NoData)
}

Obj-C:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler {
    NSLog(@"Received notification: %@", userInfo);
    completionHandler(UIBackgroundFetchResultNoData);
}

Ok -- we're close to finishing. The only thing left is to call initializeNotificationServices -- the best place for it would be inside the application:didFinishLaunchingWithOptions: method.

Swift:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {  
   initializeNotificationServices()
   return true
}

Obj-C:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(nullable NSDictionary *)launchOptions {
    [self initializeNotificationServices];
    return YES;
}

Every time you launch your app, it will try to register your device with Apple and then pass the received device token to Syncano. The only thing left to do is to actually test it by sending yourself a push notification.

Sending push notifications with Dashboard

Go back to your Dashboard and enter the list of your iOS devices. Select a device, enter its options, and then click on Send Message.

Send Message 01

If you're working in a development environment, make sure the Use sandbox switch is in the on position. Type your message content and click Confirm to send your message.

Send Message 02

Other Syncano features

We’ve covered the basics of what you can do with Syncano that you could also do with Parse. But Syncano offers so much more!

Advanced User management, Groups, writing and running Scripts, configuring a custom data output format with Templates, real-time synchronization with Channels, and much more!

Make sure to visit our docs for more information about what Syncano can do for you.

If you need help, join our Slack community or write to our support at [email protected].

You can also just leave your comments under this article -- one of us will be happy to respond.

Have fun using Syncano -- we're happy to have you on board! :)

Build powerful apps in half the time

Use our serverless platform to set up your backend in minutes.

Learn more

#ios #developer, #kravmaga learner, dev relations at @syncano. Smash fear, learn anything!