My journey in developing library for Android

It’s been a long time since I’ve written anything here. Half a year, OMG! But it’s time to stop procrastinating and start writing again. Today I want to share my experience in developing an Android library. And, however, I do not intend to publish it, the process was very interesting for me and I have learnt a lot from it. So let’s start.

Background

Square has a great library called Flow. It provides simple API to establish your app’s navigation via custom defined Screens that represent state of UI. It is very simple and very unusual from standard Android development with Activities and Fragments. Flow works in pair with Mortar - it provides tiny overlay for Activity livecycle and promotes Model-View-Presenter architecture pattern which became very popular nowadays. With the help of Dagger (first or second) it manages scopes of application reducing memory usage and providing different configurations even at runtime. In general, this combination gives you powerful tool to build applications fast and the code you write is very clear and testable. Screens represent state, Views represent UI, Presenters - business logic, Dagger graphs/components tie them all together.

I’ve used all of these libraries on several projects and liked the simplicity Screens give developer in terms of UI navigation. This was my first source of inspiration.

Idea

Everybody knows ButterKnife. Somebody likes, somebody don’t. I like definitely. It hides so much boilerplate you can almost forget about its existence. Simple API and right implementation make it one of the best libraries for Android development. Annotation processing foundation gives it way to generate all hand-written boilerplate for you and hide it.

Annotation processing itself became very popular past few years. This is the thing I missed somehow in Java and wanted to explore. Many projects like ButterKnife started to appear in Android world. I want to showcase Dart and FragmentArgs. The first one helps you with extras in Activity removing getIntent().getExtra() like code. FragmentArgs works similar - hides Fragment arguments extras manipulation and provides simple Builder pattern to construct Fragment instances.

This was the core inspiration - I wanted to have a library to help with Intent-based Activities creation and navigation process (like Flow does), to provide a way to pass arguments, serialize and deserialize them and remove all usual boilerplate (like FragmentArgs and Dart do).

I’m pretty sure the idea is obvious and many developers have also thought about sort of it. For me it was nice reason to learn annotation processing and write useful library.

Process

I started learning annotation processing with great article by Hanness Dorfmann in his blog. It gives a very good start in terms of understanding core aspects of annotation processing, where the pitfalls are and how to avoid them. Still waiting for part 2 by the way :)

I gave my project a working title - ActivityScreens. The API contains of two annotations: @ActivityScreen - defines an Activity to be represented as Screen with arguments to be found in Activity class fields annotated with @ActivityArg. Pretty simple, huh. The following example explains how it can be used.

    @ActivityScreen
    public class SampleActivity extends Activity {

        @ActivityArg
        long id;
        @ActivityArg
        String title;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            // read fields annotated with @ActivityArg
            SampleActivityScreen.inject(this);
        }
    }

Generated helper class SampleActivityScreen:

    public final class SampleActivityScreen {
        public final long id;

        public final String title;

        public SampleActivityScreen(long id, String title) {
            this.id = id;
            this.title = title;
        }

        public void open(Activity activity) {
            Intent intent = toIntent(activity);
            activity.startActivity(intent);
        }

        public void openForResult(Activity activity, int requestCode) {
            Intent intent = toIntent(activity);
            activity.startActivityForResult(intent, requestCode);
        }

        public Intent toIntent(Activity activity) {
            Intent intent = new Intent(activity, SampleActivity.class);
            Bundle bundle = new Bundle();
            bundle.putLong("id", id);
            bundle.putString("title", title);
            intent.putExtras(bundle);
            return intent;
        }

        public static void inject(SampleActivity activity) {
            Bundle bundle = activity.getIntent().getExtras();
            if (bundle == null) {
                throw new NullPointerException("SampleActivity has empty Bundle. Use open() or openForResult() to launch activity.");
            }
            checkArguments(bundle);
            activity.id = bundle.getLong("id");
            activity.title = bundle.getString("title");
        }

        private static void checkArguments(Bundle bundle) {
            if (!bundle.containsKey("id")) {
                throw new IllegalStateException("Required argument id with key 'id' is not set");
            }
            if (!bundle.containsKey("title")) {
                throw new IllegalStateException("Required argument title with key 'title' is not set");
            }
        }
    }

And how to use it:

    new SampleActivityScreen(3, "Sample title").open(activity);

So I started coding, sometimes 1-2 hours a day, with breaks from week to month, sometimes digging into it on weekends. First of all I checked for errors of usage - if @ActivityScreen is set on non-Activity derived object, or Activity is abstract, or private; the same with @ActivityArg - fields must not be private, protected, or final. Also checked for edge cases like Activity without arguments. Then generated simple Screen class with one empty method. It was only beginning.

Month later I came to arguments, coded processing of primitive types first. The goal was to support at least all the types Bundle object support which means they can be parceled. This was the moment I realized the need to setup tests, because it was very easy to break something in code generation and it was pretty hard to identify mistakes. It was very unusual to write code generating another code which also must compile successfully and without errors. So tests primarily were covering generated code, not annotation-processor code. After testing setup was done I actually wrote tests for all features I had and for all compile errors that could happen. It gave me confidence to code further with no fear to break something. All types should be supported so I added type by type with writing tests at first. When the goal was close and only few types left like SparceParcelableArray or ArrayList<Parcelable> I figured it would be good to have a mechanism in library to add complex types which can be transformed to Parcelable objects or primitives. Few days of refactoring and the feature was done. After that adding types I mentioned was easy.

Finally main set of features was ready and I was thinking about publishing. But no.

In total, it took me about 6 month to get the project to working state. Sure, actual coding time I spent would be about one working week or so, but the real time stays the same. I have found that many other developers came up with the same idea and implemented it almost the same way I did. I was not upset with it. On the contrary, it felt that I did the right job and I am not the only one. Also I have found some problems in my project without solution I could give. After all I pushed code to github, listed existing issues and finished working on it.

When the work is done

As I stated at the beginning I liked working on this library. It taught me a lot. And I definitely would like to work on library project again. Now I want ActivityScreens to serve as an example of annotation processing based library. It has reasonable amount of features, test covered and actually can be used. But with set of problems it has and existing alternatives I see no reasons to publish it. So, feel free to check it out and explore - ActivityScreens. Also if someone interesting in contributing - feel free to do it too. Maybe this project will be published someday.

In the end I share links to alternatives: IntentBuilder and PrettyBundle - both of them are very good and thank authors for their job and time.

That’s all I want to say today. Feel free to contact me with feedback. Be awesome.

P.S. By the way I moved to Singapore and now working at BandLab. We are making a platform for musicians and fans to collaborate and engage in music creation process. It is the main reason I was absent these months :)

Leave a Comment