“The Android framework manages the lifecycles of UI controllers, such as activities and fragments.”
“UI Controller in Android?” I hear you asking, what’s that?
Let’s back up a second: In traditional Model View Controller architecture (MVC), it is the role of the Controller to route between the View and the Model to maintain state, and to connect to other Controllers in the app. In this second role, MVC represents a way to have isolated units of functionality that can be wired together along known interfaces, and don’t reach into the inner workings of other units of functionality.
Android has a View class, from which interfaces like EditText, and Button inherit. With Architecture Components and AndroidX, it now has a ViewModel. However it doesn’t strictly have anything labeled Controller. However, in a couple of cases, developer.android.com refers to Activities and Fragments as UI Controllers, which I think is correct. Particularly in the case of Fragments acting as controllers, we can create units of functionality as we did in the MVC days.
Consider a bar chart View, one that is responsible for showing some data visually, and perhaps serving up some interactivity to drill into individual pieces of data. It might be backed by some type of List data structure, where each element in the list represents one bar of the chart.
A ViewModel might hold that list of data, and a View might be responsible for drawing the graph on screen, but why do we need a UI Controller (AKA Fragment)? Here’s a subtle, but specific case: Imagine that on click, you get a little label above a bar that gives you a readout of the numerical value for that piece of data. Also imagine, that on configuration change (AKA rotation), we want to keep that specific label on-screen.
The way we persist this state is in the containing Fragment, which might use SavedInstanceState or the ViewModel to persist the data between configuration changes.
## We want to know that if we change some other widget on the same page that we haven’t subtly broken the internals of the bar chart or vice versa — that if we change something in the bar chart, we haven’t subtly broken some aspect of our app.
However, there’s a good way and a bad way of wiring Views to enclosing Fragments: We want to maintain an interface to the bar chart where we can just shove it a List of data, and have it do the right thing. We want to know that if we change some other widget on the same page that we haven’t subtly broken the internals of the bar chart or vice versa — that if we change something in the bar chart, we haven’t subtly broken some aspect of our app. Meaning, we don’t want to expose the internals of the bar chart to the rest of the app. We want to just import the bar chart Fragment class as a unit of functionality that we can use in any other Fragment or Activity.
We take this approach of making a modular component (another term of art that is uncomfortably overloaded in Android), rather than having a monolithic ‘God’ object serve as the controller for a group of Views. Here then, the ViewModel, View, Fragment triad becomes the basic unit of reusable functionality. Additionally we can make these styleable, capable of responding to attribute settings in XML or code. Our bar chart is a fairly ‘dumb’ or ‘card’ type of view, but imagine that we want to wire together a bunch of components that serve more as buttons and levers, rather than visualizations. Previously we used callbacks to communicate between the View and the containing UI Controller, but now we can use the ViewModel.
We can also use the ViewModel to publish data and events out to other Model-View-Fragment units: The ViewModel is observed by our containing Activity or Fragment, with routes to other ViewModels serving as the plumbing that ties everything together.
“An abstraction leak happens when it exposes details and limitations of its underlying implementation to the user…”
“Wait!” I hear you objecting, “doesn’t this create a “leaky abstraction”, where the shared ViewModel exposes unnecessary aspects of the components implementation? On some level, that is true, and you need some sort of convention to separate public aspects of a ViewModel from private ones. Either that or multiple, separate, private and public ViewModels.
[Edit: An approach that used Kotlin’s ‘protected’ keyword was originally here, but on reflection will not work.]
As a final note I would mention that I know some of you are fairly traumatized by Fragments, the manager, and Fragment Transactions. Admittedly, dealing with these has been a pain in the past. AndroidX and the Architecture Components introduces the Navigation Component, which I think makes a lot of that trauma go away. I have a talk on it here.