Android file upload doesn't have to be hard
If you’re new to app development in Android, file upload might appear like an uphill task. But in reality it’s just two things -- requesting permission to access the sdcard/camera, and then making API to upload the files. Unless your requirements explicitly ask you to manipulate the files or optimize them in some way, things are fairly straight forward. In this post, we’ll talk about some of the common challenges and some of the best practices to rectify those challenges while creating a file uploader in Android.
Compress Media Files Before Upload
The common requirement for a file upload menu in Android is for uploading images and other media files. When the file to be uploaded is big, you will need to find ways to upload the file without intruding the user’s experience. There are multiple ways that you can do this. For instance, you can consider moving the file upload process to another thread so that the file upload happens in the background. Alternatively, you can transform the file and compress it so that the file is bandwidth friendly. All major apps on Android perform certain file operations to reduce the size.
Compression and file transformations work great with media files because they are easily compressible. In case of images, you can use the native Bitmap classes to resize and reduce the image size. Here is an example of code that reduces the image size to 200KB with minimum loss of visual data.
public File saveBitmapToFile(File file){
try {
// BitmapFactory options to downsize the image
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
o.inSampleSize = 6;
// factor of downsizing the image
FileInputStream inputStream = new FileInputStream(file);
//Bitmap selectedBitmap = null;
BitmapFactory.decodeStream(inputStream, null, o);
inputStream.close();
// The new size we want to scale to
final int REQUIRED_SIZE=75;
// Find the correct scale value. It should be the power of 2.
int scale = 1;
while(o.outWidth / scale / 2 >= REQUIRED_SIZE &&
o.outHeight / scale / 2 >= REQUIRED_SIZE) {
scale *= 2;
}
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
inputStream = new FileInputStream(file);
Bitmap selectedBitmap = BitmapFactory.decodeStream(inputStream, null, o2);
inputStream.close();
//Create a new image file and then return it.
file.createNewFile();
FileOutputStream outputStream = new FileOutputStream(file);
selectedBitmap.compress(Bitmap.CompressFormat.JPEG, 100 , outputStream);
return file;
} catch (Exception e) {
return null;
}
}
For compressing videos, it might be a better idea to use an open-source video transcoder library like Android Transcoder or rely on a cloud based video transcoding service to reduce the size.
Keep Things in the Background using Services
User experience should always be given the highest priority. Animating a spinning wheel while the file is being uploaded works. But wouldn’t it be better if we could move the upload process to the background. To make that happen, you can use the IntentService class. IntentService provides a background service for running time-consuming operations. Thus, the user interface stays responsive and the service keeps the view informed of the background service’s status.
Remember to declare your services inside the manifest as follows:
<service
android:name=".RSSPullService"
android:exported="false"/>
The android:exported = “false” prevents the app from sharing the service with other applications.
However, the service is just a component that runs in the background and it resides in the main thread. If the background operation is blocking, you can decide to spin up a new thread of use something like AsyncTask or HandlerThread instead of the traditional Thread() class.
Move Processor Intensive Tasks to the Server
Compressing images and videos are not usually processor intensive. It’s also okay to do basic file validations because you don’t want someone uploading malicious files into your server. You can also set a isByteLesserThan() validation to check whether the file is of appropriate size. But that should be it. When you’re expecting bigger files, client-side file manipulations can negatively impact performance.
You should then think about moving the processor intensive tasks to the server. This is because the server can scale, the client can not. Apart from that, you should be easily able to find all sorts of libraries to do most of the common operators for the server stack on GitHub. For instance, if your application needs to implement a multimedia file optimizer, you can use the FFMPEG library directly or a FFMPEG module that works with your stack. Alternatively, you can use a multimedia cloud solution like Cloudinary that can perform the optimization on the fly. You can then push the URL of the media file into your server.
Use A Library to Make API Calls
If you’ve made HTTP calls before using Java, you might be familiar with java.net.HttpUrlConnection for making API calls and the following syntax. The code below is for setting up a connection and the actual file upload hasn’t been done yet.
// Open a HTTP connection
conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true); // Allow Inputs
conn.setDoOutput(true); // Allow Outputs
conn.setUseCaches(false); // Don't use cache
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("ENCTYPE", "multipart/form-data");
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
conn.setRequestProperty("uploaded_file", fileName);
Worry not, you are not short of alternatives! There are a number of libraries that you can use to make HTTP calls. OKHTTP is one of the popular Java HTTP client, but there are others like Retrofit and Volley that might suit your use case. Retrofit and volley work well when you’re communicating with a web service whereas OKHTTP is good for all the everyday HTTP operations.
Here’s a small sample that implements file upload using OKHTTP:
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.parse("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
File file = new File("README.md");
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
}
You can use a MultipartBody.Builder to build sophisticated request bodies compatible with HTML file upload forms.
Handle Errors and Exceptions Gracefully
Neither developers nor end users love errors. But you need to have error handlers in place to gracefully report them back to the user and prove that your application is efficient to handle real life scenarios. For instance, if the code fails to retrieve a response from the server, an error object should be returned as feedback to the user. If more details about the error is known, you can collect the additional data along with the context and provide user options to finish what they were doing. There are many ways that you can throw an error/exception. The most popular one is using Try/Catch. The code below demonstrates the try/catch error.
public void ExampleMethod() throws IOException
{
if (conditionForError == true)
{
throw new IOException("Custom exception msg");
}
try
{
// routine 1
// routine 2
// routine 3
}
catch (ExceptionRoutine1 e)
{
// That's good for diagnosing the problem
System.out.println("Thrown exception: " + e.getMessage());
}
catch (ExceptionRoutine2 e)
{
// custom exception
throw new Exception("Error in Routine 2", e);
}
catch (Exception e)
{
// General error can be anything*
// captured by the java class Exception
// print in the console detailed technical info
e.printStackTrace();
}
}
If you are curious to know more about errors and exceptions, here is a good guide to get you started.
Get the Permissions Right
Permissions help the user protect the privacy and your applications need to specifically request for permission when trying to access sensitive data. This is a very trivial detail that a beginner developer might fail to notice, but it could prevent you uploading the files as expected. The purpose of the permission is to prevent an unauthorized app from accessing certain privileges. As you might already know, there are three permission levels in Android that affects third-party app behavior: normal, signature and dangerous permissions.
For a simple file upload app, you will the need to following permissions:
- Internet
- Access Network State
- Read External Storage
- Camera
Permissions 3 and 4 fall into the dangerous category and the user will need to explicitly grant access to those permissions if you need to use them. So, go ahead and add them into your Android Manifest file.
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
For dangerous permissions, this is not enough. The application needs to check whether it has the permission to access a resource and this needs to be done every time the app tries to access that resource. This is imminent because the user can revoke the permission any time. Here’s how you check the permission using ContextCompat.checkSelfPermission().
if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
}
If permission is not granted, you should show a pop up that lets the user know why that permission is required asynchronously. Then ask for the permission using ActivityCompat.requestPermissions(). You can read more about permissions over at Android documentation on permissions.
Android File Upload Isn’t Hard…
Uploading Files in Android is similar to that of any other client platform. But, there are things that needs to be taken care of and errors that you need to anticipate beforehand. In this article, we’ve talked about some of the best practices such as:
- Compressing the files to make them bandwidth friendly
- Improving responsiveness by using background services
- Transferring intense file manipulation logic to the server
- Throw exceptions to prevent app from crashing
- Don’t forget to add the right permissions
We hope that you’ve enjoyed reading the post. Share your thoughts in the comments.
Limor Wainstein is a technical writer and editor at Agile SEO, a boutique digital marketing agency focused on technology and SaaS markets. She has over 10 years' experience writing technical articles and documentation for various audiences, including technical on-site content, software documentation, and dev guides. She specializes in big data analytics, computer/network security, middleware, software development and APIs.LinkedIn: https://www.linkedin.com/in/limormaayan/Twitter: @LimiMaayan