<

How to save your database to Dropbox (and how to restore it) from within a Cordova application - Android only (part 3)

Published on
3,152 Points
152 Views
Last Modified:
Marco Gasi
Freelance, I like to share what I know. Find out my articles in my learner-to-learners blog codingfix.com
Creating a Cordova application which allow user to save to/load from his Dropbox account the application database.

In this third part of our tutorial article about how to save your database to Dropbox (here abbreviated to DBX, in uppercase) from within a Cordova mobile application for Android devices, we'll concentrate on the specific steps needed to

  • connect to DBX and to save to Dropbox our MySQL dump file
  • retrieve it from DBX and use it to fill out our database

this can be useful if our user has uninstalled our application or if for some reason the local backup file has been lost or corrupted.


Creating a Dropbox app


The first thing we have to do is to create an app in the Dropbox dashboard in order to successfully login using Oauth2. A very good tutorial can be found here and I encourage you to read it carefully if you have some doubt. Here I quickly summarize the needed steps:

  1. navigate to https://www.dropbox.com/developers
  2. login into your account ;)
  3. create a new app clicking on the "Create your app" link in the center of the page
  4. choose Dropbox API
  5. decide if you want/need full access to DBX or if you just need to access a single folder created specifically for your app
  6. set a name for your app and click the blue "Create app" button
  7. take note of your app key and your app secret; also notice and remember the redirect URI, which for Oauth2 must be https://www.dropbox.com/1/oauth2/redirect_receiver

Now your DBX app is created and it will allow you to login to DBX from your hybrid mobile application.

Getting Dropbox Javascript SDK

To use the API you have to install the Dropbox JavaScript SDK, a javascript interface to the Dropbox v2 API. To install it, open a command prompt or a terminal window and type the following command:

npm install --save dropbox

Now, if you are familiar with webpack or browserify, you can just use the SDK as explained in the official Github repository. But if you don't want or just don't know how to use those tools, go the directory where the SDK has been installed, copy the file Dropbox-sdk.min.js from the dist directory of the package to www/js folder of your app and then link it in your index.html:

<script type="text/javascript" src="js/Dropbox-sdk.min.js"></script>

Now we are finally ready to rock!


Back to the app


Now it's time to install the last plugin we need to go ahead, cordova-plugin-inappbrowser. Just navigate to your project root directory with your CLI and type:

cordova plugins add cordova-plugin-inappbrowser

Once installed, we have to tweak it a bit. In fact, if we didn't apply this little fix, after having logged int DBX account, we would remain blocked on a blank page and we'd never be redirected to our app. So open in your code editor the file platforms/android/platform_www/plugins/cordova-plugin-inappbrowser/www/inappbrowser.js. Near the bottom of the script, around the line 110 or so, immediately after this line:

strWindowFeatures = strWindowFeatures || "";

Put the following 3 lines:

if (strUrl == 'https://www.dropbox.com/1/oauth2/redirect_receiver') {
    strWindowFeatures += "location=no,hidden=yes";
}

This way we're telling inappbrowser to hide itself if the url is the redirect url set in our DBX app.

Allow origin?

Another issue I run into is the error message returned by Dropbox when I tried to save a file the first time. This was the message:

Error: Request has been terminated Possible causes: the network is offline, Origin is not allowed by Access-Control-Allow-Origin, the page is being unloaded, etc.

To make a long story short, just replace your Content-Security-Policy meta in your index.html with this one (or add it, if it is missing):

<meta http-equiv="Content-Security-Policy" content="default-src *; style-src 'self' http://* 'unsafe-inline'; script-src 'self' http://* 'unsafe-inline' 'unsafe-eval'; media-src *" />

The error is gone, and the app runs. Great!


Setting up Dropbox connection


As said above, DBX API uses OAuth2 to manage authentication. So the first thing we have to do is to prepare some stuff to allow our app to login into DBX:

var CLIENT_ID = 'xxxxxxxxxxxxxxx';//we use App key as our client id 
var dbxt; //an object to store our Dropbox connection and its access token 
var dbx = new Dropbox({clientId: CLIENT_ID}); //the starting Dropbox object

Put above lines immediately after the app.initialize  call, before the other functions we have already written to operate on our database. This is not mandatory, of course: you can put everywhere you're most comfortable with :)


UI modifications


To go ahead with our little project, we have now to slightly change our UI. That is, we'll modify the behavior of the buttons which export and import respectively the database: instead, to actually perform these actions, they will just make visible another div with 2 buttons in order to allow the user to choose

  • if export the database only locally or even to DBX
  • if import the database from a local file or from DBX


Changes to the markup


Let's go to change our index.html first: just replace the buttons section with the following markup:

        <div class="centered">
            <h3>Select a country:</h3>
            <select id="countries">
            </select>
            <a href="#" id="createDB" class="btn btn-primary">Create tables</a>
            <a href="#" id="emptyDB" class="btn btn-primary">Empty database</a>
            <a href="#" id="setExportOptions" class="btn btn-primary">Export database</a>
            <div id="exportOptions" class="impexpOptions">
                <ul>
                    <li>
                        <a href="#" id="exportDB" class="btn btn-inline btn-primary">Save locally</a>
                    </li>
                    <li>
                        <a href="#" id="exportDbToDropbox" class="btn btn-inline btn-primary">Save to Dropbox</a>
                    </li>
                </ul>
            </div>
            <a href="#" id="setImportOptions" class="btn btn-primary">Import database</a>
            <div id="importOptions" class="impexpOptions">
                <ul>
                    <li>
                        <a href="#" id="importDB" class="btn btn-inline btn-primary">Restore from device</a>
                    </li>
                    <li>
                        <a href="#" id="importDbFromDropbox" class="btn btn-inline btn-primary">Restore from Dropbox</a>
                    </li>
                </ul>
            </div>
        </div>

In your index.css file we add some rules:

.btn-inline{
    margin: 10px 0 !important;
    padding: 5px 10px !important;
}
.impexpOptions{
    display: none;
}
.impexpOptions ul{
    padding: 0;
}
.impexpOptions ul li{
    display: inline-block;
}


Changes to the logic


In your index.js file look for the event handlers of our button and replace them with the following ones:

    $('#createDB').click(function (e) {
        e.preventDefault();
        createTables();
    });

    $('#emptyDB').click(function (e) {
        e.preventDefault();
        dropTables();
    });

    $('#setExportOptions').click(function (e) {
        e.preventDefault();
        $('#exportOptions').slideToggle(400, function(){
            $('html, body').animate({
                scrollTop: $('#exportOptions').offset().top
            }, 600);
        });
    });

    $('#exportDB').click(function (e) {
        e.preventDefault();
        exportBackup(false);
    });

    $('#exportDbToDropbox').click(function (e) {
        e.preventDefault();
        exportBackup(true);
    });

    $('#setImportOptions').click(function (e) {
        e.preventDefault();
        $('#importOptions').slideToggle(400, function () {
            $('html, body').animate({
                scrollTop: $('#importOptions').offset().top
            }, 600);
        });
    });

    $('#importDB').click(function (e) {
        e.preventDefault();
        importBackup(false);
    });

    $('#importDbFromDropbox').click(function (e) {
        e.preventDefault();
        importBackup(true);
    });



Wow, several things are changed here! So, let's summarize the changes:

  • we have added 2 event handlers for buttons with id setExportOptions and setImportOptions: here we just show hidden divs with buttons to actually perform actions (notice we have added a callback to scroll the page and always get the 2 new buttons visible)
  • besides the buttons with ids exportDB and importDB  we have added 2 new buttons with ids exportDbToDropbox and importDbFromDropbox
  • calling exportBackup() and importBackup() functions, we have used a boolean parameter we didn't use before: this parameter is set to false to export the database only locally and to import the database from a local backup file; it's set to true, instead, if we want to save our backup to our DBX account or if we want to restore our datbase from a remote backup file saved into our DBX account

In fact, we're going to modify our exportBackup() and importBackup() functions in order to accept such as paramter and acting accordingly to its value.


Connecting to Dropbox


Before to rewrite our functions to integrate DBX code, let's see examine how we manage the connection through Oauth2. First the code (excerpt from exportBackup() function):

if (dbxt == null) {
    dbx.authenticateWithCordova(function (accessToken) {
        dbxt = new Dropbox({accessToken: accessToken});
                //your stuff here
} else {
        //your stuff here
}

Let's analyze this code. First, we check if dbxt is null. What is dbxt? Is a tokenized DBX connection, that is the object we use once authenticated in DBX. As you remember, we have declared this object immediately after the call to app..initialized method. And immediately after having declared this object we had set up another object we use to perform the login to DBX, the dbx object:

var dbxt; 
var dbx = new Dropbox({clientId: CLIENT_ID})

So, if we didn't already established a valid connection to DBX (dbxt is null), then we use the authenticateWithCordova() method of the object dbx to perform the login and get an accessToken which will identify our connection as an authorized connection. This access token will be a property of the dbxt object which will allow us to perform the actions we need to do. If we already have established a connection to DBX, we just go ahead with our stuff :)


Uploading files


To export our database to DBX we have to use the filesUpload() and filesDownload() methods of our dbxt object. Let's see our new exportBackup() function:

function exportBackup(toDropbox) {
    var successFn = function (sql) {
        window.resolveLocalFileSystemURL(cordova.file.dataDirectory, function (dirEntry) {
            dirEntry.getDirectory('/dropboxTestBackup', {create: true}, function (dirEntry) {
                dirEntry.getFile('dropboxTestBackup.sql', {create: true}, function (fileEntry) {
                    alert("Create the file: " + fileEntry.name + '. You can find it in your Internal storage at the following path: Android/data/com.example.dropboxTest/files/dropboxTestBackup/');
                    writeFile(fileEntry, sql);
                    if (toDropbox) {
                        if (dbxt == null) {
                            dbx.authenticateWithCordova(function (accessToken) {
                                dbxt = new Dropbox({accessToken: accessToken});
                                dbxt.filesUpload({
                                    path: '/' + fileEntry.name,
                                    contents: sql,
                                    mode: 'overwrite',
                                    mute: true
                                }).then(function (response) {
                                    alert('Your backup has been successfully uploaded to your Dropbox!')
                                    console.log(response);
                                }).catch(function (error) {
                                    alert('Error saving file to your Dropbox! You can retry or manually copy the file Android/data/com.webintenerife.qbook/files/qbookBackup/qbook.sql to your Dropbox folder or in another secure place.')
                                    console.error(error.message);
                                });
                            }, function (e) {
                                console.log("failed Dropbox authentication");
                                console.log(e.message);
                            });
                        } else {
                            console.log('export: user already authenticated');
                            dbxt.filesUpload({
                                path: '/' + fileEntry.name,
                                contents: sql,
                                mode: 'overwrite',
                                mute: true
                            }).then(function (response) {
                                alert('Your backup has been successfully uploaded to your Dropbox!')
                                console.log(response);
                            }).catch(function (error) {
                                alert('Error saving file to your Dropbox! You can retry or manually copy the file Android/data/com.webintenerife.qbook/files/qbookBackup/qbook.sql to your Dropbox folder or in another secure place.')
                                console.log(error.message);
                            });
                        }
                    }
                });
            }, onErrorCreateFile);
        }, onErrorCreateDir);
    };
    cordova.plugins.sqlitePorter.exportDbToSql(db, {
        successFn: successFn
    });
}

filesUpload() function is quite simple. We pass it following parameters:


  • path: the file path we want
  • content: the data to write in the file (this is passed to the callback function successFn() by cordova.plugins.sqlitePorter.exportDbToSql() function)
  • mode: selects what to do if the file already exists (possible values are add, overwrite and update: http://dropbox.github.io/dropbox-sdk-js/global.html#FilesWriteMode).
  • mute: a boolean value to set if we want to receive a notification from DBX when the file has been changed (AFAIK, at July 21, 2017 this doesn't work)


After the upload is complete we alert user the job is done.


Downloading files


And now go to see the importBackup() function:

function importBackup(fromDropbox) {
    if (!fromDropbox) {
        var pathToFile = cordova.file.dataDirectory + '/dropboxTestBackup/dropboxTestBackup.sql';
        window.resolveLocalFileSystemURL(pathToFile, function (fileEntry) {
            fileEntry.file(function (file) {
                var reader = new FileReader();
                reader.onloadend = function (e) {
                    var successFn = function () {
                        alert('Database restored successfully!');
                        loadCountries();
                        loadUsers();
                    };
                    cordova.plugins.sqlitePorter.importSqlToDb(db, this.result, {
                        successFn: successFn
                    });
                };
                reader.readAsText(file);
            }, onErrorLoadFile);
        }, onErrorLoadFs);
    } else {
        if (dbxt == null) {
            dbx.authenticateWithCordova(function (accessToken) {
                dbxt = new Dropbox({accessToken: accessToken});
                dbxt.filesDownload({
                    path: "/dropboxTestBackup.sql"
                }).then(function (response) {
                    var reader = new FileReader();
                    reader.readAsText(response.fileBlob);
                    reader.onloadend = function () {
                        var successFn = function () {
                            alert('Database restored successfully!');
                            loadCountries();
                            loadUsers();
                        };
                        var errorFn = function (e) {
                            console.log('error importing db');
                            console.log(e.message);
                        };
                        cordova.plugins.sqlitePorter.importSqlToDb(db, this.result, {
                            successFn: successFn,
                            errorFn: errorFn
                        });
                    };
                }).catch(function (error) {
                    alert('Error reading file from your Dropbox!');
                    console.error(error.message);
                });
            }, function () {
                console.log("Failed to login to your Dropbox.");
            });
        } else {
            dbxt.filesDownload({
                path: "/dropboxTestBackup.sql"
            }).then(function (response) {
                var reader = new FileReader();
                reader.readAsText(response.fileBlob);
                reader.onloadend = function () {
                    var successFn = function () {
                        alert('Database restored successfully!');
                        loadCountries();
                        loadUsers();
                    };
                    var errorFn = function (e) {
                        console.log('error importing db');
                        console.log(e.message);
                    };
                    cordova.plugins.sqlitePorter.importSqlToDb(db, this.result, {
                        successFn: successFn,
                        errorFn: errorFn
                    });
                };
            }).catch(function (error) {
                alert('Error reading file from your Dropbox!');
                console.error(error.message);
            });
        }
    }
}

It's a bit longer but it's as simple as the previous one. Once we have established a valid connection to DBX, we can download the file we want and pass it to our reader to read it and, if everything is good, to pass it to SQLite Porter plugin to import the content of the file in our database. All done!


Conclusions


The app is raw, I know, but I hope it can illustrate clearly enough how you can connect to DBX from within your Android Cordova app and how to save/restore a database. Probably you might want to add a spinner or something else to notify the user the app is running while the database is being exported or imported, but this was not crucial for our goal here so I didn't cover that.

What's I really don't like is the way inappbrowser plugin remain stuck on a blank page while it is connecting to DBX, but I still don't have found a valid workaround. I thought it could be possible open a custom remote page on a server and use Php or ASP to do the dirty job before to come back to the app, but this would insert a third element (an external server) I would like to avoid. Have you some idea? Let me know!


Read Part 1 


Read Part 2   



0
Comment
Author:Marco Gasi
  • 3
3 Comments
 
LVL 31

Author Comment

by:Marco Gasi
Hi Mr. Wolfe. Sorry for the delay but I missed your message. I saw it today when I was going to write you to ask if there was news LOL.

To replay to your question, yes, I would like at the bottom of each article something like
This article has been published in my blog www.codingfix.com/...
Is this possible?
Thank you
0
 
LVL 31

Author Comment

by:Marco Gasi
Okay, but whay I meant is to put a link to myu blog to attract visitors. Is this possible?
0
 
LVL 31

Author Comment

by:Marco Gasi
Ok. Thank you so much, Mr. Wolff.
0

Featured Post

Prepare for your VMware VCP6-DCV exam.

Josh Coen and Jason Langer have prepared the latest edition of VCP study guide. Both authors have been working in the IT field for more than a decade, and both hold VMware certifications. This 163-page guide covers all 10 of the exam blueprint sections.

Join & Write a Comment

In this video, Percona Solution Engineer Dimitri Vanoverbeke discusses why you want to use at least three nodes in a database cluster. To discuss how Percona Consulting can help with your design and architecture needs for your database and infras…
In this video, Percona Solutions Engineer Barrett Chambers discusses some of the basic syntax differences between MySQL and MongoDB. To learn more check out our webinar on MongoDB administration for MySQL DBA: https://www.percona.com/resources/we…
Suggested Courses

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month