Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
former_member190800
Contributor

The SODataOfflineStore contains two methods for synchronizing the local Ultralite database with an OData backend via Mobilink:  flush:, and refresh:.

Flush:  submits all pending changes on local database to backend

Refresh:  pulls all changes from backend, and commits in local database

The two will commonly be used in sequence:  (1) flush the local changes, then (2) pull the remote changes.

Both are implemented as sets of delegate methods:  <SODataOfflineStoreRefreshDelegate, SODataOfflineStoreFlushDelegate>, but it is simple to wrap them into completion blocks, and then chain them in a single method.


Here is an example of how to convert the SODataOfflineStoreRefreshDelegate to a single block-style interface, named refresh:(void(^)(BOOL success))completion.  When the refresh is complete, the completion block will be called, passing success == YES on success, and success == NO on failure.

Edit:  A concern about the standard NSNotification API's from Apple is their propensity to create unexpected retain cycles.  There's a seminal blog post on this topic here.  NickLockwood has written a nice extension on the addObserver: method, named FXNotifications that does a good job of eliminating this issue.  The examples below will use the FXNotifications version of the API.  To add to your project, download the repo, and include FXNotifications.h/.m into your project.


#pragma mark - OfflineStoreRefresh block wrapper

- (void) refresh:(void(^)(BOOL success))completion

{

    NSString *refreshFinishedNotification = [NSString stringWithFormat:@"%@.%@", kRefreshDelegateFinished, self.description];

    NSString *refreshFailedNotification = [NSString stringWithFormat:@"%@.%@", kRefreshDelegateFailed, self.description];

   

    [[NSNotificationCenter defaultCenter] addObserver:self forName:refreshFinishedNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note, id observer) {

       

        [[NSNotificationCenter defaultCenter] removeObserver:observer name:refreshFinishedNotification object:observer];

   

        completion(YES);

    }];

   

    [[NSNotificationCenter defaultCenter] addObserver:self forName:refreshFailedNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note, id observer) {

       

        [[NSNotificationCenter defaultCenter] removeObserver:observer name:refreshFailedNotification object:observer];

   

        completion(NO);

    }];

   

    [self scheduleRefreshWithDelegate:self];

}

#pragma mark - OfflineStore Refresh Delegate methods

- (void) offlineStoreRefreshSucceeded:(SODataOfflineStore *)store {

    NSString *refreshFinishedNotification = [NSString stringWithFormat:@"%@.%@", kRefreshDelegateFinished, self.description];

    [[NSNotificationCenter defaultCenter] postNotificationName:refreshFinishedNotification object:nil];

}

- (void) offlineStoreRefreshFailed:(SODataOfflineStore *)store error:(NSError *)error{

     NSString *refreshFailedNotification = [NSString stringWithFormat:@"%@.%@", kRefreshDelegateFailed, self.description];

    [[NSNotificationCenter defaultCenter] postNotificationName:refreshFailedNotification object:error];

}

-(void)offlineStoreRefreshFinished:(SODataOfflineStore *)store {

}

-(void)offlineStoreRefreshStarted:(SODataOfflineStore *)store {

}

You'll probably recognize this pattern from the post Using Blocks with the SAP Mobile SDK 3.0 SP05+ on using blocks with the scheduleRequest: API.  First, subscribe to notifications that will be fired when the delegate callbacks are invoked.  Second, prepare to call your completion block with the correct parameters when the notifications are observed.  Last, call the original method ( i.e.: [self scheduleRefreshWithDelegate:self]), to start the process.

I put this code in my OfflineStore<SODataOfflineStore> implementation.  By following the same pattern for the SODataOfflineStoreFlush delegate methods, we can then chain the flush: and refresh: in one method, named flushAndRefresh(void(^)(BOOL))completion.

#pragma mark - FlushAndRefresh block wrapper

- (void) flushAndRefresh:(void (^)(BOOL))completion

{

    [self flush:^(BOOL success) {

        if (success) {

            [self refresh:^(BOOL success) {

                if (success) {

                    completion(YES);

                } else {

                    completion(NO);

                }

            }];

        } else {

            completion(NO);

            }

    }];

}

This flushAndRefresh: method could be added to the interface of your SODataOfflineStore implementation.  I chose instead to put it in my own protocol, "ODataStore", which already contained my openStoreWithCompletion: block that is implemented by both my OnlineStore and OfflineStore classes.

#import <Foundation/Foundation.h>

@protocol ODataStore <NSObject>

@required

- (void) openStoreWithCompletion:(void(^)(BOOL success))completion;

@optional

- (void) flushAndRefresh:(void(^)(BOOL success))completion;

@end


The flushAndRefresh: is only relevant for the SODataOfflineStore; the SODataOnlineStore does not use the local Ultralite database, so all requests are immediately tried over the network.








Now that the flush: and refresh: delegate methods are wrapped into a single completion block, we can connect the method to a UITableViewController's UIRefreshController!  The following snippet can be added to any UITableViewController, to sync the local database when the end user drags the table view down.

@implementation MyUITableViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    self.refreshControl = [[UIRefreshControl alloc] init];

    [self.refreshControl addTarget:self action:@selector(refreshStore) forControlEvents: UIControlEventValueChanged];

}

#pragma mark UIRefreshControl

- (void)refreshStore

{

    [[[DataController shared] localStore] flushAndRefresh:^(BOOL success) {

        if (success) [self.tableView reloadData];

        [self.refreshControl endRefreshing];

    }];

}

Resources for this project are located here:  SAP/STSOData · GitHub.

For extra credit, customize the look-and-feel of your UIRefreshController, using some of the examples here:  http://www.appcoda.com/pull-to-refresh-uitableview-empty/, to update your end user of the progress of the flush and refresh.

// happy coding!