In this post we will observe Activity through a prism of Single Responsibility Principle of Object Oriented design, and attempt to understand what is the one single responsibility that is best associated with Activity in Android.
Activity:
The following statement opens the official “Activities” page by Google:
An Activity is an application component that provides a screen with which users can interact in order to do something, such as dial the phone, take a photo, send an email, or view a map.
After such an introduction, it is only natural that we will be thinking about Activities
in terms of application’s UI. Today, when the topic of modular design became especially popular and many abbreviations (e.g. MVP) were promoted to a rank of “buzz-word”, perception of Activity
as UI element is especially notable.
I myself started developing for Android with these concepts in mind, but after a while I felt that there are some fundamental issues with this approach, and I began to wonder whether, maybe, after all, UI implementation details do not belong to Activities? This was not an easy concept to adopt, but after I got used to it and implemented it in a real application – I never looked back.
Single Responsibility Principle:
Single Responsibility Principle, as formulated by Robert “Uncle Bob” Martin, states:
A class should have only one reason to change.
For the purpose of our current discussion, we will narrow down the “reasons for change” list to just two reasons:
- Redesign of application’s UI which doesn’t change the general application’s functionality (“facelift”)
- Change in functionality which doesn’t require any change in application’s UI
We will designate as “UI logic” all the logic which could potentially change due to reason #1 (UI changes). We will designate as “business logic” all the logic which could potentially change due to reason #2 (functionality changes).
In order to adhere to the “narrowed” Single Responsibility Principle, classes in our applications must not contain “UI logic” and “business logic” together.
Why separate UI and business logic:
You might wonder what we, the developers, gain by adhering to the Single Responsibility Principle. Well, while it is true that UI and business logic can coexist, there are clear benefits to their complete separation. Some of these benefits are:
- Easy UIs changes – you will be able to update or even completely replace UI while keeping the business logic (almost) intact if they are separated
- UI logic “pollutes” and “obscures” business logic – both your business logic and UI logic will be much more readable and maintainable if they are separated
- UI is tricky to unit test – you definitely want to unit test your business logic, but if UI implementation is not “abstracted out”, you will need to apply test scenarios on UI elements in order to exercise your business logic, which is tricky (and your tests will need to be changed every time UI changes)
The above list of benefits is neither complete, nor the points listed there are necessarily the main benefits, but it shows that separation of UI and business logic is something that we would definitely like to have in our applications.
The “standard” way of implementation of Activities:
Now, once we agreed that separation of UI and business logic is a desirable system’s property in general, we can get back to Activities
. Maybe the way we usually implement Activities
already produces the desired outcome? I don’t think so.
Let’s take a look at one simple example. The following two responsibilities can be found in a single Activity
in almost any application:
- Register listeners for user’s interactions with application’s UI
- Perform actions in response to user’s interactions with application’s UI
It is very natural that Activity
both registers listeners and handles user’s interactions, right? But let’s look at this from a slightly different angle.
In order to register listeners with UI components Activity
must be aware of their IDs (R.id.*
). This is a clear dependency on UI implementation detail – Activity
“knows” the names of UI components (and, usually, it will also know their type: TextView
, Button
, etc.). Therefore, responsibility #1 from the above list falls into “UI logic bucket”.
In response to user’s interactions with application’s UI, Activity
can perform various UI manipulations (e.g. change colors and shapes), but it will, usually, also initiate (or perform by itself) some actions that provide additional value to the user (e.g. note taking application would store a note on button click). These actions are not UI manipulation and do not depend on UI in any way – they are derived from the more global definition of application’s “functionality”. These are application’s “business rules”. Therefore, in the most general case, responsibility #2 from the above list falls into “business logic bucket”.
What we saw now is that even the simplest scheme of registering listeners with UI components and handling interactions with these components in the same class makes UI and business logic entangled. And every developer, who ever had to debug Activities having 500+ lines of code, most of which are UI manipulations with several lines of business logic embedded here and there, knows the pain of trying to figure out what the problem is and how to fix it (without breaking anything else).
Why Activities are not UI elements:
In order to adhere to our “narrowed” Single Responsibility Principle, Activities
in our applications should contain either UI logic only, or business logic only. Which approach is better?
Turns out that Android platform team has already decided for us. The below list of Activity's
dependencies is sufficient in order to make separation of business logic from Activity practically impossible:
Activity
extendsContext
.
The exhaustive list of all the features that make Activity and app’s business logic inseparable is much longer (e.g. request runtime permission, integration with LoaderManager, etc.), but this one is sufficient by itself. It might be surprising that such a basic fact that all of us got accustomed to is of such importance, but it is really that simple.
Functionally, Context objects provide access to most platform’s features that third party applications can use. This is a generalization, but we don’t need a more detailed description for the discussion in this post.
Since Activity is Context’s subclass, our applications use its API in order to take control over a subset of features and resources of the platform. And the logic that “orchestrates” these features and resources is our business logic. Therefore, no matter how hard we try, we will not be able to completely separate business logic from Activities.
Since we can’t separate business logic from Activities, we shall separate all UI logic from it. This is not a trivial task, but it is very well worth the effort in a long run.
Dirtiness test:
In order to be able to discuss separation of UI and business logic quantitatively, we should define some sort of “dirtiness metric” – a measurable quantity which can serve as an indication of how “dirty” our logic is. In fact, we will define two metrics: one for business logic and one for UI logic.
Business logic dirtiness test (for Activities):
Any occurrence of one of the following in your Activities is one “dirtiness point”:
- Lookup in R.layout.* (layout files)
- Lookup in R.id.* (Views’ IDs)
- Dependency on any class or interface which doesn’t score 0 in tests 1 and 2 above
Note that this test is “transitive” – not only your Activities should not be aware of UI implementation details, but the classes that you reference in Activities can’t posses this knowledge either. Therefore, you can’t simply put all “dirty code” in some “helper” class which you instantiate in Activity (unless you don’t aim for 0 in this test).
UI logic dirtiness test (for classes that encapsulate UI logic):
Any occurrence of one of the following in classes that encapsulate UI logic is one “dirtiness point”:
- Dependency on Activity
- Dependency on any class or interface which doesn’t score 0 in test 1 above
Note that this test is also “transitive” – there should be no “dependency chain” from classes that encapsulate UI logic to Activities. Unfortunately, I can’t define dependency on Context as “dirtiness point” – you’ll need to provide a Context to UI encapsulating classes because there is no way to inflate a View without a reference to some Context (Context are God Objects, remember?).
You should always aim for 0 in both the above tests. It is not always feasible to score 0, but you should be explicitly aware of all “dirtiness points” in your applications and have a good reasons for not cleaning them away.
Conclusion:
In this post we discussed why Activities in Android should not contain UI logic. We started by agreeing that separation of UI logic from business logic is a desirable feature, and then showed that it is practically impossible to separate business logic from Activity due to a very tight coupling of Activity to various parts of Android framework. We also defined “dirtiness metrics” for business and UI logic in order to be able to measure the “dirtiness” in our apps.
The concepts discussed in this post are very “high level” and it might be not that evident how our conclusions can be applied in practice. Therefore, I also wrote a series of posts that demonstrate one possible architectural pattern that is built upon the ideas discussed here and can be used for actually writing applications.
Please leave your comments and questions below, and consider subscribing to our newsletter if you liked the post.
Thanks so much, that was very useful post for me
Great post!
Nice post, i used to make the standart Activity, to be honest i never had much problem with this, but i want to explore more options on how to develop a application. Thank you.
Thanks for the post. This is helping illustrate the importance of MVP for me.
Everything was great. But I didn’t get “score 0”! What do you mean by score 0 in your post?
Hi. “Dirtiness” score indicates the level of coupling between UI and business logic. Score of 0 in both categories defined in this post means that there is no “dependency path” from business logic to UI logic and vice-versa. This is the best achievable level of decoupling. Score of 1 means that there is one dependency path and etc.
>We will designate as “UI logic” all the logic which could potentially change due to reason #1 (UI changes). We will designate as “business logic” all the logic which could potentially change due to reason #2 (functionality changes).
That’s not very helpful definition, since it depends on definition of what is “UI” and what is “functionality”, which is more than unclear. And having undefined this affects the rest of the reasoning, making it meaningless.
Hello Joseph,
Thanks for your comment. I agree that terms “UI logic” and “business logic” are loosely-defined, but it is always the case – this separation is subjective, and different developers might have different opinions about where “UI logic” ends and “business logic” begins.
I wouldn’t want this post to be reduced to a discussion of what “UI logic” is, therefore I introduced the simple test that you quoted. I hoped that this definition of “UI logic” through “change request” will allow each reader to “plug in” his view of what “UI logic” is, and concentrate on the main idea I’m trying to convey in this post. If you’re interested in a more specific examples, you might read my series of posts about MVC and MVP in Android.
Hi, Vasiliy. Can I translate this article into Chinese to share it with Chinese Android developers?
Hello Chaos Leong,
It will be a great honor for me to have my article translated and shared.
I will share you the link as soon as possible after the work is done. 🙂
Hi, here is the URL of the translated article.
http://chaosleong.github.io/2017/03/19/Why-Activities-in-Android-are-not-UI-Elements/
It is really a great pleasure to see people take so much interest in my work. Thank you!
Hey Vasiliy,
This is brilliant. I’ve read your MVP posts too and I’m very interested in implementing this myself for my Android projects.
By Day, I’m a Symfony PHP programmer, by night I’m a hobbyist Android developer (I’d rather this be the other way around). So, I’m used to doing things in an MVC-esque way and Android has frustrated me because it cannot lend itself to this.
I’d be interested in seeing a demo HelloWorld Android Studio project on GitHub showcasing exactly how you get around this. I can’t think how you separate the UI from an Activity while getting a “dirtiness” score of 0.
Thanks!
Thanks Matthew, I appreciate your feedback.
MVC series of posts is already based on a tutorial application on GitHub. Exactly what you asked for 🙂 You can grab it from here: https://github.com/techyourchance/android_mvc_tutorial. This application is bigger than a standard “hello world”, though – in my experience, “hello worlds” often don’t “scale” well, therefore it is good to see the concept in a more complete application. I will also be open sourcing one application that I wrote that uses this approach.
If you take a look at tutorial application you will see that the “dirtiness” score there is 1. The best way to get rid of this last “dirtiness point” would be to use dependency injection library (e.g. Dagger 2) in order to inject views implementations into presenters (I didn’t want to make the tutorial app too complicated by usign DI library)
Nice article!
Nice article, didn’t think that the ‘Activity’ / ‘Fragment’ cannot be a UI element
Nice Article. It indeed cleared many misconceptions. Good work.
Thanks for the post, so helpful to understand the things. Is there any example code available which implements it, so it can be more clear.
Hello Prathm,
There is a series of articles about MVP and MVC architectures in Android that builds upon the concepts presented here and comes with an open-sourced tutorial application.
Hi Vasiliy,
Nice article. Can you make an article on Dagger-2 dependency injection? It would be very helpful to your followers.
Hello Maulik,
I’m glad you liked the article.
I have entire set of articles about dependency injection in general, and about Dagger 2 specifically. I would suggest to read them in this order:
Dependency Injection in Android
Dagger 2 Tutorial
Dagger 2 Scopes Demystified
There is also an advanced video tutorial that shows how to structure Dagger modules and components for long time maintainability.
Thanks for the marvelous posting! I certainly enjoyed reading it,
you may be a great author.I will make sure to bookmark your blog and definitely
will come back someday. I want to encourage one to continue your great
posts, have a nice afternoon!
I really appreciate the concepts described in this article! I’m having some trouble reconciling it with Uncle Bob’s Clean Architecture[1], though. Can you elaborate on the alignment between this architecture and that one, where Controllers do not hold references to Views?
[1] https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
Hello Ben,
This is very good and interesting question.
Note that Uncle Bob discussed dependencies, not references. Due to the presence of view interface in between the controller and the view, controllers don’t need to depend on view implementations. Therefore, they don’t depend on UI details.
Imagine the following situation: let’s say you want to replace visual UI with some command line interface. Which parts of the code will need to change? Well, almost nothing. You’ll just need to implement additional subclass of view interface which will handle command line stuff instead of Android Views stuff, and then replace the current implementation of view with the new one. If you’re using proper DI in your codebase, then you’ll be able to do that without changing the controller at all (it’s not shown in my series about MVC, but I have two courses where I show how to do that).
Therefore, as far as I can tell, this approach is in line with Uncle Bob’s principles.
BTW, note that if you’d use Activities or Fragments as views, the above wouldn’t be true. That’s another evidence that Activities and Fragments aren’t UI elements.
Great and important post!
Thanks for the post. I have two questions:
1. In practice, where do you keep UI logic? Are Fragments good for it?
2. setContentView(R.layout.*) automatically fails business logic dirtiness test. How do you deal with it?
Hello Alexander,
Glad you liked the post. My follow-up series on MVC in Android summarizes “my approach” with examples.