awex

Additional

Language
Java
Version
v0.0.2 (Apr 29, 2016)
Created
Oct 20, 2015
Updated
May 27, 2016 (Retired)
Owner
Rayco Arana Rodriguez (raycoarana)
Contributors
Rayco Arana Rodriguez (raycoarana)
Alejandro Miragall Arnal (alexmiragall)
2
Activity
Badge
Generate
Download
Source code

awex

AWEX (Android Work EXecutor) is a thread pool to execute tasks that uses Promises to deliver results. Promises that can be cancelled, can be combined or even can process collections in parallel automatically.

How to use it?

First thing you need to do is setup an Awex object, that will be your thread pool. You could create some provider that will keep an instance to the Awex object implementing a Singleton pattern or you could implement it with your DI framework.

public class AwexProvider {

    private static final int WORKER_PRIORITY = Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE;
    private static final int MAX_THREADS = 4;

    private static Awex sInstance = null;

    public static synchronized Awex get() {
        if (sInstance == null) {
            sInstance = new Awex(new AndroidThreadHelper(), 
                                 new AndroidLogger(), 
                                 new LinearWithRealTimePriorityPolicy(WORKER_PRIORITY, MAX_THREADS));
        }
        return sInstance;
    }

}

Once you have an Awex object, all you need to do to start is submit tasks. A Task is an object like the classic Runnable but with some extras. To start with a simple example, let's create a task, submit to the thread pool and do something when it finishes.

Awex awex = AwexProvider.get();
awex.submit(new Task<Integer>() {
    @Override
    protected Integer run() throws InterruptedException {
        //Do some heavy task here

        return 42; //Return some result
    }
}).done(new DoneCallback<Integer>() {
    @Override
    public void onDone(Integer result) {
        Log.i("Awex", "Result to the task execution is: " + result);
    }
});

If your task doesn't return anything, you could use VoidTask instead and have a cleaner code than having to use Task.

awex.submit(new VoidTask() {
    @Override
    protected void runWithoutResult() throws InterruptedException {
        //Do some heavy task here
    }
}).done(new DoneCallback<Void>() {
    @Override
    public void onDone(Void result) {
        Log.i("Awex", "Task finished successfully!");
    }
});

Promises

Inspired by JDeferred library, Awex use Promises to let you add subtasks to your task on all possible scenarios, when it finishes correctly, when it fails or in any case.

awex.submit(someTask)
    .done(new DoneCallback<Integer>() {
        @Override
        public void onDone(Integer result) {
            //Task finishes correctly
        }
    })
    .fail(new FailCallback() {
        @Override
        public void onFail(Exception exception) {
            //Task fails
        }
    })
    .progress(new ProgressCallback() {
        @Override
        public void onProgress(float progress) {
            //Task done some progress
        }
    })
    .always(new AlwaysCallback() {
        @Override
        public void onAlways() {
            //Task either finishes correctly or fail
        }
    });

But if you are in Android, many times you want that some of that callbacks gets executed in the main thread to update the UI. For that purpose, Awex provide the same interfaces prefixed by UI. Every time an UI callback is added, its code is executed in the main thread.

awex.submit(someTask)
    .done(new UIDoneCallback<Integer>() {
        @Override
        public void onDone(Integer result) {
            view.setValue(result); //All it's ok, show the result to the user
        }
    })
    .fail(new UIFailCallback() {
        @Override
        public void onFail(Exception exception) {
            view.showError(); //Can't get the value, show the error message
        }
    })
    .progress(new UIProgressCallback() {
        @Override
        public void onProgress(float progress) {
            view.updateProgress(progress);
        }
    })
    .always(new UIAlwaysCallback() {
        @Override
        public void onAlways() {
            view.hideLoading(); //In any case, hide the loading animation
        }
    });

Waiting for results

You could get the result of the promise at any time, blocking your current thread until it's finished.

Promise<Integer> promise = awex.submit(...);
try {
    Integer result = promise.getResult();
} catch (Exception ex) {
    //Task fails with exception ex
}

But you can get a default value in case of error

Promise<Integer> promise = awex.submit(...);
Integer result = promise.getResultOrDefault(42); //will return 42 when the task fails

Cancelling tasks through promises

A problem you could face when using promises on Android is that sometimes, when the user leaves some Activity or Fragment, any task that is running in the background to load information don't need to be completed and can be cancelled. Even more, you must cancel it and remove any reference to the UI, so you don't leak the Activity or Fragment. For that and many other cases, Awex lets you cancel tasks using the associated promise. Tasks can be interrupted or not when gets cancelled, tasks can query if they are already cancelled or not, so they can stop doing things once cancelled in a gracefully way. In any case, after a promise/task is cancelled, no done/fail/always callback will be executed.

public void onCreate() {
    ...

    //Load data to be shown
    mPromise = awex.submit(new Task<Integer>() {
        @Override
        protected Integer run() throws InterruptedException {
            return 42;
        }
    }).done(new DoneCallback<Integer>() {
        @Override
        public void onDone(Integer result) {
            //Task finishes correctly
        }
    });
}

public void onDestroy() {
    //On destroy, ensure any pending task is cancelled
    mPromise.cancelTask();
}

Tasks and promises could also be cancelled interrupting the worker thread. In that case, the thread will be removed from the thread pool and interrupted. A new thread will be created in the thread pool as soon as a new task is submitted.

public void onCancelSave() {
    mPromise.cancelTask(/* mayInterrupt */true); //Tries to abort the worker thread
}

You could even know when some task in cancelled and add a callback for that case. That will be the only callback that will be executed when a promise/task is cancelled. Remember to not reference any Activity/Fragment from a cancelled callback or it will be leaked!

mPromise = AwexProvider.get().submit(new Task<Integer>() {
    @Override
    protected Integer run() throws InterruptedException {
        //Do some work...

        if (isCancelled()) return -1; //This result won't be processed never

        //Do more work...

        return 42;
    }
}).done(new DoneCallback<Integer>() {
    @Override
    public void onDone(Integer result) {
        //Task finishes correctly
    }
}).cancel(new CancelCallback() {
    @Override
    public void onCancel() {
        //Task cancelled...
    }
});

Not just promises

There are many more things you can do with Awex and its promises.

OR operator

awex.submit(new Task<Integer>() {
    @Override
    protected Integer run() throws InterruptedException {
        throw new RuntimeException("Some error");
    }
})
.or(awex.of(42))
.done(new DoneCallback<Integer>() {
    @Override
    public void onDone(Integer result) {
        //This will be executed always with 42
    }
});

Awex supports doing an OR of many promises using the anyOf() method in the Awex object.

AND operator

awex.submit(new Task<Integer>() {
    @Override
    protected Integer run() throws InterruptedException {
        return 42;
    }
})
.and(awex.of(43))
.done(new DoneCallback<Collection<Integer>>() {
    @Override
    public void onDone(Collection<Integer> result) {
        //result will contain 42 and 43
    }
});

Awex supports doing an AND of many promises using the allOf() method in the Awex object.

AfterAll operator

awex.afterAll(awex.of(41), awex.of(42), awex.of(43))
    .done(new DoneCallback<MultipleResult<Integer>>() {
        @Override
        public void onDone(MultipleResult<Integer> result) {
            //MultipleResult contains all values or error of all promises
        }
    });

Filter operator

FilterSingle

Promise<Integer, Void> promise = awex.of(42);

promise = promise.filterSingle(value -> value > 45);

promise.getResult(); //This will throw an exception, promise don't have any value

Filter all

Filter in parallel

Map operator

Mapsingle

MapSingle operator return a new promise with the result mapped using the method provided. For eaxmple in this case we see how we can map an Integer value of a promise to an String value.

Promise<Integer, Void> promise = mAwex.of(42);

Promise<String, Void> mappedPromise = promise.mapSingle(String::valueOf);

String value = mappedPromise.getResult(); //Value will be "42"

Map all

Map in parallel

Foreach operator

Foreach

ForeachParallel

Then operator

Then operator receives the result of the promise and returns a new promise that will be resolved when the inner operation finishes. Imagine some cloud filesystem API that works completely async where we have a getFolder() and getFile() methods that returns Promises; with Awex could be used in this way:

private Promise<Journey, Void> loadJourney(String userId, String journeyId) {
    return getFolder(getRootFolder(), userId).then(userFolder -> getFolder(userFolder, journeyId))
                                             .then(journeyFolder -> getFile(journeyFolder, "journey.bin"))
                                             .mapSingle(journeyFile -> deserialize(journeyFile));
}

All async, no blocked threads, fluent syntax and no boilerplate.

Download

Download via Maven:

<dependency>
  <groupId>com.raycoarana.awex</groupId>
  <artifactId>awex-android</artifactId>
  <version>0.0.2</version>
</dependency>

or Gradle:

compile 'com.raycoarana.awex:awex-android:0.0.2'

Contributors

License

Copyright 2015 Rayco Araña

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.