PhoneGap Plugin for the Dropbox Sync API

A couple of weeks ago, I started working on a PhoneGap application that required file synchronization between the users computer and their mobile device.

I looked at the new Dropbox Sync API and decided to give it a try.

The Dropbox Sync API is available for iOS and Android. So, I created a PhoneGap plugin to leverage that API in PhoneGap applications. In this post, I share the iOS version of the plugin along with a sample application.

Dropbox also provides a Core API that gives you lower level access to Dropbox files. But using that api, you’d have to write your own synchronization logic. When you use the Dropbox Sync API, you delegate all the complexities of file synchronization to Dropbox.

You can watch a video of the sample application here:

Steps

Here are the high level steps I used to create the plugin:

These are the steps to create the plugin from scratch. You don’t have to go through these steps if you just want to use the plugin in your own PhoneGap application. I’ll work on packaging the plugin so that it can be added to your PhoneGap application using the standard plugin installation procedure.

1.  Use the Dropbox Apps Console to create your own Dropbox application.

2.  Create a PhoneGap application as usual.

./create ~/phonegap/projects/pgsync org.coenraets.pgsync pgsync

3.  In Xcode, add the Dropbox Sync SDK to the PhoneGap project: see instructions here.

4.  Adjust AppDelegate.m to handle linking the application to the user’s Dropbox account.

First, add this code to didFinishLaunchingWithOptions():

DBAccountManager *accountManager =
    [[DBAccountManager alloc] initWithAppKey:@"YOUR_APP_KEY" secret:@"YOUR_APP_SECRET"];
[DBAccountManager setSharedManager:accountManager];
    
DBAccount *account = [accountManager.linkedAccounts objectAtIndex:0];
if (account) {
    DBFilesystem *filesystem = [[DBFilesystem alloc] initWithAccount:account];
    [DBFilesystem setSharedFilesystem:filesystem];
    NSLog(@"App linked successfully.");
}

Then, add the following function to the class:

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url
  sourceApplication:(NSString *)source annotation:(id)annotation {

    DBAccount *account = [[DBAccountManager sharedManager] handleOpenURL:url];
    if (account) {
        DBFilesystem *filesystem = [[DBFilesystem alloc] initWithAccount:account];
        [DBFilesystem setSharedFilesystem:filesystem];
        NSLog(@"App linked successfully.");
        return YES;
    }
    return NO;
    
}

5.  Create a plugin class called DropboxPlugin that exposes the Dropbox Sync API to the PhoneGap application. Implement it as follows:

#import "DropboxPlugin.h"

#import "AppDelegate.h"

#import <Dropbox/Dropbox.h>

@implementation DropboxPlugin


- (void) link:(CDVInvokedUrlCommand*)command
{
    NSLog(@"Executing link()");

    AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
    
    UIViewController *topView = appDelegate.viewController;
    
    CDVPluginResult* pluginResult = nil;

    [[DBAccountManager sharedManager] linkFromController:topView];
    
    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];

}

- (void) checkLink:(CDVInvokedUrlCommand*)command
{
    NSLog(@"Executing checklink()");

    CDVPluginResult* pluginResult = nil;
    
    DBAccount* account = [[DBAccountManager sharedManager] linkedAccount];
    
    pluginResult = [CDVPluginResult resultWithStatus:account ? CDVCommandStatus_OK : CDVCommandStatus_ERROR];
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}


- (void) unlink:(CDVInvokedUrlCommand*)command
{
    NSLog(@"Executing unlink()");

    CDVPluginResult* pluginResult = nil;

    [[[DBAccountManager sharedManager] linkedAccount] unlink];
    
    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

- (void)listFolder:(CDVInvokedUrlCommand*)command
{
    NSLog(@"Executing listFolder()");

    [self.commandDelegate runInBackground:^{
        CDVPluginResult* pluginResult = nil;
        NSString* path = [command.arguments objectAtIndex:0];
    
        DBPath *newPath = [[DBPath root] childPath:path];
        NSArray *files = [[DBFilesystem sharedFilesystem] listFolder:newPath error:nil];
    
        NSMutableArray *items = [NSMutableArray array];
    
        for (DBFileInfo *file in files) {
            NSLog(@"\t%@", file.path.stringValue);
            NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
            [dictionary setObject:file.path.stringValue forKey:@"path"];
            [dictionary setObject:[NSNumber numberWithLongLong:[file.modifiedTime timeIntervalSince1970]*1000] forKey:@"modifiedTime"];
            [dictionary setObject:@(file.size) forKey:@"size"];
            [dictionary setValue:[NSNumber numberWithBool:file.isFolder] forKey:@"isFolder"];
            [items addObject:dictionary];
        }
    
        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray: items];
        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
    }];
}


- (void)addObserver:(CDVInvokedUrlCommand*)command
{
    NSLog(@"Executing addObserver()");
    NSString* path = [command.arguments objectAtIndex:0];
    DBPath *newPath = [[DBPath root] childPath:path];
    
    [[DBFilesystem sharedFilesystem] addObserver:self forPathAndDescendants:newPath block:^() {
        NSLog(@"File change!");
        [self writeJavascript:@"dropbox_fileChange()"];
    }];
}


- (void)readData:(CDVInvokedUrlCommand*)command
{
    NSLog(@"Executing readData()");

    [self.commandDelegate runInBackground:^{
        CDVPluginResult* pluginResult = nil;
        NSString* path = [command.arguments objectAtIndex:0];
    
        DBPath *newPath = [[DBPath root] childPath:path];
        DBFile *file = [[DBFilesystem sharedFilesystem] openFile:newPath error:nil];
    
        NSData *data = [file readData:nil];
    
        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArrayBuffer: data];
        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
    }];
    
}


- (void)readString:(CDVInvokedUrlCommand*)command
{
    NSLog(@"Executing readString()");

    [self.commandDelegate runInBackground:^{
        CDVPluginResult* pluginResult = nil;
        NSString* path = [command.arguments objectAtIndex:0];
        
        DBPath *newPath = [[DBPath root] childPath:path];
        DBFile *file = [[DBFilesystem sharedFilesystem] openFile:newPath error:nil];
        
        NSString *data = [file readString:nil];
        
        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString: data];
        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
    }];
    
    
}

@end

6.  Write the JavaScript code that invokes the plugin methods. To keep things partitioned, I chose to encapsulate the plugin functionality in a module:

var dropbox = (function() {

    var pluginName = "org.coenraets.DropboxPlugin";

    var link = function() {
        var deferred = $.Deferred();
        Cordova.exec(
            function(result) {
                setTimeout(function() {
                    deferred.resolve(result);
                }, 1000);
            },
            function(error) {
                deferred.reject(error);
            },
            pluginName, "link", [""]);
        return deferred.promise();
    }

    var checkLink = function() {
        var deferred = $.Deferred();
        Cordova.exec(
            function(result) {
                deferred.resolve(result);
            },
            function(error) {
                deferred.reject(error);
            },
            pluginName, "checkLink", [""]);
        return deferred.promise();
    }

    var unlink = function() {
        var deferred = $.Deferred();
        Cordova.exec(
            function(result) {
                deferred.resolve(result);
            },
            function(error) {
                deferred.reject(error);
            },
            pluginName, "unlink", [""]);
        return deferred.promise();
    }

    var listFolder = function(path) {
        var deferred = $.Deferred();
        Cordova.exec(
            function(result) {
                deferred.resolve(result);
            },
            function(error) {
                alert("getFiles error");
                console.log("getFiles error");
                deferred.reject(error);
            },
            pluginName, "listFolder", [path]);
        return deferred.promise();
    }

    var addObserver = function(path) {
        var deferred = $.Deferred();
        Cordova.exec(
            function(result) {
                alert("addObserver result");
                deferred.resolve(result);
            },
            function(error) {
                deferred.reject(error);
            },
            pluginName, "addObserver", [path]);
        return deferred.promise();
    }

    var readData = function (fileName) {
        var deferred = $.Deferred();
        Cordova.exec(
            function(result) {
                deferred.resolve(result);
            },
            function(error) {
                deferred.reject();
            },
            pluginName, "readData", [fileName]);
        return deferred.promise();
    }

    var readString = function (fileName) {
        var deferred = $.Deferred();
        Cordova.exec(
            function(result) {
                deferred.resolve(result);
            },
            function(error) {
                deferred.reject();
            },
            pluginName, "readString", [fileName]);
        return deferred.promise();
    }

    return {
        link: link,
        checkLink: checkLink,
        unlink: unlink,
        listFolder: listFolder,
        addObserver: addObserver,
        readData: readData,
        readString: readString
    }

}());

Observing Changes

In addition to the file manipulation functions (listFolder, readString, readData), the Dropbox Sync API also comes with a handy method called addObserver() that allows you to watch changes to the Dropbox files. The plugin uses that method to invoke a JavaScript function (dropbox_fileChange) in the PhoneGap application when Dropbox files are changed. The PhoneGap application can then refresh the file list as appropriate.

Source code

The source code for the plugin and the sample application is available on GitHub:
https://github.com/ccoenraets/phonegap-dropbox-sync.

This is definitely work in progress. I will continue to update the repository to make the plugin easier to use in an existing app.

  • Hi Christophe – an improvement if I may: https://github.com/ccoenraets/phonegap-dropbox-sync/issues/1

    • Christophe Coenraets

      Thanks Shazron! I’m on it :)

  • tomByrer

    Thanks for the post; I’ve been looking at both PhoneGap & DropBox sync, & you’ve helped quell my worries.

    I am a bit concerned about using addObserver() though; I assume it will prevent the mobile radio from going into a sleep mode? I’d prefer a manual check for updates.

  • Mosselman

    Hi, thanks for the code.

    What is the flow though? Will a user’s account ‘just’ be coupled from his phone? Is there some sort of login? Does someone who uses the app have to approve the link?

  • Pingback: Best of JavaScript, HTML & CSS – Week of June 10, 2013 | Flippin' Awesome()

  • Hans B

    Thank you for this interesting plugin…

  • Sorry but I have to ask… Why didn’t you approve my comment?

  • Prima donna ?

  • Hello my name is Sander and i am student from the Netherlands. I am working on my first app, the idea is to build a music player wich playes files from a shared dropbox folder.

    I am using Phonegap and you’re solution seems perfect. I was wondering if you (or anyone else who understands) can help me by installing the plugin.

    My setup is very basic, and all i got so far is a running phonegap app with jQuery mobile.
    i’ve included the javascript files (app.js dropbox.js and encoder.js).
    i also have included the plugins in the plugin folder.
    i want to overwrite the AppDelegate.m with your’s but if i do so i get the following error: it can’t find “Dropbox/Dropbox.h”

    Now i have the following questions of ny own why this might not be working.
    1. Where can i find Dropbox.h and where do i put it?
    2. i’ve a dropbox app key but it’s not used, is it required anywhere?
    3. I get the following error: plugin ‘org.coenreats.dropboxPlugin’ not found, or is not a CDVPlugin. check your pluggin mapping in config.xml. What to do with this error, what do i do with config.xml?

    I understand this might not be easy to teach to me but if you try it would be appreciated! Also the school assignment is almost due so i quick reply would be great! Any screencast showing steps A-Z for installing the plugin in a basic hello world phonegap app would be great but might be to much to ask for.

    Thanks in advance, Sander

  • ich danke Ihnen für den Austausch großer Beitrag

  • Article writing is also a excitement, if you be acquainted with then you can write otherwise it is complex to write.

  • Definitely believe that that you said. Your favorite justification appeared to be on the web the simplest factor to have in mind of. I say to you, I definitely get irked even as other folks think about concerns that they plainly do not know about. You controlled to hit the nail upon the top and also defined out the whole thing with no need side effect , other folks could take a signal. Will likely be again to get more. Thank you

  • David

    When you posted this last June you mentioned you planned to update the repository. SInce then – no updates on this version, although looks like an Android version is available here: https://github.com/rossmartin/phonegap-dropbox-sync.

    Wondering if you had/have any updates, follow-up, or plans to do so? Thanks Christophe (and Ross Martin for the Android version)…

    • Hey you’re welcome for the Android version of the plugin. I just added some new features to it today.

      I also updated the UI on the sample app and integrated some Topcoat Effeckt features (modal and off screen nav menu).

      Topcoat Effeckt is silky smooth in PhoneGap! Check out my updated version of the plugin out to see :~)

  • robert

    Natural Seo and moral Seo are the other names of cost-effective SEO. The idea of cost-effective SEO is based on human queries as it will help in getting the real visitors to web page. check out

  • robert

    Article submission, topic, and search phrases are the key problems to pay attention to in material promotion. go to website

  • Robert

    Using this choice will improve the great high quality of your weblogs to a degree. serruriers

  • goood thanks admins

  • Thanks you admin coenraets Ka-ran-lik – Dead ASDa

  • The website is looking bit flashy and it catches the visitors eyes. Design is pretty simple and a good user friendly interface.
    Engineering and Construction

  • Hi there! Nice stuff, do keep me posted you post again something like this!causes of lower back pain in women

  • Carlos Melgoza

    Hi, im creating my first app, with phonegap, and phonegap build, and i want to know how to add this plug in my config.xml, or what i have to do instead of doing all the objective-c part.

  • His highest production of our company, we use the sport socks products: cotton, cotton, polyester, nylon and lycra. It includes anti-bacterial..http://jonsunsport.com/spor-coraplari.html

  • work on Android 4.0.4.anks admin

css.php