Architecture Components were announced at Google I/O 2017 and provide some frameworks to help developers create more maintainable and more robust apps. The architecture components don’t specifically do anything which will prevent apps from crashing or otherwise misbehaving, but they offer some frameworks which encourages decoupled components within the app which should lead to better behaved app. In this series we’ll take a look at these components and see how we can benefit from them. In this final article we’ll take a step back and look at the overall benefit that Architecture Components can offer us.



If you’ve read the previous articles in this series then some of the benefits of using Architecture Components should be fairly obvious. By using them well we can quite easily cope with some of the common pitfalls when dealing with things like orientation changes. However the important part of the previous sentence is “By using them well”. I personally think that they have been structured sensibly and by their nature will tend to cultivate a more robust architecture, but it is worth taking the time to understand what each of the components does in order to get the most benefit, and to use the correct component in the correct place.

But let’s consider what the use of these components will actually do for our code. Once of the key things is de-coupling. The Architecture Components actually provide some interfaces that we’d usually need to write ourselves and it can be easy to create tight coupling between the components of our app when we do this. By using the Architecture Components we can actually remove much of the interdependency between our components (by using the intrinsic patterns such as publish / subscribe). Our components just need to know about the common LiveData objects, and not each other.

Having decoupled components not only leads to better OO design, but also directly enforces the Dependency Inversion Principle (the D in S.O.L.I.D). Moreover having nicely decoupled components will generally result in code which is easier to reuse and is also much easier to maintain.

Another important consideration is unit testability. On the whole using the Architecture Components will improve the testability of our code because decoupled components are inherently easier to test. For example, fewer interdependencies mean you will need fewer mocks or fakes. As a rule, I try to avoid instantiating objects in-situ — i.e. using the new keyword is often a smell that we’re making code harder to test. This is because we cannot easily mock an object which is created in this way. I prefer to pass in pre-created objects through the constructor, or pass in a Factory object through the constructor which will create objects as we need them. By doing this we can either pass in mock objects for our tests, or pass in a mock factory which will enable us to return mock objects. Consider the following two implementation of the same class:

class MyClass { public String doSomething() { SomeObject someObject = new SomeObject(); return someObject.doSomething(); } } class MyClass { private final SomeObject someObject; public MyClass(SomeObject someObject) { this.someObject = someObject; } public String doSomething() { return someObject.doSomething(); } } class MyClass { private final Factory factory; public MyClass(Factory factory) { this.factory = factory; } public String doSomething() { SomeObject someObject = factory.createSomeObject(); return someObject.doSomething(); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class MyClass { public String doSomething ( ) { SomeObject someObject = new SomeObject ( ) ; return someObject . doSomething ( ) ; } } class MyClass { private final SomeObject someObject ; public MyClass ( SomeObject someObject ) { this . someObject = someObject ; } public String doSomething ( ) { return someObject . doSomething ( ) ; } } class MyClass { private final Factory factory ; public MyClass ( Factory factory ) { this . factory = factory ; } public String doSomething ( ) { SomeObject someObject = factory . createSomeObject ( ) ; return someObject . doSomething ( ) ; } }

In the first case, it is difficult to test this class in isolation from SomeObject because it creates an instance of it, and we cannot replace this with a mock of SomeObject. However in the second case, by creating SomeObject externally, we can easily call this class with a mock of SomeObject. The third example takes it a step further – we pass in a mock of the Factory, and this can return a mock of SomeObject. Passing in objects via the constructor is often called “constructor dependency injection”.

However making classes testable is often harder in components which are instantiated by the Android Framework such as Activities and Fragments because we are not calling the constructors from within our code – it is usually the Framework which is instantiating them. Therefore performing constructor dependency injection is not possible if it is not our code which is responsible for creating a particular object.

This is where things cam become a little tricky with testability and the Architecture Components. It is not a failing with the Architecture Components themselves, it is trying to use them within the constraints of not being able to perform constructor dependency injection which make life trickier. The problem becomes apparent in the onAttach() method of our LocationFragment:

LocationFragment.java @Override public void onAttach(Context context) { super.onAttach(context); LocationViewModel locationViewModel = ViewModelProviders.of(getActivity()).get(LocationViewModel.class); LiveData<CommonLocation> liveData = locationViewModel.getLocation(context); liveData.observe(this, new Observer<CommonLocation>() { @Override public void onChanged(@Nullable CommonLocation commonLocation) { updateLocation(commonLocation); } }); } 1 2 3 4 5 6 7 8 9 10 11 12 @ Override public void onAttach ( Context context ) { super . onAttach ( context ) ; LocationViewModel locationViewModel = ViewModelProviders . of ( getActivity ( ) ) . get ( LocationViewModel . class ) ; LiveData < CommonLocation > liveData = locationViewModel . getLocation ( context ) ; liveData . observe ( this , new Observer < CommonLocation > ( ) { @ Override public void onChanged ( @ Nullable CommonLocation commonLocation ) { updateLocation ( commonLocation ) ; } } ) ; }

Using this code makes it difficult to use a mock of LocationViewModel in our unit tests. In many circumstances we could use constructor dependency injection to inject a test instance of ViewModelProvider, and use this to provide a mock LocationViewModel from the get() method. However this is hampered by the fact that we do not directly instantiate LocationFragment.

It is worth mentioning that there is variant of the ViewModelProviders#of() method which allows us to specify a Factory which is responsible for creating our ViewModel instances, which can then be mocks. However, the same issues arise — we cannot easily pass in an alternate Factory instance without using constructor dependency injection. However if we do this then we’ll have to think about mocking FragmentManager so that the real ViewModelProvider can operate, so we’ll ignore this option, and mock the ViewModelProvider instead.

If we are already using a dependency injection Framework such as Dagger, then we can utilise that to inject an instance, but we’re not, so we have a couple of options. One option we have is to use setter dependency injection:

LocationFragment.java public class LocationFragment extends LifecycleFragment implements LocationListener { private static final String FRACTIONAL_FORMAT = "%.4f"; private static final String ACCURACY_FORMAT = "%.1fm"; private TextView latitudeValue; private TextView longitudeValue; private TextView accuracyValue; private ViewModelProvider viewModelProvider = null; @Override public void onAttach(Context context) { super.onAttach(context); FragmentActivity activity = getActivity(); LocationViewModel locationViewModel = getViewModelProvider(activity).get(LocationViewModel.class); LiveData<CommonLocation> liveData = locationViewModel.getLocation(context); liveData.observe(this, new Observer<CommonLocation>() { @Override public void onChanged(@Nullable CommonLocation commonLocation) { updateLocation(commonLocation); } }); } @VisibleForTesting void setViewModelProvider(ViewModelProvider viewModelProvider) { this.viewModelProvider = viewModelProvider; } private ViewModelProvider getViewModelProvider(FragmentActivity activity) { if (viewModelProvider == null) { viewModelProvider = ViewModelProviders.of(activity); } return viewModelProvider; } . . . } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class LocationFragment extends LifecycleFragment implements LocationListener { private static final String FRACTIONAL_FORMAT = "%.4f" ; private static final String ACCURACY_FORMAT = "%.1fm" ; private TextView latitudeValue ; private TextView longitudeValue ; private TextView accuracyValue ; private ViewModelProvider viewModelProvider = null ; @ Override public void onAttach ( Context context ) { super . onAttach ( context ) ; FragmentActivity activity = getActivity ( ) ; LocationViewModel locationViewModel = getViewModelProvider ( activity ) . get ( LocationViewModel . class ) ; LiveData < CommonLocation > liveData = locationViewModel . getLocation ( context ) ; liveData . observe ( this , new Observer < CommonLocation > ( ) { @ Override public void onChanged ( @ Nullable CommonLocation commonLocation ) { updateLocation ( commonLocation ) ; } } ) ; } @ VisibleForTesting void setViewModelProvider ( ViewModelProvider viewModelProvider ) { this . viewModelProvider = viewModelProvider ; } private ViewModelProvider getViewModelProvider ( FragmentActivity activity ) { if ( viewModelProvider == null ) { viewModelProvider = ViewModelProviders . of ( activity ) ; } return viewModelProvider ; } . . . }

Here we have a setter which we can call from our tests to substitute in our own ViewModelProvider instance:

LocationFragmentTest.java public class LocationFragmentTest { private LocationFragment locationFragment; @Mock private ViewModelProvider viewModelProvider; @Mock private LocationViewModel locationViewModel; @Mock private LocationLiveData locationLiveData; @Mock private Context context; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); locationFragment = new LocationFragment(); locationFragment.setViewModelProvider(viewModelProvider); when(viewModelProvider.get(any(Class.class))).thenReturn(locationViewModel); when(locationViewModel.getLocation(any(Context.class))).thenReturn(locationLiveData); } @Test public void givenALocationFragment_whenOnAttachIsCalled_thenItStartsObservingTheLiveDataObject() throws Exception { locationFragment.onAttach(context); verify(locationLiveData).observe(eq(locationFragment), any(Observer.class)); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class LocationFragmentTest { private LocationFragment locationFragment ; @ Mock private ViewModelProvider viewModelProvider ; @ Mock private LocationViewModel locationViewModel ; @ Mock private LocationLiveData locationLiveData ; @ Mock private Context context ; @ Before public void setUp ( ) throws Exception { MockitoAnnotations . initMocks ( this ) ; locationFragment = new LocationFragment ( ) ; locationFragment . setViewModelProvider ( viewModelProvider ) ; when ( viewModelProvider . get ( any ( Class . class ) ) ) . thenReturn ( locationViewModel ) ; when ( locationViewModel . getLocation ( any ( Context . class ) ) ) . thenReturn ( locationLiveData ) ; } @ Test public void givenALocationFragment_whenOnAttachIsCalled_thenItStartsObservingTheLiveDataObject ( ) throws Exception { locationFragment . onAttach ( context ) ; verify ( locationLiveData ) . observe ( eq ( locationFragment ) , any ( Observer . class ) ) ; } }

We inject a mocked ViewModelProvider and we can then perform a test on a mocked LocationLiveData instance to verify that we begin observing on it.

It’s not ideal – the use of @VisibleForTesting is a sign that we’ve had to alter our code to be more testable rather than it being testable in its own right.

An alternate mechanism would be to move all of the code which corresponds to LiveData into a separate class, and we delegate to this from LocationFragment. As this is now a separate class which will be instantiated from within our LocationFragment code we can use constructor dependency injection on this new class and unit test it in isolation from LocationFragment. While that doesn’t make LocationFragment any easier to test, it externalises some of its logic and makes that independently testable. One of the aims of the Architecture Components is to slim down our Activities and Fragments and externalise much of the complex logic, and if we can slim the LocationFragment down to bare bones, and have unit testable logic in separate classes, then we are increasing the testable footprint of our codebase.

All in all, the Architecture Components offer enormous benefits. I am always reluctant to use unstable libraries in production code because both APIs and behaviour can change, which is never a good thing. The team developing Architecture Components believe them to be pretty stable, but it is still possible that there may be changes before a full release. I eagerly await the stable release so that we can start using them in anger!

The source code for this article is available here.

Many thanks to Yiğit Boyar and Sebastiano Poggi for proof-reading services – this post would have been much worse without their input. Any errors or typos that remain are purely my fault!

© 2017, Mark Allison. All rights reserved.

Related

Copyright © 2017 Styling Android. All Rights Reserved.

Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.