First impressions of Agera

Agera makes development way easier, more modular and far more maintainable

I will probably use Agera whenever I can. It simplifies everything. And especially when you combine it with Dagger, it makes your life as an Android developer a lot easier.

Agera

Agera has only been released a few weeks ago. It is a library by Google, that allows you to write reactive Android applications. The best thing however is, that it has been developed specifically for Android. Which most importantly makes it easy to deal with the lifecycles of the different components.

Probably the most important thing you need to remember about reactive programming is that it works through activation. Repositories do nothing until they are observed. So this means, you can create them but they will delay loading data until they are activated. When they are not active they will still take note of changes in the underlying data sets, but they will not reload until they become active again.

Hybrid

Before Agera existed, so let’s say up until very recently. I always used loaders to load locally stored data and I used retrofit to get data from the network. With Agera I can do both; network data and locally stored data, on any thread of my choosing. And the best part is that I can even post-process remote data with local data, or the other way around.

The case

I choose a case where I can really put Agera to the test. Not just loading data from one source, but also composing the data from different sources. Just like I do in Appsii’s Apps page. There are two completely different types of data-sources several to load data from. Firstly the user’s tags and tagged apps, and launch history. They are all loaded from a SQL database. The second data-source is Android’s package manager. The package manager is used to query the installed apps.

When app of this data is loaded, it needs some post-processing; the appropriate tags must be set on the apps and the apps need to be grouped, and sorted based on their tag information.

And keep in mind that even though the apps-page needs this information as a whole, other parts of the application do not need the grouped version of the data. But they may only need the tags that exist in the application. So I want to build a repository that gives me just the tags, and I want my app-page data repository to use this as an input. Effectively creating building blocks that I can compose at will.

Primary types

Let’s first quickly discuss Agera’s primary types we’ll be using. The first component is the Supplier. A Supplier is some source of data. Next is Function. Function is used for transformations. It has an input and an output. Result is another type that is used as the return type for most of the functions in the stream. An Observable is something that can be observed. In other words, listened to by other objects.

A repository is a supplier and an Observable. It is also the main type in Agera.

Loading the tags

The tags are all stored in an sqlite database, accessed using a content-provider. To load the tags from the database we first need a supplier.

static class AppTagCursorSupplier implements Supplier<Result<Cursor>> {

    @Inject
    Context mContext;

    public AppTagCursorSupplier(AppsiApplication app) {
        app.getComponent().inject(this);
    }

    @NonNull
    @Override
    public Result<Cursor> get() {
        Uri uri = AppsContract.TagColumns.CONTENT_URI;
        ContentResolver contentResolver = mContext.getContentResolver();
        Cursor cursor = contentResolver.query(uri, AppTagQuery.PROJECTION, null, null, AppTagQuery.ORDER);

        if (cursor == null) return Result.failure();
        return Result.success(cursor);
    }
}

This supplier just returns a cursor that can be used to access the data. Next, we need to transform that data using a transformation function. The transformations takes the cursor and transforms it into a list of app-tag objects.

private static class AppTagCursorTransform implements Function<Cursor>, Result<List<Apptag>>> {

    @NonNull
    @Override
    public Result<List<Apptag>> apply(@NonNull Cursor cursor) {
        int count = cursor.getCount();
        List<Apptag> result = new ArrayList<>(count);

        while (cursor.moveToNext()) {
            long id = cursor.getLong(AppTagQuery._ID);
            boolean defaultExpanded = cursor.getInt(AppTagQuery.DEFAULT_EXPANDED) == 1;
            String name = cursor.getString(AppTagQuery.NAME);
            int position = cursor.getInt(AppTagQuery.POSITION);
            int columnCount = cursor.getInt(AppTagQuery.COLUMN_COUNT);
            int tagType = cursor.getInt(AppTagQuery.TAG_TYPE);
            boolean visible = cursor.getInt(AppTagQuery.VISIBLE) == 1;
            AppTag tag = new AppTag(id, name, position, defaultExpanded,
                    visible, columnCount, tagType);
            result.add(tag);
        }

        cursor.close();

        return Result.success(result);
    }
}

Now that we have a way to access the data and a way to transform it into something useful, let’s define a repository that does this for us. For this use the complex repository builder. First we start using an empty repository.

This repository observes the content provider containing our tags. Next we tell it to update per loop. Different algorithms allow you to throttle the throughput. Next, we tell it to (from this point on) to execute on a dedicated executor for app-data.

Next, we tell it to get the data from our supplier (skip on error) and then we transform the data with the Function above.

@Named(NAME_APPS_TAGS)
@Provides
final public Repository<Result<List<AppTag>>> provideAppTagsRepository(
        AppsiApplication app,
        @Named(NAME_APPS) Executor appsExecutor) {

    return Repositories.repositoryWithInitialValue(Result<List<AppTag>>absent())
            .observe(new ContentProviderObservable(app, AppsContract.TagColumns.CONTENT_URI))
            .onUpdatesPerLoop()
            .goTo(appsExecutor)
            .attemptGetFrom(new AppTagCursorSupplier(app))
            .orSkip()
            .thenTransform(new AppTagCursorTransform())
            .onDeactivation(RepositoryConfig.SEND_INTERRUPT)
            .compile();
}

Now we have a compiled repository that will load it’s data in the background. One thing to remember is that this repository will not do any work until it becomes observed (activated).

Component lifecycle

Because of it’s architecture Agera fits the Android lifecycle perfectly. Just remember to register and unregister the listener (Updatable) in the right lifecycle method.

RegisterUnregister
ActivityonStart / onResumeonStop / onPause
FragmentonStart / onResumeonStop / onPause
ViewonAttach ToWindowonDetach FromWindow

For Activities and Fragments make sure you register and deregister either in onStart and onStop, or in onResume and onPause. Depending on your situation. For views use the callback that informs you that your view has been attached to, or detached from it’s window.

If you liked this post, keep an eye on this blog as I will write about some more advanced use-cases in a future post.

Cheers!