In this post, we will create a sample app Upload Manager such as Android Download Manager. In this android app tutorial we build a complete solution for file uploading with head up a notification, No matter your app is background or foreground because that time we are using Service Enqueue for file uploading, In case of failure, the user can retry or cancel file upload without open the application. I will try to make a complete solution just like a phone download manager.

Prerequisite

In this article, We are using Retrofit, JobIntentService and Broadcast Receiver, Notification Service. You have a deep knowledge of each one. I have written articles for all major concepts. Read for capture file from Camera & Gallery using FileProvider. For Working with JobIntentService follow this article.

Data Flow of this Upload Manager

Step for implementation Upload Manager

Create a new project with min SDK 21.

Add lib dependency in app/build.gradle

Create a Retrofit instance for calling file upload service



Forgetting file upload progress let’s creates a CountingRequestBody

Now create a subclass of JobIntentService

Furthermore, Create a BroadcastReceiver for listening file upload progress

Create another BroadcastReceiver with Retry and Cancel action button. While any error occurred during file upload user can retry for file upload

In MainActivity, We write code for getting the file from camera & gallery using FileProvider for upload file to the server

Finally, Enqueue the job to JobIntentService.

After following above step we will prepare Upload Manager (Demo App)

1. Create Project

Let move to android studio and create a new project with named FileUploadService. Choose min SDK version 21 and select EmptyActivity template.

Let’s go to res =>value => open string.xml file add some string constant that we are using in this project.

<resources> <string name="app_name">Upload Manager</string> <string name="noti_channel_default">Default Channel</string> <string name="btn_retry_not">Retry</string> <string name="btn_cancel_not">Cancel</string> <string name="file_upload_successful">File has been uploaded successfully</string> <string name="uploading">Uploading</string> <string name="in_progress">in progress</string> <string name="message_failed">File has been not uploaded</string> <string name="message_upload_success">Uploading Success</string> <string name="error_upload_failed">Uploading failed</string> <string name="message_upload_failed">File is not uploaded. Please TRY AGAIN</string> </resources>

2. Add Dependency

In this android app tutorial, we are using for libraries. These are listed below.

Retrofit – I think no need to much introduction about that. You guys were already aware that one. Retrofit mostly used for calling Remote API

RxAndroid and RxJava – RxJava and RxJava are most common libraries for these days. They provide react feature in android app development

Dexter – Manage run time permission in android

Glide – is image loading libraries that use to show image on the ImageView in android.

Let’s open the app build.gradle file and add some dependencies for using necessary libraries

dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.retrofit2:converter-gson:2.4.0' // reactive implementation "io.reactivex.rxjava2:rxjava:2.1.10" implementation "io.reactivex.rxjava2:rxandroid:2.0.2" implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0' /** * dependency to request the runtime permissions. */ implementation 'com.karumi:dexter:5.0.0' implementation 'com.github.bumptech.glide:glide:4.8.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0' }

3. Let’s Prepare Retrofit instance

Create a new Retrofit Interface and define a method named on file upload. We will use this one for file upload.

3.1 – Interface RestApiService

package com.wave.fileuploadservice.service; import io.reactivex.Single; import okhttp3.MultipartBody; import okhttp3.RequestBody; import okhttp3.ResponseBody; import retrofit2.http.Multipart; import retrofit2.http.POST; import retrofit2.http.Part; /** * Created on : Feb 25, 2019 * Author : AndroidWave */ public interface RestApiService { @Multipart @POST("fileUpload.php") Single<ResponseBody> onFileUpload(@Part("email") RequestBody mEmail, @Part MultipartBody.Part file); }

3.2 – Create Retrofit Service class using RestApiService interface

On above I have created RestApiService interface. Let’s create a service class that return Retrofit instance. We have to add converter factory as well such as RxJava2CallAdapterFactory and GsonConverterFactory

package com.wave.fileuploadservice.service; import retrofit2.Retrofit; import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; import static com.wave.fileuploadservice.BuildConfig.BASE_URL; /** * Created on : Feb 25, 2019 */ public class RetrofitInstance { private static Retrofit retrofit = null; public static RestApiService getApiService() { if (retrofit == null) { retrofit = new Retrofit .Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); } return retrofit.create(RestApiService.class); } }

4. Creates a countable RequestBody for listening file progress

package com.wave.fileuploadservice.service; import android.support.annotation.NonNull; import java.io.IOException; import okhttp3.MediaType; import okhttp3.RequestBody; import okio.Buffer; import okio.BufferedSink; import okio.ForwardingSink; import okio.Okio; import okio.Sink; public class CountingRequestBody extends RequestBody { private final RequestBody delegate; private final Listener listener; public CountingRequestBody(RequestBody delegate, Listener listener) { this.delegate = delegate; this.listener = listener; } @Override public MediaType contentType() { return delegate.contentType(); } @Override public long contentLength() { try { return delegate.contentLength(); } catch (IOException e) { e.printStackTrace(); } return -1; } @Override public void writeTo(@NonNull BufferedSink sink) throws IOException { CountingSink countingSink = new CountingSink(sink); BufferedSink bufferedSink = Okio.buffer(countingSink); delegate.writeTo(bufferedSink); bufferedSink.flush(); } final class CountingSink extends ForwardingSink { private long bytesWritten = 0; CountingSink(Sink delegate) { super(delegate); } @Override public void write(@NonNull Buffer source, long byteCount) throws IOException { super.write(source, byteCount); bytesWritten += byteCount; listener.onRequestProgress(bytesWritten, contentLength()); } } public interface Listener { void onRequestProgress(long bytesWritten, long contentLength); } }

5. Now creates a FileUploadService

Create a new subclass of JobIntentService in src folder named is FileUploadService. In this service we are majorly doing three things. let’s check above diagram. We are using two BroadcastReceiver one for listing file upload progress. Second for retry and cancel action button.

package com.wave.fileuploadservice; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.support.annotation.NonNull; import android.support.v4.app.JobIntentService; import android.support.v4.app.NotificationCompat; import android.util.Log; import com.wave.fileuploadservice.receiver.FileProgressReceiver; import com.wave.fileuploadservice.receiver.RetryJobReceiver; import com.wave.fileuploadservice.service.CountingRequestBody; import com.wave.fileuploadservice.service.RestApiService; import com.wave.fileuploadservice.service.RetrofitInstance; import com.wave.fileuploadservice.utils.MIMEType; import io.reactivex.BackpressureStrategy; import io.reactivex.Flowable; import io.reactivex.FlowableEmitter; import io.reactivex.FlowableOnSubscribe; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Action; import io.reactivex.functions.Consumer; import io.reactivex.schedulers.Schedulers; import java.io.File; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.RequestBody; import static com.wave.fileuploadservice.receiver.RetryJobReceiver.ACTION_CLEAR; import static com.wave.fileuploadservice.receiver.RetryJobReceiver.ACTION_RETRY; public class FileUploadService extends JobIntentService { private static final String TAG = "FileUploadService"; RestApiService apiService; Disposable mDisposable; public static final int NOTIFICATION_ID = 1; public static final int NOTIFICATION_RETRY_ID = 2; /** * Unique job ID for this service. */ private static final int JOB_ID = 102; String mFilePath; NotificationHelper mNotificationHelper; public static void enqueueWork(Context context, Intent intent) { enqueueWork(context, FileUploadService.class, JOB_ID, intent); } @Override public void onCreate() { super.onCreate(); mNotificationHelper = new NotificationHelper(this); } @Override protected void onHandleWork(@NonNull Intent intent) { Log.d(TAG, "onHandleWork: "); /** * Download/Upload of file * The system or framework is already holding a wake lock for us at this point */ // get file file here mFilePath = intent.getStringExtra("mFilePath"); if (mFilePath == null) { Log.e(TAG, "onHandleWork: Invalid file URI"); return; } apiService = RetrofitInstance.getApiService(); Flowable<Double> fileObservable = Flowable.create(new FlowableOnSubscribe<Double>() { @Override public void subscribe(FlowableEmitter<Double> emitter) throws Exception { apiService.onFileUpload( FileUploadService.this.createRequestBodyFromText("info@androidwave.com"), FileUploadService.this.createMultipartBody(mFilePath, emitter)).blockingGet(); emitter.onComplete(); } }, BackpressureStrategy.LATEST); mDisposable = fileObservable.subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<Double>() { @Override public void accept(Double progress) throws Exception { // call onProgress() FileUploadService.this.onProgress(progress); } }, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { // call onErrors() if error occurred during file upload FileUploadService.this.onErrors(throwable); } }, new Action() { @Override public void run() throws Exception { // call onSuccess() while file upload successful FileUploadService.this.onSuccess(); } }); } private void onErrors(Throwable throwable) { /** * Error occurred in file uploading */ Intent successIntent = new Intent("com.wave.ACTION_CLEAR_NOTIFICATION"); successIntent.putExtra("notificationId", NOTIFICATION_ID); sendBroadcast(successIntent); PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, new Intent(this, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); /** * Add retry action button in notification */ Intent retryIntent = new Intent(this, RetryJobReceiver.class); retryIntent.putExtra("notificationId", NOTIFICATION_RETRY_ID); retryIntent.putExtra("mFilePath", mFilePath); retryIntent.setAction(ACTION_RETRY); /** * Add clear action button in notification */ Intent clearIntent = new Intent(this, RetryJobReceiver.class); clearIntent.putExtra("notificationId", NOTIFICATION_RETRY_ID); clearIntent.putExtra("mFilePath", mFilePath); clearIntent.setAction(ACTION_CLEAR); PendingIntent retryPendingIntent = PendingIntent.getBroadcast(this, 0, retryIntent, 0); PendingIntent clearPendingIntent = PendingIntent.getBroadcast(this, 0, clearIntent, 0); NotificationCompat.Builder mBuilder = mNotificationHelper.getNotification(getString(R.string.error_upload_failed), getString(R.string.message_upload_failed), resultPendingIntent); // attached Retry action in notification mBuilder.addAction(android.R.drawable.ic_menu_revert, getString(R.string.btn_retry_not), retryPendingIntent); // attached Cancel action in notification mBuilder.addAction(android.R.drawable.ic_menu_revert, getString(R.string.btn_cancel_not), clearPendingIntent); // Notify notification mNotificationHelper.notify(NOTIFICATION_RETRY_ID, mBuilder); } /** * Send Broadcast to FileProgressReceiver with progress * * @param progress file uploading progress */ private void onProgress(Double progress) { Intent progressIntent = new Intent(this, FileProgressReceiver.class); progressIntent.setAction("com.wave.ACTION_PROGRESS_NOTIFICATION"); progressIntent.putExtra("notificationId", NOTIFICATION_ID); progressIntent.putExtra("progress", (int) (100 * progress)); sendBroadcast(progressIntent); } /** * Send Broadcast to FileProgressReceiver while file upload successful */ private void onSuccess() { Intent successIntent = new Intent(this, FileProgressReceiver.class); successIntent.setAction("com.wave.ACTION_UPLOADED"); successIntent.putExtra("notificationId", NOTIFICATION_ID); successIntent.putExtra("progress", 100); sendBroadcast(successIntent); } private RequestBody createRequestBodyFromFile(File file, String mimeType) { return RequestBody.create(MediaType.parse(mimeType), file); } private RequestBody createRequestBodyFromText(String mText) { return RequestBody.create(MediaType.parse("text/plain"), mText); } /** * return multi part body in format of FlowableEmitter */ private MultipartBody.Part createMultipartBody(String filePath, FlowableEmitter<Double> emitter) { File file = new File(filePath); return MultipartBody.Part.createFormData("myFile", file.getName(), createCountingRequestBody(file, MIMEType.IMAGE.value, emitter)); } private RequestBody createCountingRequestBody(File file, String mimeType, final FlowableEmitter<Double> emitter) { RequestBody requestBody = createRequestBodyFromFile(file, mimeType); return new CountingRequestBody(requestBody, new CountingRequestBody.Listener() { @Override public void onRequestProgress(long bytesWritten, long contentLength) { double progress = (1.0 * bytesWritten) / contentLength; emitter.onNext(progress); } }); } }

6. Now Create a BroadcastReceiver for listening file upload progress

Create a subclass of BroadcastReceiver named is FileProgressReceiver and override onReceive() methods. As per name suggesting. we receive file upload progress here and update the progress bar notification. Let’s define below action and manages actions accordingly.

package com.wave.fileuploadservice.receiver; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.support.v4.app.NotificationCompat; import com.wave.fileuploadservice.MainActivity; import com.wave.fileuploadservice.NotificationHelper; import com.wave.fileuploadservice.R; import java.util.Objects; public class FileProgressReceiver extends BroadcastReceiver { private static final String TAG = "FileProgressReceiver"; public static final String ACTION_CLEAR_NOTIFICATION = "com.wave.ACTION_CLEAR_NOTIFICATION"; public static final String ACTION_PROGRESS_NOTIFICATION = "com.wave.ACTION_PROGRESS_NOTIFICATION"; public static final String ACTION_UPLOADED = "com.wave.ACTION_UPLOADED"; NotificationHelper mNotificationHelper; public static final int NOTIFICATION_ID = 1; NotificationCompat.Builder notification; @Override public void onReceive(Context mContext, Intent intent) { mNotificationHelper = new NotificationHelper(mContext); // Get notification id int notificationId = intent.getIntExtra("notificationId", 1); // Receive progress int progress = intent.getIntExtra("progress", 0); switch (Objects.requireNonNull(intent.getAction())) { case ACTION_PROGRESS_NOTIFICATION: notification = mNotificationHelper.getNotification(mContext.getString(R.string.uploading), mContext.getString(R.string.in_progress), progress); mNotificationHelper.notify(NOTIFICATION_ID, notification); break; case ACTION_CLEAR_NOTIFICATION: mNotificationHelper.cancelNotification(notificationId); break; case ACTION_UPLOADED: Intent resultIntent = new Intent(mContext, MainActivity.class); PendingIntent resultPendingIntent = PendingIntent.getActivity(mContext, 0 /* Request code */, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT); notification = mNotificationHelper.getNotification(mContext.getString(R.string.message_upload_success), mContext.getString(R.string.file_upload_successful), resultPendingIntent); mNotificationHelper.notify(NOTIFICATION_ID, notification); break; default: break; } } }

7. Create RetryJobReceiver

Create a new class that extends BroadcastReceiver named is RetryJobReceiver. FileUploadService will send a broadcast to RetryJobReceiver in case of an error in during file uploading. Such as network failure, internal server error, etc.

package com.wave.fileuploadservice.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import com.wave.fileuploadservice.FileUploadService; import com.wave.fileuploadservice.NotificationHelper; import java.util.Objects; public class RetryJobReceiver extends BroadcastReceiver { public static final String ACTION_RETRY = "com.wave.ACTION_RETRY"; public static final String ACTION_CLEAR = "com.wave.ACTION_CLEAR"; NotificationHelper mNotificationHelper; @Override public void onReceive(Context context, Intent intent) { /** * Handle notification user actions */ mNotificationHelper = new NotificationHelper(context); int notificationId = intent.getIntExtra("notificationId", 0); String filePath = intent.getStringExtra("mFilePath"); switch (Objects.requireNonNull(intent.getAction())) { case ACTION_RETRY: mNotificationHelper.cancelNotification(notificationId); Intent mIntent = new Intent(context, FileUploadService.class); mIntent.putExtra("mFilePath", filePath); FileUploadService.enqueueWork(context, mIntent); break; case ACTION_CLEAR: mNotificationHelper.cancelNotification(notificationId); break; default: break; } } }

8. Declare JobIntentService, Permission, and Receiver in AndroidManifest

8.1 Permission for storage, camera and Internet permission

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" /> <uses-feature android:name="android.hardware.camera.flash" />

8.2 Declare JobIntentService and related permission

For pre oreo devices declare WAKE_LOCK permission

<!--for JobIntentService--> <uses-permission android:name="android.permission.WAKE_LOCK" />

Inside the application tag declared FileUploadService

<service android:name=".FileUploadService" android:permission="android.permission.BIND_JOB_SERVICE" />

For Receiver

<receiver android:name=".receiver.FileProgressReceiver"> <intent-filter> <action android:name="com.wave.ACTION_CLEAR_NOTIFICATION" /> <action android:name="com.wave.ACTION_PROGRESS_NOTIFICATION" /> <action android:name="com.wave.ACTION_UPLOADED" /> </intent-filter> </receiver> <receiver android:name=".receiver.RetryJobReceiver"> <intent-filter> <action android:name="com.wave.ACTION_RETRY" /> <action android:name="com.wave.ACTION_CLEAR" /> </intent-filter> </receiver>

9. Create a File Provider for getting file from storage

Create resource file inside the res=>xml=>file_provider_paths.xml and add below code

<?xml version="1.0" encoding="utf-8"?> <paths> <external-path name="my_images" path="Android/data/com.wave.fileuploadservice/files/Pictures" /> <!-- replace com.wave.fileuploadservice with your package name --> </paths>

10. Define provider inside AndroidManifest.xml

<provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_provider_paths" /> </provider>

11. The complete AndroidManifest.xml looks like

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.wave.fileuploadservice"> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" /> <uses-feature android:name="android.hardware.camera.flash" /> <!--for JobIntentService--> <uses-permission android:name="android.permission.WAKE_LOCK" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_provider_paths" /> </provider> <service android:name=".FileUploadService" android:permission="android.permission.BIND_JOB_SERVICE" /> <receiver android:name=".receiver.FileProgressReceiver"> <intent-filter> <action android:name="com.wave.ACTION_CLEAR_NOTIFICATION" /> <action android:name="com.wave.ACTION_PROGRESS_NOTIFICATION" /> <action android:name="com.wave.ACTION_UPLOADED" /> </intent-filter> </receiver> <receiver android:name=".receiver.RetryJobReceiver"> <intent-filter> <action android:name="com.wave.ACTION_RETRY" /> <action android:name="com.wave.ACTION_CLEAR" /> </intent-filter> </receiver> </application> </manifest>

12. Create a Helper class for manage notification named is NotificationHelper

package com.wave.fileuploadservice; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.ContextWrapper; import android.graphics.Color; import android.support.v4.app.NotificationCompat; import android.support.v4.content.ContextCompat; /** * Helper class to manage notification channels, and create notifications. */ public class NotificationHelper extends ContextWrapper { private NotificationManager manager; public static final String WAVE_CHANNEL = "default"; public NotificationHelper(Context mContext) { super(mContext); NotificationChannel mChannel = null; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { mChannel = new NotificationChannel(WAVE_CHANNEL, getString(R.string.noti_channel_default), NotificationManager.IMPORTANCE_DEFAULT); mChannel.setLightColor(Color.GREEN); mChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); getManager().createNotificationChannel(mChannel); } } public NotificationCompat.Builder getNotification(String title, String body, int progress) { NotificationCompat.Builder mBuilder; mBuilder = new NotificationCompat.Builder(getApplicationContext(), WAVE_CHANNEL); mBuilder.setSmallIcon(getSmallIcon()); mBuilder.setColor(ContextCompat.getColor(getApplicationContext(), R.color.colorAccent)); mBuilder.setContentTitle(title) .setContentText(body) .setOngoing(true) //.setContentIntent(resultPendingIntent) .setDefaults(NotificationCompat.DEFAULT_ALL) .setPriority(NotificationCompat.PRIORITY_HIGH); mBuilder.setVibrate(new long[] { 0L }); mBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC); mBuilder.setProgress(100, progress, false); if (progress == 100) { mBuilder.setProgress(0, 0, false); mBuilder.setContentText(body); } return mBuilder; } public NotificationCompat.Builder getNotification(String title, String body, PendingIntent resultPendingIntent) { NotificationCompat.Builder mBuilder; mBuilder = new NotificationCompat.Builder(getApplicationContext(), WAVE_CHANNEL); mBuilder.setSmallIcon(getSmallIcon()); mBuilder.setColor(ContextCompat.getColor(getApplicationContext(), R.color.colorAccent)); mBuilder.setContentTitle(title) .setContentText(body) .setContentIntent(resultPendingIntent) .setDefaults(NotificationCompat.DEFAULT_ALL) .setPriority(NotificationCompat.PRIORITY_HIGH); mBuilder.setVibrate(new long[] { 0L }); mBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC); return mBuilder; } /** * Send a notification. * * @param id The ID of the notification * @param notification The notification object */ public void notify(int id, NotificationCompat.Builder notification) { getManager().notify(id, notification.build()); } /** * Get the small icon for this app * * @return The small icon resource id */ private int getSmallIcon() { return android.R.drawable.stat_notify_sync; } /** * Get the notification manager. * <p> * Utility method as this helper works with it a lot. * * @return The system service NotificationManager */ private NotificationManager getManager() { if (manager == null) { manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); } return manager; } public void cancelNotification(int notificationId) { getManager().cancel(notificationId); } }

13. Go in the Activity and do following operation

Open activity xml and add some components such as ImageView for displaying selected and captured image, button for requesting camera and gallery intent. and One button for starting JobIntentService.

Get an image from gallery and camera

Enqueue the Job and pass file path with Intent.

14 . The Complete code of MainActivity

package com.wave.fileuploadservice; import android.Manifest; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.support.v4.content.FileProvider; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import com.karumi.dexter.Dexter; import com.karumi.dexter.MultiplePermissionsReport; import com.karumi.dexter.PermissionToken; import com.karumi.dexter.listener.DexterError; import com.karumi.dexter.listener.PermissionRequest; import com.karumi.dexter.listener.PermissionRequestErrorListener; import com.karumi.dexter.listener.multi.MultiplePermissionsListener; import java.io.File; import java.io.IOException; import java.util.Calendar; import java.util.List; public class MainActivity extends AppCompatActivity implements View.OnClickListener { static final int REQUEST_TAKE_PHOTO = 101; static final int REQUEST_GALLERY_PHOTO = 102; File mPhotoFile; ImageView ivDisplayImage; Button buttonUpload; TextView tvSelectedFilePath; ImageView ivSelectImage; TextView txvResult; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ivDisplayImage = findViewById(R.id.ivDisplayImage); buttonUpload = findViewById(R.id.buttonUpload); tvSelectedFilePath = findViewById(R.id.tvSelectedFilePath); ivSelectImage = findViewById(R.id.imageView2); txvResult = findViewById(R.id.tvResult); buttonUpload.setOnClickListener(this); ivSelectImage.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.buttonUpload: if (tvSelectedFilePath.getText().toString().isEmpty()) { Toast.makeText(this, "Select file first", Toast.LENGTH_LONG).show(); return; } Intent mIntent = new Intent(this, FileUploadService.class); mIntent.putExtra("mFilePath", tvSelectedFilePath.getText().toString()); FileUploadService.enqueueWork(this, mIntent); break; case R.id.imageView2: selectImage(); break; } } /** * Alert dialog for capture or select from galley */ private void selectImage() { final CharSequence[] items = { "Take Photo", "Choose from Library", "Cancel" }; AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setItems(items, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int item) { if (items[item].equals("Take Photo")) { MainActivity.this.requestStoragePermission(true); } else if (items[item].equals("Choose from Library")) { MainActivity.this.requestStoragePermission(false); } else if (items[item].equals("Cancel")) { dialog.dismiss(); } } }); builder.show(); } /** * Requesting multiple permissions (storage and camera) at once * This uses multiple permission model from dexter * On permanent denial opens settings dialog */ private void requestStoragePermission(final boolean isCamera) { Dexter.withActivity(this) .withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA) .withListener(new MultiplePermissionsListener() { @Override public void onPermissionsChecked(MultiplePermissionsReport report) { // check if all permissions are granted if (report.areAllPermissionsGranted()) { if (isCamera) { startCamera(); } else { chooseGallery(); } } // check for permanent denial of any permission if (report.isAnyPermissionPermanentlyDenied()) { // show alert dialog navigating to Settings chooseGallery(); } } @Override public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) { token.continuePermissionRequest(); } }) .withErrorListener(new PermissionRequestErrorListener() { @Override public void onError(DexterError error) { Toast.makeText(MainActivity.this.getApplicationContext(), "Error occurred! ", Toast.LENGTH_SHORT).show(); } }) .onSameThread() .check(); } public void startCamera() { mPhotoFile = newFile(); Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (takePictureIntent.resolveActivity(getPackageManager()) != null) { if (mPhotoFile != null) { Uri photoURI = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider", mPhotoFile); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO); } } } public void chooseGallery() { Intent pickPhoto = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); pickPhoto.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivityForResult(pickPhoto, REQUEST_GALLERY_PHOTO); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK) { if (requestCode == REQUEST_TAKE_PHOTO) { tvSelectedFilePath.setText(mPhotoFile.getAbsolutePath()); Glide.with(MainActivity.this) .load(mPhotoFile) .apply(new RequestOptions().centerCrop().circleCrop()) .into(ivDisplayImage); } else if (requestCode == REQUEST_GALLERY_PHOTO) { Uri selectedImage = data.getData(); tvSelectedFilePath.setText(getRealPathFromUri(selectedImage)); Glide.with(MainActivity.this) .load(getRealPathFromUri(selectedImage)) .apply(new RequestOptions().centerCrop().circleCrop()) .into(ivDisplayImage); } } } public File newFile() { Calendar cal = Calendar.getInstance(); long timeInMillis = cal.getTimeInMillis(); String mFileName = String.valueOf(timeInMillis) + ".jpeg"; File mFilePath = getFilePath(); try { File newFile = new File(mFilePath.getAbsolutePath(), mFileName); newFile.createNewFile(); return newFile; } catch (IOException e) { e.printStackTrace(); } return null; } public File getFilePath() { return getExternalFilesDir(Environment.DIRECTORY_PICTURES); } public String getRealPathFromUri(Uri contentUri) { Cursor cursor = null; try { String[] proj = { MediaStore.Images.Media.DATA }; cursor = getContentResolver().query(contentUri, proj, null, null, null); assert cursor != null; int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); cursor.moveToFirst(); return cursor.getString(columnIndex); } finally { if (cursor != null) { cursor.close(); } } } }

After following all the above step your app is ready to use. I hope it’s helpful for you, then help me by sharing this post with all your friends who learning android app development.

If you have any queries, feel free to ask them in the comment section below. Happy Coding