Documentation : Simple sync - iOS

This example shows a simple way to share data between multiple users and devices.

Application consists of only one entity, which has one field. Main screen of application contains table with entities. User can add new entity and synchronize with mobeelizer cloud. There are two users on purpose. During runtime one can change users to observe synchronization.

Design Your App

Create new application in App Designer called SimpleSync.

Secondly, create new model called SimpleSyncEntity with text field - title.

For now leave conflict resolving property on overwrite. By default, there is one group - users, device category - mobile and role - users-mobile define. Deploy application to test environment. On test environment create two users with passwords:

  • a - usera
  • b - userb

Finally, download configured template for iOS project. Change class prefix to SS and model prefix to MSS.

Use the Mobeelizer SDK

Open project in XCode. We can see that several files has been generated and configured.  

Configure AppDelegate

First of all, there is app delegate with configured creation and destruction of Mobeelizer object. Generated application does not have any view. We will add storyboard: SSStoryboad to our application and set newly created storyboard as Main Storyboard in project settings. Furthermore, we have to remove setting window from application:didFinishLaunchingWithOptions: in SSAppDelegate

SSAppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Initialize Mobeelizer SDK
    [Mobeelizer create];

    return YES;
}
- (void)applicationWillTerminate:(UIApplication *)application
{	
    // Terminate Mobeelizer SDK
    [Mobeelizer destroy];
}

SSAppDelegate.m

Configure Mobeelizer entity

Both application.xml and Mobeelizer.plist were generated. Moreover, model has been generated for SimpleSyncEntity. We will add guid and owner fields together with custom costructor. We will also overwrite isEqual function to compare guid.

MSSSimpleSyncEntity.h
@property(strong, nonatomic) NSString* title;
@property(strong, nonatomic) NSString* guid;
@property (nonatomic, strong) NSString* owner;
- (MSSSimpleSyncEntity*) initWithTitle:(NSString*)title;
MSSSimpleSyncEntity.m
- (MSSSimpleSyncEntity*) initWithTitle:(NSString*)title{
    self.title = title;
    return self;
}

- (BOOL)isEqual:(id)object {
    if ([object isKindOfClass:[MSSSimpleSyncEntity class]]) {
        MSSSimpleSyncEntity* entity = object;
        if ([entity.guid isEqualToString:self.guid]) {
            return YES;
        }
    }
    return NO;
}

MSSSimpleSyncEntity.m MSSSimpleSyncEntity.h

Create user login and logout manager

Next, we will handle user functionality. We will create singleton class SSUserManager, which will hold information about currently logged user. There are two use cases to handle user functionality. Firstly, when application starts user 'a' will be logged in. Secondly, during runtime, users can be switched.

SSUserManager.h
@property (copy, nonatomic) NSString* user;

+ (SSUserManager*)instance;
- (BOOL) performLoginAsUser:(NSString *)login;
- (void) switchUser;
SSUserManager.m
- (BOOL) performLoginAsUser:(NSString *)login{
    self.user = login;
    NSString* password = nil;
    if ([login isEqualToString:USER_A]) {
        password = @"usera";
    } else {
        password = @"userb";
    }

	//Login to Mobeelizer instance
    MobeelizerOperationError *error = [Mobeelizer loginToInstance:@"test" withUser:login andPassword:password];

    if(error != nil) {
        NSLog(@"Joining failed: %@ - %@", error.code, error.message);
    }
    return error == nil;
}

-(void) switchUser{
    if ([self.user isEqualToString:USER_A]) {
        self.user = USER_B;
    } else {
        self.user = USER_A;
    }

    [self performLogout];
    [self performLoginAsUser:self.user];
}

- (void) performLogout {
	//Logout from Mobeelizer
    [Mobeelizer logout];
}

SSUserManager.h SSUserManager.m

Next, we will move to user interface. Add a new file to project. Choose the UITableViewController subclass template and name the class SSUserContextController. We will add button to navigation bar, which will handle switching users. Controller will hold reference to button, because we will change appearance of button to show currently logged user.

SSUserContextController.h
#define RGB(r, g, b) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1]
#define USER_A_COLOR RGB(255, 128, 0)
#define USER_B_COLOR RGB(0, 128, 0)

@property (strong, nonatomic) UIBarButtonItem *userButton;

We add button to navigation bar and log user 'a' on viewDidLoad:

SSUserContextController.m
- (void)viewDidLoad{
    [super viewDidLoad];
    [[self navigationController] setNavigationBarHidden:NO animated:NO];
    userButton = [[UIBarButtonItem alloc] initWithTitle:@"User" style:UIBarButtonItemStyleBordered target:self action:@selector(userButtonClicked:)];
    self.navigationItem.rightBarButtonItem = userButton;

    [[SSUserManager instance] performLoginAsUser:USER_A];
}

Now add function to handle button click:

SSUserContextController
- (IBAction)userButtonClicked:(id)sender {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [[SSUserManager instance] switchUser];

        dispatch_async(dispatch_get_main_queue(), ^{
            [self refreshUserButton];

            [UIView beginAnimations:@"View Flip" context:nil];
            [UIView setAnimationDuration:0.75];
            [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
            [UIView setAnimationTransition: UIViewAnimationTransitionFlipFromRight forView:self.view cache:YES];
            [self reloadData];
            [UIView commitAnimations];
        });
    });
}

- (void)refreshUserButton {
    if ([[SSUserManager instance].user isEqualToString:USER_B]) {
        [userButton setTitle:@"B"];
        [userButton setTintColor:USER_B_COLOR];
    } else {
        [userButton setTitle:@"A"];
        [userButton setTintColor:USER_A_COLOR];
    }
}

SSUserContextController.h SSUserContextController.m

Currently, if any controller extends SSUserContextController, it will have functionality of switching users.

Create entity table view controller

Now, we will create view. Add navigation controller with table view controller to storyboard. Set prototype cell identifier to SimpleSyncCell and set style to basic. Next, add a new file to the project. Choose the SSUserContextController subclass template and name the class SSSimpleController. Connect newly created controller to its design in storyboard. SSSimpleController will hold an array of table records.Moreover, we will mark added records with user image. 

SSSimpleController.h
@property (strong, nonatomic) NSMutableArray *currentItems;
@property (strong, nonatomic) UIImage* userAImage;
@property (strong, nonatomic) UIImage* userBImage;

First of all, we will add two buttons to toolbar. First one will add items and another will synchronize with Mobeelizer cloud.

SSSimpleController.m
-(void)addToolbarButtons{
    UIBarButtonItem* newButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(newClicked:)];
    [newButton setStyle:UIBarButtonItemStyleBordered];

    UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
    [space setWidth:10];

    UIBarButtonItem* syncButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(syncClicked:)];
    [syncButton setStyle:UIBarButtonItemStyleBordered];

    self.toolbarItems = @[newButton, space, syncButton];
    self.navigationController.toolbarHidden = NO;
}
- (void)viewDidLoad{
    [super viewDidLoad];
    [self addToolbarButtons];

    self.currentItems = [[NSMutableArray alloc] initWithArray:[self getItemsList]];
    userAImage = [UIImage imageNamed:@"userA.png"];
    userBImage = [UIImage imageNamed:@"userB.png"];
}

Creating new item takes random movie title and creates MSSSimpleSyncEntity. New entity is saved in Mobeelizer database and then is inserted into table. Movie titles are sorted, that is why we insert new title into correct place.

SSSimpleController.m
- (void)newClicked:(id)sender {
    MSSSimpleSyncEntity* newItem  = [[MSSSimpleSyncEntity alloc] initWithTitle:[[SSMovies instance] getRandomMovie]];
    [[Mobeelizer database] save:newItem];

    currentItems = [[NSMutableArray alloc] initWithArray:[self getItemsList]];

    NSInteger row = -1;
    for (NSInteger i=0; i<currentItems.count; i++) {
        if ([newItem isEqual:(MSSSimpleSyncEntity*)currentItems[i]]) {
            row = i;
            break;
        }
    }
    NSIndexPath* indexPath = [NSIndexPath indexPathForRow:row inSection:0];

    [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
    [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}
- (NSArray*)getItemsList {
	//Get MSSSimpleSyncEntity from Mobeelizer database
    MobeelizerCriteriaBuilder *criteria = [[Mobeelizer database] find:[MSSSimpleSyncEntity class]];
    criteria = [criteria addOrder:[MobeelizerOrder asc:@"title"]];

    return [criteria list];
}

Second button is responsible for synchronization. After synchronization finishes we have to reload data in table.

SSSimpleController.m
- (void)syncClicked:(id) sender {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
		//Synchronize with Mobeelizer cloud
        MobeelizerOperationError *error = [Mobeelizer sync];
        if(error != nil) {
            NSLog(@"Sync failure: %@ - %@", error.code, error.message);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            currentItems = [[NSMutableArray alloc] initWithArray:[self getItemsList]];
            [self.tableView reloadData];
        });
    });
}

Finally, we have to configure our table view controller.

SSSimpleController.m
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return currentItems.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MSSSimpleSyncEntity* item = currentItems[[indexPath row]];
    UITableViewCell* cell = [self createCellForItem:item atRow:indexPath.row];

    if ([item respondsToSelector:@selector(owner)]) {
        UIImageView* userLabel = (UIImageView*)[cell.contentView viewWithTag:1001];
        CGFloat userLabelY = (cell.contentView.frame.size.height - 30) /2; // 7
        CGFloat userLabelX = (cell.contentView.frame.size.width - 37); // 283

        if (userLabel == nil) {
            CGRect userLabelRect = CGRectMake(userLabelX, userLabelY, 30, 30);
            userLabel = [[UIImageView alloc] initWithFrame:userLabelRect];
            [userLabel setTag:1001];
            [cell.contentView addSubview:userLabel];
        }
        if ([item.owner isEqual:USER_A]) {
            userLabel.image = userAImage;
        } else {
            userLabel.image = userBImage;
        }
    }
    return cell;
}

- (UITableViewCell*)createCellForItem:(MSSSimpleSyncEntity*)item atRow:(NSInteger)row {
    static NSString *CellIdentifier = @"SimpleSyncCell";
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    cell.textLabel.text = item.title;
    return cell;
}

SSSimpleController.h SSSimpleController.m

When adding new records to table, we were using SSMovies singleton to generate random movie title.

SSMovies.h
+ (SSMovies*)instance;
- (NSString*)getRandomMovie;

There is private array initialized with movies from sample plist.

SSMovies.m
+ (SSMovies*)instance {
    if (sharedInstance == nil) {
        sharedInstance = [[SSMovies alloc] init];
    }
    return sharedInstance;
}

- (id)init {
    if(self = [super init]){
        NSString *path = [[NSBundle mainBundle] pathForResource:@"Movies" ofType:@"plist"];
        movies = [[NSArray alloc] initWithContentsOfFile:path];
    }
    return self;
}

- (NSString*)getRandomMovie {
    int randomIndex = arc4random() % [movies count];
    return [movies[randomIndex] valueForKey:@"title"];
}

SSMovies.h SSMovies.m

There are few files, which are necessary Movies.plistuserA.pnguserB.png.

Conclusion

To sum up, this is the simplest example of using synchronization with Mobeelizer. Our model consists of only one field. We can create sample records and synchronize with Mobeelizer cloud.

Attachments:

SimpleSync.png (image/png)
SimpleSyncEntity.png (image/png)
TitleField.png (image/png)
AddUser.png (image/png)
SimpleSyncTemplate.png (image/png)