Android deprecated Tasks.call - replacement

Viewed 969

In my android app I have an option to backup the database to Google Drive. For that I am using DriveServiceHelper class, but I just noticed, in Android 11 the Task.call is deprecated.

      public Task<FileList> queryFiles() {
    return Tasks.call(mExecutor, () ->
            mDriveService.files().list().setSpaces("drive").execute());
}

From my BackupActivity then I call queryFiles from backup method:

  public void backup(View v) {
        driveServiceHelper.queryFiles()
                .addOnSuccessListener(fileList -> {
                  // another code
                })
                .addOnFailureListener(e -> showMsgSnack(getString(R.string.uploaderror)));

I did not find any solution how to deal with this to avoid complete rework of that class.

What I tried:

I tried to replace with runnable, also callable, but it doesn't work as Task is expected to be returned, not Filelist.

also I tried to use TaskCompletionSource:

public Task<FileList> queryFiles(int delay) throws IOException, ExecutionException, InterruptedException {

    new Thread(
            new Runnable() {

                @Override
                public void run() {
                    TaskCompletionSource<FileList> taskCompletionSource = new TaskCompletionSource<>();

                    FileList result = null;
                    try {
                        result = mDriveService.files().list().setSpaces("drive").execute();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    FileList finalResult = result;
                    new Handler().postDelayed(() -> taskCompletionSource.setResult(finalResult), delay);

                    return taskCompletionSource.getTask();
                }
            }).start();
    
}

but the return works not from a method of void type.

2 Answers

Ok, after hours of testing I tried this solution and this seems working for now: (using executorService, and in Handler a Looper is needed.)

public Task<FileList> queryFiles() {
    final TaskCompletionSource<FileList> tcs = new TaskCompletionSource<FileList>();
    ExecutorService service = Executors.newFixedThreadPool(1);

    service.execute(
            new Runnable() {
                @Override
                public void run() {

                    FileList result = null;
                    try {
                        result = mDriveService.files().list().setSpaces("drive").execute();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    FileList finalResult = result;
                    new Handler(Looper.getMainLooper()).postDelayed(() -> tcs.setResult(finalResult), 1000);

                }
            });

    return tcs.getTask();

    }

I meant something like this:

 public Task<FileList> queryFiles(int delay) throws IOException {
        Task<FileList> retVal;
        final FutureValue<Task<FileList>> future = new FutureValue<>();

        // now run this bit in a runnable
        /*
        TaskCompletionSource<FileList> taskCompletionSource = new TaskCompletionSource<>();
    
        FileList result = mDriveService.files().list().setSpaces("drive").execute();
        new Handler().postDelayed(() -> taskCompletionSource.setResult(result), delay);
    
        return taskCompletionSource.getTask();
        */

        new Thread(
                new Runnable() {

                    @Override
                    public void run() {
                            TaskCompletionSource<FileList> taskCompletionSource = new TaskCompletionSource<>();
    
                            FileList result = mDriveService.files().list().setSpaces("drive").execute();
                            new Handler().postDelayed(() -> taskCompletionSource.setResult(result), delay);
    
                            // and we replace the return statement with something else
                            // return taskCompletionSource.getTask();
                            future.set(taskCompletionSource.getTask());
                   }
        }).start();

       // And block (wait) for future to finish so we can return it, deadlocking the main thread...

//      return future.get();

      //FIXME do either this
//      retVal = future.get();

      // For bonus points, we'll do a timed wait instead -- OR THIS
        try {
            retVal = future.get(30, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            future.cancel(true);
            Log.d(LOG_TAG, "Exception "+e+" happened!", e);
        } catch (InterruptedException | ExecutionException e) {
            Log.d(LOG_TAG, "Exception "+e+" happened!", e);
        }

    return retVal;
}

and that should set you on some path to solving the problem.

However, if the only reason for using the Task<> is just so you can add success/fail listeners to these methods - i strongly suggest you come up with something better that actually runs on background threads instead of the thread you're calling them on.

The FutureValue class:

/**
 * Implementation of {@link Future}, allowing waiting for value to be set (from another thread).
 * Use {@link #set(Object)} to set value, {@link #get()} or {@link #get(long, TimeUnit)} to retrieve
 * value.
 * TODO: tests
 *
 * @param <T> type of awaited value
 */
public class FutureValue<T> implements Future<T> {
    private static final String LOGTAG = "FutureValue";
    private static final long NANOS_IN_MILLI = TimeUnit.MILLISECONDS.toNanos(1);

    private volatile T value;
    private volatile boolean isDone = false;
    private volatile boolean isCanceled = false;

    /**
     * Sets value awaited by this future.
     *
     * @param value value
     */
    public synchronized void set(T value) {
        this.value = value;
        isDone = true;
        notifyAll();
    }

    /** {@inheritDoc} */
    @Override
    public synchronized boolean cancel(boolean mayInterruptIfRunning) {
        isCanceled = true;
        notifyAll();
        return !isDone;
    }

    /** {@inheritDoc} */
    @Override
    public boolean isCancelled() {
        return isCanceled;
    }

    /** {@inheritDoc} */
    @Override
    public boolean isDone() {
        return isDone;
    }

    /** {@inheritDoc} */
    @Override
    public synchronized T get() {
        while (!isDone) {
            if (isCanceled) {
                return value;
            }
            try {
                wait();
            } catch (InterruptedException ignored) {
                Log.w(LOGTAG, "We're just gonna ignore this exception: " + ignored, ignored);
            }
        }

        return value;
    }

    /** {@inheritDoc} */
    @Override
    public synchronized T get(long timeout, @NonNull TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException {

        final long targetTime = System.nanoTime() + unit.toNanos(timeout);

        while (!isDone && !isCanceled) {
            try {
                final long waitTimeNanos = targetTime - System.nanoTime();
                if (waitTimeNanos <= 0) {
                    throw new TimeoutException();
                }
                wait(waitTimeNanos / NANOS_IN_MILLI, (int) (waitTimeNanos % NANOS_IN_MILLI));
            } catch (InterruptedException ignored) {
                Log.w(LOGTAG, "We're just gonna ignore this exception: " + ignored, ignored);
            }
        }

        return value;
    }

}
Related