Part 3 — Single activity architecture + some funky Dagger

Ian Alexander
ProAndroidDev
Published in
10 min readMay 12, 2020

--

This series takes a basic MVP app using Retrofit and RxJava to display a list of Github repositories; and converts it into a modern Android app — along the way it will give an introduction to a variety of techniques used when architecting Android apps, explain why those techniques are used, and perform a few experiments to boot.

If you just want to see the code go here the repo will be updated as the series progresses.

Part 1 — Simple dependency injection with Dagger

Part 2 — Converting Presenters into ViewModels

Part 3— Single activity architecture + some funky Dagger

To follow along, this is the project after using MVVM. The next change will be to make use of single activity architecture and the navigation androidx library, plus there’s something funky we can do with Dagger. If you’re comfortable converting activities to fragments, skip on to halfway through the post.

Fragments in 2020

Fragments have come a long way since an infamous Advocating Against Fragments post by square in 2014. Back then Fragments were complex to navigate between, bug prone, and had a cumbersome lifecycle.

Fast forward to 2020 and the androidx navigation library solves fragment navigation, bugs are few and far between, and the lifecycle…well the lifecycle is still quite cumbersome.

But, for better or worse, single activity architecture is the recommended approach from Google and seems to be the future of Android development. So lets go ahead and convert this project to single activity architecture, and see some of the benefits it can bring.

A single activity

This will be the only activity in the entire app, conceptually similar to your application class, but specific to your apps UI. All our fragments will be launched within this activity.

As it’s our only activity a good place to put it can be as a direct descendent of the UI folder.

Morphing Activities into Fragments

Currently there are two screens, both activities. Both these activities extend BaseActivity. So the change here is simple, rename the base activity and change the class it extends.

Inheritance can sometimes receive its fair share of flak, but used well, it can make code changes a breeze. In this case our BaseActivity contains all our common screen based logic which applies equally well to fragments. Here we only have 2 activities to refactor, but imagine a larger app with 10 or 20? By changing one line every Fragment has all the common screen behaviour we need.

There are areas of programming that are regularly abused — inheritance being one of those — but often these areas are also very powerful. Instead of avoiding those areas, a better strategy can be to learn when they’re useful and when they’re not so useful.

But there’s still some work to do to make our screens work with the fragment lifecycle.

Fragment lifecycle

In an Activity most view initialisation code goes in onCreate. A fragment has a significantly more complex lifecycle. There’s a lot of choice when it comes to choosing where to put the code which initialises your view. I try to use:

  • onCreate for initialising data, e.g. fetching values from the Bundle.
  • onCreateView for inflating views. In recent versions of the androidx fragment library the layout id can be passed directly into the constructor, as I plan to add data binding to this project, I won’t use that functionality here.
  • onViewCreated for putting views in the their correct state, e.g. applying listeners and initial values.
  • onStart/onStop etc. as usual

There’s a lot of conflicting advice on which lifecycle methods are best to use for what. Ultimately, find a structure you like and stick to it throughout your project. And if you’re ever unsure on which lifecycle method to use, check the fragment source code — it’s well documented and can tell you exactly when various lifecycle methods are called to suit your purposes.

And the Main Fragment

Although this step is quite time consuming, there’s nothing complex here. All the code stays exactly the same, it’s just rearranged into new lifecycle methods.

Code that needs an Activity

There are always some places in a fragment where you’ll need access to the activity or context. But there’s no guarantee in the api that the context or activity will be non-null when you need it. So to avoid a mountain of null checks, the androidx libraries helps you out.

There’s also a requireActivity method if needed. And for cases where you specifically need an AppCompatActivity, ComponentActivity, or even your single activity (AndroidPlaygroundActivity)— you can create your own extension function.

When using these methods just be aware that it isn’t completely clean. By using methods such as these we are making assumptions about where the fragment is being used and what state the fragments in. Most of the time thats fine, but sometimes it’s not.

Update the manifest

With all our screens converted to Fragments, our Manifest can now be significantly simplified — with a single Activity declaration!

And that’s it, single activity architecture is ready (minus navigation). You can manually navigate between fragments, but that’s often complex and bug prone. With the navigation androidx library, navigation is a breeze. There’s a lot of great docs and articles out there on the navigation library so I won’t go into how to set it up here. But you can look at the repository to see it in action.

Funky dependency injection

Now the interesting bit!

Update 17/06/2020:

With the alpha release of hilt dependency injection with Dagger is about to get a hell of a lot easier! Hilt is an opinionated library over the top of dagger that removes almost all boilerplate dagger code. It makes DI simpler and attempts to standardise how DI is written in Android. From an early look it’s a huge improvement and is sure to become the favoured approach for DI in Android.

The rest of this article goes into some principles about DI, but be aware that with the release of hilt the DI method used in this article should be considered legacy and is actually much easier to achieve with hilt.

A new component

So far our Dagger setup has been made of a single component — AndroidPlaygroundComponent — and every object within that component has access to every other object. That works fine for simple apps, but as apps get bigger we often need to create additional components which only have access to a limited selection of objects.

To try this out lets create a second component…in fact we’ve already been creating additional components, every time @ContributesAndroidInjector is used, Dagger is creating a (Android specific) subcomponent behind the scenes for reasons I won’t go into here — suffice to say the Android OS is not DI friendly.

With knowledge of @ContributesAndroidInjector, it’s easy to create a component for our single activity which contains scoped objects that can only be accessed by other objects in the same component (psst. this means all these objects can only be alive while the Activity is alive).

The @ContributesAndroidInjector annotation takes a modules parameter which defines all the objects available to this component. Every object within @ContributesAndroidInjector can access every other object. But if you try to inject the object into another component, dagger will throw a compile time error.

What about if we need an object fromAndroidPlaygroundComponent ? No problem, each subcomponent has access to all the objects in it’s parent component, grandparent component, great-grandparent component, and so on.

Adding Fragments to the graph

As we’re using single activity architecture, fragments can only ever exist in the single activity. So they should also be scoped to the Activity component.

You’ll notice that the fragments use @ContributesAndroidInjector, this is again due to Androids DI unfriendliness which means Dagger needs to create a new subcomponent for each Fragment.

You might rightly say here that Fragments shouldn’t know about each other in the DI graph. No problem, subcomponents can’t use objects in other subcomponents that are not their parent. So in our dagger world, MainFragment has no knowledge of DetailsFragment and visa versa.

But why is this useful?

Take something like navigation, in most Android apps the code for navigation is scattered throughout our screens. This breaks many fundamental principles of good architecture. Which you realise when upgrading to the androidx navigation library — a change needs made to EVERY screen, in large apps this becomes extremely cumbersome. Simply put, scattered responsibility is hard to maintain and bug prone.

Architectural principles often encourage abstracting responsibility behind interfaces so that responsibility becomes an implementation details — easy to swap in and out without effecting the rest of the app. These days this is commonly encouraged for the data layer of the app and implementation details like Retrofit are often hidden behind interfaces. So why not use the same concept in the view layer of our app?

Abstracting navigation

What if our Fragment has no knowledge of how navigation occurs? Instead fragments use a simple navigation interface to order navigation. This simplifies fragments by limiting their responsibility and encourages composable behaviour in the UI — reducing code duplication.

As this is code which has no connection to Android, this could now live in your business layer (but preferably your view model layer if your app has that many layers). And the implementation would live on your Android layer.

Usually the only requirement of navigation code is that it has an Activity reference. Luckily we added our single activity to the dagger graph earlier so it can be injected into our implementation. And as the Navigator is scoped to the Activity it can only be used inside the subcomponents object graph — so only while the Activity is alive.

Where to call the Navigator

This is a little more tricky.

The simple answer is to inject the Navigator into your fragment and call it from there.

But there is another possibility. Anyone that’s used MVVM in Android will be familiar with one-time actions — there’s a host of medium articles on the subject. For example, the view model wants to display a toast but only wants to display it once, so has to reset view state after the toast is displayed to avoid re-showing the toast after an event such as a screen rotation.

As the navigator now uses a similar concept to business logic, and our view models will only exist while an activity exists, our view models can become part of the activity subcomponent, and the navigator can be injected directly into the view model constructor.

This means there’s no need to expose one time actions such as navigation through view states. And your view models becomes as simple as:

This is now super simple to test or debug, your navigation code is abstracted to a single class, plus no fancy one-time actions are needed in your view states.

The only downfall of this approach is that if you’re using the androidx ViewModel this approach WON’T WORK. The reason being that the ViewModel has a longer lifecycle than an Activity. By using this method you’d be indirectly adding a reference to the Activity within the ViewModel. Therefore on every screen rotation, Activity would leak. There are ways you could get round that, but nothing pretty.

So if you’re using ViewModel you’re stuck injecting the navigator into the Fragment.

Extending the concept

You can even extend the same concept to Toasts, Snackbars, Keyboard listening, and much more. In the final code for this section I’ve solved the bug mentioned at the end of the last article by using this same structure for Toasts. Essentially a whole variety of code in the view layer of an app can follow strong architectural principles and be styled as focused classes of responsibility — all by using the power of dependency injection.

Conclusion

Single activity architecture is a step up in Android development, and with the navigation library, converting a codebase to this structure can be a breeze. It also opens up some interesting architecture patterns which now require less boilerplate.

The ideas in the second half of the article attempt to solve a common problem with Android view code where — even in well architected codebases — many fundamental principles such as SOLID are forgotten and features like navigation are scattered throughout Activities and Fragments.

The overriding principle is that just like any other layer of your app, the view layer can be architected as focused classes of composable behaviour. This makes it easy to modify UI down the line, changing from Snackbars to Dialogs to show announcements? A single class can be changed to modify how announcements throughout the app are displayed. Updating to the androidx navigation library? Again, a single class needs modified.

Many parts of your view become implementation details — simple to swap in and out without effecting the rest of your app.

On top of this, testing becomes simpler. For standard unit tests too much much responsibility leads to long, hard to write and maintain tests — a code smell. The same holds true for UI tests, by making our view layer composable, UI tests are far easier to write and maintain.

I’ve used this approach in a few apps, and I’ve found that it makes developing easier, less code is duplicated, bugs are less common, and most importantly, large UI changes become a breeze. So what do you think? Is this approach useful?

Final code for this section is here.

--

--