Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Android] rxJava vs Agera
#1
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.
Reply
#2
Alright, rxJava sounds pretty decent, although Agera sounds a bit immature to be used right now. I might want to hold off on converting things over to that, but since it's Google-made, that might be unwise.
Reply
#3
(05-17-2016, 10:24 PM)endrift Wrote: Alright, rxJava sounds pretty decent, although Agera sounds a bit immature to be used right now. I might want to hold off on converting things over to that, but since it's Google-made, that might be unwise.

I used Agera in a music player project of mine to manage playback state, playback queue contents, and loading songs and albums from the OS-wide content provider.

I also forgot to mention something that's unique to Android: the unholy method count limit. Android has a limit of 65536 methods. It's a flaw in the dex file design. Currently rxJava+rxAndroid add 4675 methods to the app, 4605 being rxJava and the rest rxAndroid. Agera, on the other hand, adds only 445 methods.

Method count for rxAndroid+rxJava
Method count for Agera (This includes a compile time dependency for those @NonNull annotations. The code already uses that library because it's included in the support libraries)

Now the method limit can be circumvented with MultiDex, but MultiDex slows down app startup.
Reply
#4
If you think Agera is ready, I'll trust your judgment on this one, although I'd prefer not to have the intermediate version using rxJava in the tree if you think Agera is definitely the future.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)