Adding Dropbox to an Android App
This article was peer reviewed by Wern Ancheta. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
Document and file storage has been essential to computing devices since their inception. Traditionally storage was local, but the past years have seen an increasing move to storing in the cloud. Cloud storage has it’s problems, but offers near infinite capacity and low cost.
In this tutorial I will explain how to store files from an Android app in Dropbox by using the Dropbox Core SDK for Java 6+ in Android. According to the SDK description this Core SDK is a Java library to access Dropbox’s HTTP-based Core API v2. This SDK also supports the older Core API v1, but that support will be removed at some point.
You can find the final code for this project on GitHub.
To connect with Dropbox, you need to have a dropbox account and use the Dropbox API. Create a new app in the App Console.
After the new app is created it will be set to development status. With this status the app can only be used by test users and if you Enable Additional Users these can be up to 100. When the app is ready you have to apply for production status.
The app console contains app info and you will need the App Key for the Android project.
Add Permissions
Create a project in Android Studio (With a Login Activity) and in AndroidManifest.xml add the following for internet and storage permissions. You can remove the auto-generated permissions added in AndroidManifest.xml by the LoginActivity
. They are not required in this project.
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Add Dependencies
In build.gradle(Module: app) add the following:
dependencies {
//...
compile 'com.dropbox.core:dropbox-core-sdk:2.0.1'
compile 'com.squareup.picasso:picasso:2.5.2'
}
Note: The first dependency is for the Dropbox Core API. The Picasso library is not mandatory, but is useful for loading images.
Add the Dropbox Activity
Open AndroidManifest.xml, and in the <application></application>
section, add the AuthActivity
.
<application>
...
<!-- Dropbox AuthActivity -->
<activity
android:name="com.dropbox.core.android.AuthActivity"
android:configChanges="orientation|keyboard"
android:launchMode="singleTask">
<intent-filter>
<!-- Insert your app key after “db- ...” -->
<data android:scheme="db-APP_KEY" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
Duplicate File Error
While testing the app I encountered an error that after reading this Stackoverflow answer was due to a conflict during build time of multiple License.txt or Notice.txt files. To avoid this conflict, in build.gradle(Module: app), in the android{…} section, exclude these files.
android {
...
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
}
}
App key
Add your app key to strings.xml:
<!-- Change this to your app key -->
<string name="APP_KEY">APP_KEY_HERE</string>
Now the Android project is configured to use the Dropbox API, add a new Activity to the project, with the File->New->Activity->Basic Activity menu item. Call it MainActivity.java.
Project Description
The sample app will consist of logging in to the user’s dropbox account, getting detailed information and performing a image upload to the app’s Dropbox folder. The project will have two activities, LoginActivity
and MainActivity
.
Login with Dropbox
The LoginActivity
‘s task is straightforward. On a button click it performs a login task to generate an access token for the Dropbox app identified by the APP_KEY
in the string resources.
The startOAuth2Authentication()
method will open the Dropbox AuthActivity
, this is the activity added to the Manifest file. In the AuthActivity
the user must confirm their Dropbox account. After the user has confirmed their Dropbox account, redirect them to the LoginActivity
.
The startOAuth2Authentication()
method doesn’t return any access Token, it only opens AuthActivity
which is internally used for authentication. Now the user is authenticated, they need an access token.
AuthActivity
In LoginActivity
replace all the auto-generated code, except the project package name, with the following:
public class LoginActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
Button SignInButton = (Button) findViewById(R.id.sign_in_button);
SignInButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Auth.startOAuth2Authentication(getApplicationContext(), getString(R.string.APP_KEY));
}
});
}
@Override
protected void onResume() {
super.onResume();
getAccessToken();
}
public void getAccessToken() {
String accessToken = Auth.getOAuth2Token(); //generate Access Token
if (accessToken != null) {
//Store accessToken in SharedPreferences
SharedPreferences prefs = getSharedPreferences("com.example.valdio.dropboxintegration", Context.MODE_PRIVATE);
prefs.edit().putString("access-token", accessToken).apply();
//Proceed to MainActivity
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
}
}
}
For the layout of this activity, update activity_login.xml to:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.valdio.dropboxintegration.LoginActivity">
<!-- Update package name -->
<Button
android:id="@+id/sign_in_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sign_in"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
Add the following to strings.xml for the sign in button text:
...
<string name="sign_in">Sign in</string>
The LoginActivity
‘s onResume()
lifecycle method requests the token by calling the getOAuth2Token()
method. The token is stored in the app’s SharedPreferences
data for later use, then MainActivity
opened.
The onResume()
lifecycle method was called before when the app opened for the first time. Why didn’t you get a token then?
This is because the user was not authenticated before. When redirecting from the AuthActivity
with successful authentication, the onResume()
method will be called once again and generate the access token.
Dropbox Client Is Now Logged In
In MainActivity
declare the following variables:
public class MainActivity extends AppCompatActivity {
private static final int IMAGE_REQUEST_CODE = 101;
private String ACCESS_TOKEN;
...
}
In MainActivity
‘s content_main.xml layout file replace the content with the following:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="valdioveliu.valdio.com.dropboxintegration.MainActivity"
tools:showIn="@layout/activity_main">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="@+id/accountData">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/imageView"
android:layout_marginLeft="15dp"
android:layout_marginTop="50dp"
android:text="Name"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/name_textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:gravity="center_horizontal"
android:text=""
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/textView3"
android:layout_marginLeft="15dp"
android:layout_marginTop="58dp"
android:text="Email"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/email_textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/textView"
android:gravity="center_horizontal"
android:text=""
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
</RelativeLayout>
Account Information
Getting user account information or accessing Dropbox in any way is a network request, and with Android, network requests are handled asynchronously. These network requests all require a Dropbox client.
Create DropboxClient.java and add the following code:
public class DropboxClient {
public static DbxClientV2 getClient(String ACCESS_TOKEN) {
// Create Dropbox client
DbxRequestConfig config = new DbxRequestConfig("dropbox/sample-app", "en_US");
DbxClientV2 client = new DbxClientV2(config, ACCESS_TOKEN);
return client;
}
}
The ACCESS_TOKEN
string in the getClient()
method is the token received during the user’s login.
The next task is getting the user’s account details. The UserAccountTask
class represents the accounts details request. The constructor of this asynchronous class has an interface parameter of TaskDelegate
used to return the accounts info back to the activity in which the task was executed.
Create UserAccountTask.java and add the following code:
public class UserAccountTask extends AsyncTask<Void, Void, FullAccount> {
private DbxClientV2 dbxClient;
private TaskDelegate delegate;
private Exception error;
public interface TaskDelegate {
void onAccountReceived(FullAccount account);
void onError(Exception error);
}
UserAccountTask(DbxClientV2 dbxClient, TaskDelegate delegate){
this.dbxClient =dbxClient;
this.delegate = delegate;
}
@Override
protected FullAccount doInBackground(Void... params) {
try {
//get the users FullAccount
return dbxClient.users().getCurrentAccount();
} catch (DbxException e) {
e.printStackTrace();
error = e;
}
return null;
}
@Override
protected void onPostExecute(FullAccount account) {
super.onPostExecute(account);
if (account != null && error == null){
//User Account received successfully
delegate.onAccountReceived(account);
}
else {
// Something went wrong
delegate.onError(error);
}
}
}
The getUserAccount
method is an example of how the UserAcountTask
class is executed in the MainActivity
class.
Add the following method to the MainActivity
class, you will use it later:
protected void getUserAccount() {
if (ACCESS_TOKEN == null)return;
new UserAccountTask(DropboxClient.getClient(ACCESS_TOKEN), new UserAccountTask.TaskDelegate() {
@Override
public void onAccountReceived(FullAccount account) {
//Print account's info
Log.d("User", account.getEmail());
Log.d("User", account.getName().getDisplayName());
Log.d("User", account.getAccountType().name());
updateUI(account);
}
@Override
public void onError(Exception error) {
Log.d("User", "Error receiving account details.");
}
}).execute();
}
To display the user’s account details on the UI add the following function to MainActivity class:
private void updateUI(FullAccount account) {
ImageView profile = (ImageView) findViewById(R.id.imageView);
TextView name = (TextView) findViewById(R.id.name_textView);
TextView email = (TextView) findViewById(R.id.email_textView);
name.setText(account.getName().getDisplayName());
email.setText(account.getEmail());
Picasso.with(this)
.load(account.getProfilePhotoUrl())
.resize(200, 200)
.into(profile);
}
Uploading to Dropbox
Upload tasks happen with the help of InputStream
. A specified file is converted to InputStream
and with the help of the Core SDK, the stream uploaded to the apps folder in Dropbox.
Create UploadTask.java and add the following code to it:
public class UploadTask extends AsyncTask {
private DbxClientV2 dbxClient;
private File file;
private Context context;
UploadTask(DbxClientV2 dbxClient, File file, Context context) {
this.dbxClient = dbxClient;
this.file = file;
this.context = context;
}
@Override
protected Object doInBackground(Object[] params) {
try {
// Upload to Dropbox
InputStream inputStream = new FileInputStream(file);
dbxClient.files().uploadBuilder("/" + file.getName()) //Path in the user's Dropbox to save the file.
.withMode(WriteMode.OVERWRITE) //always overwrite existing file
.uploadAndFinish(inputStream);
Log.d("Upload Status", "Success");
} catch (DbxException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
Toast.makeText(context, "Image uploaded successfully", Toast.LENGTH_SHORT).show();
}
}
Note: Android Studio will prompt you about conflicting imports, you need to import java.io.File
for the File
variable.
To perform an upload you first need something to upload, e.g. an image. You can get an image by starting another activity for receiving a result.
Add the following method to MainActivity
:
private void upload() {
if (ACCESS_TOKEN == null)return;
//Select image to upload
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
startActivityForResult(Intent.createChooser(intent,
"Upload to Dropbox"), IMAGE_REQUEST_CODE);
}
When calling startActivityForResult()
, you need to implement the onActivityResult()
method to handle results on MainActivity
. When a valid image URI is received, construct a file and execute UploadTask
.
Add the following method to MainActivity
:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK || data == null) return;
// Check which request we're responding to
if (requestCode == IMAGE_REQUEST_CODE) {
// Make sure the request was successful
if (resultCode == RESULT_OK) {
//Image URI received
File file = new File(URI_to_Path.getPath(getApplication(), data.getData()));
if (file != null) {
//Initialize UploadTask
new UploadTask(DropboxClient.getClient(ACCESS_TOKEN), file, getApplicationContext()).execute();
}
}
}
}
In the onActivityResult()
method is the parameter, URI_to_Path.getPath(getApplication(), data.getData())
. The URI_to_Path
java class is a helper class to convert file URIs into absolute paths. It’s based on this StackOverflow answer.
This is a large class and not directly related to this tutorial, so create URI_to_Path.java and add the code here to it. Make sure to change the package name to yours.
Finally comes the onCreate()
method. Add the following code to MainActivity
, replacing anything that already exists:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (!tokenExists()) {
//No token
//Back to LoginActivity
Intent intent = new Intent(MainActivity.this, LoginActivity.class);
startActivity(intent);
}
ACCESS_TOKEN = retrieveAccessToken();
getUserAccount();
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
upload();
}
});
}
Every time the app opens you need to check if there is an access token. If the token exists, retrieve it from SharedPreferences
. Add these methods to MainActivity
to help with that:
private boolean tokenExists() {
SharedPreferences prefs = getSharedPreferences("com.example.valdio.dropboxintegration", Context.MODE_PRIVATE);
String accessToken = prefs.getString("access-token", null);
return accessToken != null;
}
private String retrieveAccessToken() {
//check if ACCESS_TOKEN is stored on previous app launches
SharedPreferences prefs = getSharedPreferences("com.example.valdio.dropboxintegration", Context.MODE_PRIVATE);
String accessToken = prefs.getString("access-token", null);
if (accessToken == null) {
Log.d("AccessToken Status", "No token found");
return null;
} else {
//accessToken already exists
Log.d("AccessToken Status", "Token exists");
return accessToken;
}
}
Conclusion
That’s all for the basics of Dropbox integration with the v2 API in Android. If you want to take a deeper look at this topic, read the Dropbox for Java documentation, the SDK source and examples.