UML for Android Engineers
Learn how to draw UML diagrams to document your Android applications. By Massimo Carli.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
UML for Android Engineers
50 mins
- Getting Started
- Understanding UML
- Deciding What to Document
- Creating Use Case Diagrams
- Creating Complex Use Case Diagrams
- Creating a Deployment Diagram
- Creating a Dependency Diagram
- Creating Class Diagrams
- Representing Relations
- Understanding Aggregation
- Representing Abstract Classes
- Representing Static Members in UML
- Representing Just What You Need: Another Example
- Using Stereotypes
- Creating Dynamic UML Diagrams
- Representing Objects
- Understanding Collaboration Diagrams
- Understanding Sequence Diagrams
- Understanding Asynchronous Invocations
- Understanding State Diagrams
- Diving Deeper Into State Diagrams
- Where to Go From Here?
Creating Class Diagrams
So far, you’ve created diagrams explaining some high-level aspects for the Poster Finder app, like:
- What the app does and who its users are.
- How the app communicates with external systems.
- What the structure of the packages and their dependencies are.
When engineers need to change the app, they need to know how the code works. They need to know what the main components are and how they’re related. This also allows you to recognize design patterns — or the lack of them. When the items you need to document are classes, you create a class diagram. In this case, the main rule is still not to describe everything, but just what you need.
Open the MainActivity file in the ui.screen package and look at the following code:
@AndroidEntryPoint
class MainActivity : AppCompatActivity(), NavigationHandler {
private lateinit var viewBinder: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinder = ActivityMainBinding.inflate(LayoutInflater.from(this))
setContentView(viewBinder.root)
if (savedInstanceState == null) {
displayFragment(MovieListFragment())
}
}
override fun displayFragment(frag: Fragment, backStack: String?) {
supportFragmentManager.beginTransaction()
.replace(viewBinder.anchorPoint.id, frag).apply {
backStack?.let {
addToBackStack(it)
}
}
.commit()
}
}
As you can see, the main items here are:
MainActivityNavigationHandlerActivityMainBinding
What can you say about them? Consider this class diagram:
Class Diagram for MainActivity
There are lots of things to say here:
- You represent each class with different sections of a box. In this diagram, the name MainActivity also tells you that it’s a concrete class. You’ll see later how to represent abstract classes. In Kotlin, you also know that a class is final by default unless you don’t write otherwise. This aspect isn’t important in the context of this diagram.
- The second part of
MainActivitycontains the instance variables for the class. Of course, this isn’t all of them, just the ones you want to describe. In this case, you have theviewBinderproperty of typeActivityMainBinding. It’s fundamental to note that the property is private. This is because you have a – in front of it. You represent public variables with + and protected with #. - The last section should contain the methods for the class. In this case, you have nothing — not because
MainActivitydoesn’t have methods, but because they’re not important for this diagram. You’ll see cases later when putting method definitions here is useful. - You represent the
AppCompatActivitywith a simple rectangle with the name of the class in it. This is because you’re not interested in the structure ofAppCompatActivitybut, as you’ll see soon, in its relations with the other classes. The same is true forActivityMainBinding. -
NavigationHandleris an interface. You represent an interface with a box with two parts. The first contains the name of the interface and the stereotype «interface». In theory, you could represent an interface by writing its name in italic, but this isn’t a good practice, especially if you draw the diagram on paper. - The lower part of the box for
NavigationHandlercontains the list of operations that, for an interface, are public. This is the reason for the +. In this diagram, you also see how the operation is in italic, but, as said, this isn’t a must — although it might be useful in the case of some default function implementations.
This is how you can represent classes and interfaces, but there’s something more important to represent: their relations.
Representing Relations
Above, you learned how to represent classes and interfaces, but what makes a class diagram useful is the way it represents relations. What’s the relation between MainActivity, AppCompatActivity, ActivityMainBinding and NavigationHandler? Consider the same class diagram:
Class Diagram and Relations
In this diagram, you can see three of the most fundamental relations:
-
MainActivityextendsAppCompatActivity. The correct way to explain this is thatAppCompatActivityis an abstraction ofMainActivity, and you represent this using a solid line ending in an empty triangle. This is implementation inheritance because you’re saying thatMainActivityIS-AAppCompatActivity, and so it does all the things thatAppCompatActivitydoes. In this diagram you’re not saying whatMainActivitydoes in a different way fromAppCompatActivityand so you are not showing the method you override. -
MainActivityalso implements theNavigationHandlerinterface. This is interface inheritance, and you represent it using a dotted line ending with an empty triangle. With this, you’re saying thatMainActivitydoes whatNavigationHandleris supposed to do. The fact thatMainActivityisn’t abstract implicitly says that it provides implementation fordisplayFragment(). - The relation between
MainActivityandActivityMainBindingis a very interesting one. A very generic way to read this isMainActivityusesActivityMainBinding, but here you’re saying something more. With the solid line starting with the full diamond and ending with an open arrow, you’re saying that not only doesMainActivityuseActivityMainBinding, but that it also creates an instance of it. This happens throughActivityMainBinding.inflate(), but the important thing here is that the lifecycle ofActivityMainBindingis the same asMainActivity, which creates it. Also, theActivityMainBindingyou create isn’t visible outside and, even more important, it’s not shared with anyone. In short,MainActivityowns the instance ofActivityMainBindingit creates. The name for this relation is composition.
These are some of the most important relations you can represent in a class diagram. However, composition isn’t the one you have when using dependency injection. In that case, you have aggregation.
Understanding Aggregation
As an example of aggregation, consider the following class diagram that describes what’s in the repository package. It’s what you can associate with the following code you find in the OMDbMovieRepository.kt file in the repository package.
class OMDbMovieRepository @Inject constructor(
private val endpoint: OMDbEndpoint
) : MovieRepository {
override fun getMovies(query: String): Flow<PagingData<MovieDto>> {
return Pager(
config = PagingConfig(pageSize = PAGE_LENGTH, enablePlaceholders = false),
pagingSourceFactory = { OMDbPagingSource(endpoint, query) }
).flow
}
}
And in the OMDbEndpoint.kt file in the api package:
interface OMDbEndpoint {
@GET("?plot=full")
suspend fun findMoviePoster(
@Query("s") movieTitle: String,
@Query("page") page: Int,
@Query("apikey") apikey: String = BuildConfig.omdbApiKey,
): OMDbResponse
}
Class Diagram – Aggregation
This diagram has a few important things to note:
- As you already know,
OMDbMovieRepositoryimplementsMovieRepository. -
OMDbMovieRepositoryusesOMDbEndpointwith an aggregation relation you represent with a solid line starting with an empty diamond and ending in an open arrow. You can say thatOMDbMovieRepositoryaggregates anOMDbEndpoint. This means thatOMDbMovieRepositoryisn’t responsible for the creation ofOMDbEndpoint. Somebody else is providing the referenceOMDbMovieRepositoryneeds. A consequence is that the sameOMDbEndpointcan be shared between different objects. It also means that the lifecycle of the object of typeOMDbEndpointisn’t bound to the lifecycle ofOMDbMovieRepository. In this diagram, you also mention what the cardinality of the aggregation is. For eachOMDbMovieRepository, you have oneOMDbEndpointimplementation. - You learned that UML is an open language. You can use your own symbols as long as it’s clear what you want to represent. In this case, you’re using a custom stereotype, «Retrofit». This tells the reader that the
OMDbEndpointinterface describes an endpoint using the Retrofit framework. This is quite Android-specific, but you’re expecting the reader to be an Android engineer, so that shouldn’t be a problem. - Open the OMDbEndpoint.kt file in the api package, and you’ll see how
operationhas the suspend modifier. But, how do you represent a suspend function in UML? This isn’t something that UML provides by default. But, again, the goal is to make this clear. In the previous diagram, you just add the suspend modifier to the signature forfindMoviePoster(). Pretty simple, right? :]
As you can see, a class diagram is very simple as long as you decide to describe just a few aspects of your code.
You learned how to represent classes, interfaces and the main relations between them. But how do you represent an abstract class?


