Android & Kotlin

Upload Manager – Display Notification Progress Bar while Uploading

Pinterest LinkedIn Tumblr

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

upload manager in Android

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" />

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.

Download Zip

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

?

10 Comments

  1. hi thank you for your tutorial.
    can you show me how to get API JSON response from this tutorial

  2. Taha Elhosseny Reply

    dear, first very thank you for your great effort in developing this tutorial, second am ask you if there is any tutorial about download files from the server and pause/resume the download using the same scenario you followed here

  3. Hi, this example not working when app killed while uploading multiple files..

  4. Hi, it’s a nice example. I’ve integrated it and it’s work without any error. But my image is not uploading anywhere..! not on the server and not on the device, it’s only showing ‘File successfully uploaded’ in Result

    I’ve set server path in base_url and package path in file_provider_path.xml
    where I’m wrong?

  5. I getting this exception

    Failed resolution of: Landroid/app/NotificationChannel;

    • John Joe

      Managed to fix it by moving this line code into if statement.
      NotificationChannel mChannel = null;

  6. Very nice tutorial. Do you have example on retrofit2(coroutine) ?

Write A Comment