|
So a long long looooooong time ago, some person thought "What if we could respond to things changing directly after they change instead of constantly polling?" Thus, Reactive programming was formed.
rxJava
Now, even though rxJava stands for "reactive extensions for Java," rxJava is more functional programming than reactive.
rxJava focuses on event driven data flow using dynamically built observables. rxJava observables can send a total of 3 events: Data, Error, and Done. rxJava has flow control operators too, like filter, reduce, map, etc. Flow control operators basically act like a subscription that sends data to another subscription in a chain. This can be extremely inefficient because of all the object allocation that goes on. It also has a rather confusing threading model, since rxJava was made to use in desktop Java applications.
Agera
While Agera is rather new, it gives reactive programming crafted explicitly for Android. At the core of Agera, you have these interfaces: Observable, Updatable, Receiver, Result, and Supplier. Unlike rxJava, reacting comes first and data flow second. And inside of Agera's implementations it uses Android primitives, like Loopers and Handlers, to communicate and automatically debounce duplicate updates to updatables (this also ensures that the thread you used to subscribe on is the one responsible for handling the updates).
Agera also has results. Results represent the actual data, and they have 3 different states: Present, Failed, and Absent. This allows the program to optionally synchronously handle data if it has already been loaded.
An example
Lets say we're loading a list of games from multiple directories on the filesystem. For each game we must ensure that it's valid and create a new Game object from the file.
rxJava:
Loader implementation:
Code: Observable.from(directories) // Each of these steps cause some sort of allocation!!
// Missed implementation detail: ReplaySubject madness that would allow for caching.
.subscribeOn(Schedulers.io()) // Or maybe it's observeOn... I don't know. rxJava is kinda ambiguous here...
.map(File::listFiles)
.flatMap(Observable::from)
.map((file) -> {
if (Game.isValidGame(file)) {
return Game.from(file);
}
return null;
})
.filter((game) -> game != null)
.toList();
Using the loader:
Code: loadGameLater() // Not seen: The method and the class that returns the above observable
.observeOn(AndroidSchedulers.mainThread()) // Same comment from above...
.subscribe(list -> {
// Do something with the list here...
});
Agera:
Loader implementation:
Code: public class GameList extends BaseObservable implements Supplier<Result<List<Game>>> {
private Result<List<Game>> result = Result.absent(); // Returns a reference to a static and immutable object
// Also, as a consequence, we get caching!
@NonNull
@Override
public Result<List<Game>> get() {
return result;
}
@Override
protected void observableActivated() {
// Called when the GameList goes from 0 subscribers to 1 or more
loadDataIfAbsent();
}
private void loadDataIfAbsent() {
if (result.isAbsent() || result.failed()) {
executor.execute(() -> { // Not seen: Java thread executor that runs this on another thread
try {
List<Game> games = new ArrayList<>();
// Essentially the raw logic from rxJava
for (File directory : directories) {
for (File file : directory.listFiles()) {
if (Game.isValidGame(file)) {
games.add(Game.from(file));
}
}
result = Result.present(ImmutableList.copyOf(games));
} catch (Exception ex) {
result = Result.failure(ex);
}
dispatchUpdate();
});
}
}
}
Using the loader:
Code: // Not seen: The class that has these methods and fields
private GameList gameList = new GameList(); // This would be a singleton somewhere and injected by Dagger
private boolean observing = false;
private void startObserving() {
if (!observing) {
gameList.addUpdatable(onGameListChanged);
// Advantage: If a piece of code clears the cache (say a new library directory) we get notified!
onGameListChanged.update(); // We have to call this after registering so previously cached data can be loaded
observing = true;
}
}
private Updatable onGameListChanged = () -> {
Result<List<Game>> result = gameList.get();
if (result.isPresent()) {
List<Game> list = result.get();
// Do something with the list...
}
};
private void stopObserving() {
if (observing) {
gameList.removeUpdatable(onGameListChanged);
observing = false;
}
}
As you can see the rxJava implementation is shorter than the Agera implementation... However, the Agera implementation has caching and proper reactivity. In rxJava caching means taking extra care to make sure that multiple threads don't request something at the same time, which causes the data to load multiple times (!!!) before caching. Furthermore, Agera deals with all the threading madness and DOESN'T create multiple objects just to do something simple.
Who is using rxJava?
There are 8,156 projects that use rxAndroid and rxJava on Github.
Who is using Agera?
There are 42 projects that use Agera on Github (due to the age of the library). Google uses Agera inside of their Google Play Movies app (which is where the code was extracted from).
Because Google made Agera, they may soon make it a recommended library. It's also made for Android instead of generic Java applications.
|