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.

14 Responses to PhoneGap Plugin for the Dropbox Sync API

  1. Shazron June 10, 2013 at 12:26 pm #

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

    • Christophe Coenraets June 10, 2013 at 12:59 pm #

      Thanks Shazron! I’m on it :)

  2. tomByrer June 10, 2013 at 11:33 pm #

    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.

  3. Mosselman June 16, 2013 at 2:26 pm #

    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?

  4. Hans B July 25, 2013 at 10:10 am #

    Thank you for this interesting plugin…

  5. Ross Martin September 5, 2013 at 7:05 pm #

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

  6. Ross Martin September 6, 2013 at 9:47 am #

    Prima donna ?

  7. Sander October 10, 2013 at 8:14 am #

    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

  8. handelsregister October 26, 2013 at 2:32 am #

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

  9. Reina October 26, 2013 at 4:19 pm #

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

  10. Laurel October 26, 2013 at 6:55 pm #

    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

  11. David January 22, 2014 at 11:19 am #

    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)…

    • Ross Martin March 5, 2014 at 12:08 am #

      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 :~)

Trackbacks/Pingbacks

  1. Best of JavaScript, HTML & CSS – Week of June 10, 2013 | Flippin' Awesome - June 17, 2013

    [...] Christophe Coenraets created a new PhoneGap plugin for working with the the Dropbox Sync API. PhoneGap Plugin for the Dropbox Sync API [...]

Leave a Reply

css.php