Custom Layout interaction on Android, with no strong references

Avoid expensive bookkeeping in your custom layouts using Dagger and RxJava

Brian Peck
Brigade Engineering

--

The Thought Process

I’ve been working on the Android Team at Brigade, and coming from a heavy Java/Scala background I’m used to putting objects inside small, self-contained, immutable classes. However, activities/fragments and layouts seemed to be inherently mutable objects; swapping out fragments inside container layouts and changing the text in views already on the screen. These changes are generally more CPU- and memory-efficient than creating everything new from scratch all the time, so the need for this made sense.

However, creation of these custom fragments and layouts use the built-in constructors. These don’t allow for providing a hard set of custom constructor arguments to ensure all data you might want is provided before the object is created. Most ways around this seem to involve just holding a strong reference to the object created, and pass along the data that is needed from the parent classes after construction.

For activities and fragment communication it seemed to make sense, as the documentation for a Fragment includes:

A fragment must always be embedded in an activity and the fragment’s lifecycle is directly affected by the host activity’s lifecycle.

However, custom layouts can have parent containers including fragments, adapters, other layouts, and more. If you wanted a custom layout to control all of the logic related to its view, while relying on data provided or state interacted with elsewhere, you seemed to wind up with a long trail of references connecting one piece to another. When a view needs to update its state after an event inside a completely different place, moving all of the data around almost seems more trouble than it’s worth.

So as an experiment I decided to see if there was a way around this using event monitoring and dependency injection. Injecting RxJava objects using Dagger, that are then used to handle communication across layouts and/or fragments without needing the chain of references.

The Code

First, we need to get a basic project setup with Dagger. Luckily, their example page was nice and easy to follow, so setting up the object graph inside the application was pretty simple.

In the example application I wrote up, I made a simple project (not even any real assets to speak of) that mimics the types of interactions that would happen when drafting an arena in Hearthstone. Creating a deck of cards, by choosing one card at a time from a limited set of choices.

However, I decided to create it without the screen real estate afforded by having multiple fragments visible. This meant updating a nice floating action button (FAB), based on input of a selected card inside a separate layout, while also being able to show all of the previously chosen cards when they needed to be accessed.

Below is an overview of the high level objects and how they’ll be interacting.

To start implementing those high level items we create the Component that items will use for their dependency graph.

Once you have your component you need to create the actual instance of the graph that each component uses, so Dagger can provide us our injected data. That is done inside your project’s Application.

And, as noted by the comment above the component member variable, some other ways are needed to add yourself to the object graph if you don’t have easy access to the Application to use the component() method.

We now can go into how a class would make use of a variable that has been provided through dependency injection. There is work needed to create these objects, however that will be explained later as some custom logic is used around the RxJava objects in the example. Knowing where and how the items are used should provide better clarity as to the custom logic being employed at creation.

First we start with the floating action button containing the progress of the draft choices.

The first thing to do (upon construction) is add yourself to the dependency injection frameworks for our Rx object.

Since we’re inside a custom layout we can’t easily access the running application. However, by utilizing a static component we can alleviate this concern.

Now that we’re in the graph, and the Observable containing our data has been initialized, we can use it to listen to the cards that are being chosen. We accomplish this by subscribing to the observable and creating the appropriate handlers.

We subscribe to our observable with a positive case handler and a negative case handler. One handler listens to the list of cards that have been selected, the other makes sure that if we have an issue while sending these messages around we don’t crash the app, and can at least report the error. Now, any time an object hits the Subject backing our injected Observable, we will be notified and can adjust our text to show the user’s progress inside the draft.

We also need to make sure we keep track of the subscription to close it when we’re done. RxJava subscriptions can be cleaned up by calling the onComplete() event on the subject, allowing any subscriptions to be closed. However, in cases were you don’t want to close down the flow of data, you’ll need to keep a reference to the subscription and unsubscribe yourself when you no longer need the information.

And with that, you have seen what happens, in one of the places, when a card is selected. So we now look at the other end of the Rx interaction, the location where the event is fired.

Inside the layout of the card being chosen by the user, we have injected the other end of that Observable: a behavior subject listening to cards.

When the image is clicked (thanks to Butterknife for the OnClick) we fire off an event across the BehaviorSubject backing the Observable that the other layout is using. Thusly, without even knowing the parent containers of the FAB or of the layout containing the card being chosen, we have provided an interaction between the two.

Though, a piece is missing; how do we create those handy Rx objects, and why was only a single item sent, but many objects are received? For that we create ourselves the other end of the component used by Dagger; the Module.

In other places we are using just the standard [Type]Subject.create() construction semantics. However, for anyone who looks at the full source I wanted to explain the more confusing, card choice, BehaviorSubject and its interaction.

For the simple application, I wanted to be able to reset the arena cards chosen. However, based on the way we’re using @Singleton injections, this meant we needed a way to clear the data being listened to.

I wanted to replicate the functionality of a ReplaySubject with the ability to reset its list of objects, without creating a new subject. This led to the chained BehaviorSubject seen above, which can have its list of chosen cards reset.

This is the bulk of the code used to create the Application. The above code, and the source, make heavy use of dependency injection and RxJava to create a simple little application demonstrating how to communicate across custom layouts and views without having to keep references to the views themselves.

Some Caveats

The astute reader may have caught something I conveniently ignored; we still have some references, but now they are to Rx subjects and subscriptions instead of a chain of layouts and containers. And these will need to get cleaned up to not cause problems such as receiving events when you don’t want to, or having the layout not removed from memory when it needs to be recycled. And there are fixes for the issues, but this is where the complexity really starts to show itself when using this type of mechanism.

When you’re injecting into fragments/activities for communication, it’s nice and easy to know when you’re not actually on the screen anymore; Android gives us onPause() and onResume() to hook into, but layouts are a bit trickier. Although we have onAttachedToWindow() and onDetachedFromWindow(), which the above examples leverage, sometimes you need to stop listening even when not quite detached yet.

For this you may need an additional Rx subject/observable that essentially tells all of the current people subscribed to it to stop listening and shut down.

For non-@Singleton objects this can be accomplished by sending the onComplete() call down the observable, and things are handled. But with @Singleton injected objects cached by Dagger, people wanting new subjects will receive ones that will never have messages sent to them.

But there are ways around this… (notice a pattern here)… using either internally-controlled singleton objects, or just non-singleton objects at all—but then you’d need to pass the non-singleton object through to the layouts. However, this might force a reference to the layout, which was the thing we were trying to avoid in the first place.

Real World Application

Hopefully this little experiment will help you cut down your class size or reference cleanup logic a little to make your application more maintainable. Proper setup using these mechanisms can fix some of the issues of custom layouts handling logic.

At Brigade we ended up not going too deep into making our actual layouts that need to communicate with each other this complex in most situations. We kept the Rx injection limited to small sections. When you start getting a heavy amount of them, across many views (especially reused views), the likelihood of not properly cleaning up the subscriptions greatly increases. And tracking down issues when that happens can become more work than the benefit of the lack of references to your views.

That said, with a good understanding of how the Rx observable chain works, and carefully watching your construction, and clean up, of the subscription events, you can remove a lot of the boilerplate needed to pass data from A to B. And by creating these communication channels you can avoid having to keep all the click or data passing logic inside the fragment containing the communicating objects.

--

--