On #JetpackCompose Alpha day, I want to share a story about Architecture in Compose
When we open sourced Compose at I/O 2019, one of the most common questions from many Android developers was “What does this mean for the architecture of our apps?”
When we open sourced Compose at I/O 2019, one of the most common questions from many Android developers was “What does this mean for the architecture of our apps?”
Compose doesn’t change the architecture of your app. The guide to app architecture remains our opinionated guidance for a common architecture for Android apps.
At the same time, there’s been a lot of discussion inside and outside of Google about architecture in Compose apps.
At the same time, there’s been a lot of discussion inside and outside of Google about architecture in Compose apps.
Today, in most Android architectures, the smallest UI component in the architecture is an Activity or Fragment.
This is a natural result of two things:
1) Android speaks Activities (or Fragments)
2) Managing custom views is harder than managing Fragments or Activities
This is a natural result of two things:
1) Android speaks Activities (or Fragments)
2) Managing custom views is harder than managing Fragments or Activities
Compose flips this on its head. In Compose the smallest UI component in the architecture can be as small as a single Text or Button.
Everything from an AppNavigator down to a Button is a composable. So the architecture of your UI can fit the architecture of your app.
Everything from an AppNavigator down to a Button is a composable. So the architecture of your UI can fit the architecture of your app.
We quickly realized that composables would need a way to consume state from a ViewModel. To accomplish this we added Compose APIs for reading UI state in compose
LiveData
https://developer.android.com/reference/kotlin/androidx/compose/runtime/livedata/package-summary
Flow
https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary#(kotlinx.coroutines.flow.Flow).collectAsState(androidx.compose.runtime.kotlinx.coroutines.flow.Flow.collectAsState.R,%20kotlin.coroutines.CoroutineContext)
RxJava2
https://developer.android.com/reference/kotlin/androidx/compose/runtime/rxjava2/package-summary
LiveData

Flow

RxJava2

One way to use compose is as a replacement for the UI toolkit. Each screen in your app gets an Activity or Fragment and simply uses Compose to render the content.
The architecture for a screen following this pattern doesn't change – use a ViewModel in the Activity or Fragment.
The architecture for a screen following this pattern doesn't change – use a ViewModel in the Activity or Fragment.
You can also use compose to manage navigation between screens, like we do in the JetNews sample ( https://github.com/android/compose-samples/blob/2107dc42de5ec6c178ca57df605908a2de31011f/JetNews/app/src/main/java/com/example/jetnews/ui/JetnewsApp.kt#L70)
As we explored this, we realized we'd need a way to use ViewModels in Compose. So, we introduced a new API
viewModel
https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary#viewmodel
As we explored this, we realized we'd need a way to use ViewModels in Compose. So, we introduced a new API
viewModel

From the beginning, Compose has been designed for unidirectional data flow. That's a design where state flows down and events flow up.
This applies both to big architectures like what you build Android Architecture Components, as well as a single composable.
This applies both to big architectures like what you build Android Architecture Components, as well as a single composable.
So now let's talk about architecture inside of Compose.
As you add state to a composable by using ViewModel, you couple that composable with the ViewModel. It becomes stateful.
Stateful composables are harder to reuse, harder to test, and tend to be more complicated.
As you add state to a composable by using ViewModel, you couple that composable with the ViewModel. It becomes stateful.
Stateful composables are harder to reuse, harder to test, and tend to be more complicated.
We can use unidirectional data flow to separate the stateful composable from a stateless composable that just draws the screen. We applied this pattern in JetNews when adding ViewModels
stateful
https://github.com/android/compose-samples/blob/2e4980a522975c10918b3deeced2bca9dbbfa74b/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt#L72
stateless
https://github.com/android/compose-samples/blob/2e4980a522975c10918b3deeced2bca9dbbfa74b/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt#L114
stateful

stateless

Using this pattern to extract state from our UI code avoids coupling your UI deeply with your state management.
And, stateless composables are easier to reuse, test, and tend to be simpler.
And, stateless composables are easier to reuse, test, and tend to be simpler.
And now for the story.
To showcase ViewModels and LiveData used in a unidirectional data flow in Compose, we aimed to refactor JetNews to use ViewModel with the alpha launch.
Here's the commit that refactors JetNews to use ViewModel https://github.com/android/compose-samples/commit/2e4980a522975c10918b3deeced2bca9dbbfa74b
To showcase ViewModels and LiveData used in a unidirectional data flow in Compose, we aimed to refactor JetNews to use ViewModel with the alpha launch.
Here's the commit that refactors JetNews to use ViewModel https://github.com/android/compose-samples/commit/2e4980a522975c10918b3deeced2bca9dbbfa74b
When reviewing that, @ppvi noticed that the alpha viewModel API can't be used in our multi-screen Compose app.
https://github.com/android/compose-samples/pull/116#pullrequestreview-470291032
Instead of creating a new ViewModel when entering a screen, it re-used the old one. The details screen would show the wrong article. Uh oh.
https://github.com/android/compose-samples/pull/116#pullrequestreview-470291032
Instead of creating a new ViewModel when entering a screen, it re-used the old one. The details screen would show the wrong article. Uh oh.
We quickly threw together some code to scope a ViewModel to a composable. This fixed the article screen – now it would load the correct article every time it entered.
https://github.com/android/compose-samples/blob/f8dc797d8677eb68fe8134c0b274c4bc9ceb13ec/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt#L77
It also destroyed the ViewModel on rotation – so we decided to
ship.
https://github.com/android/compose-samples/blob/f8dc797d8677eb68fe8134c0b274c4bc9ceb13ec/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt#L77
It also destroyed the ViewModel on rotation – so we decided to

So, we backed out ViewModels in the JetNews sample until the Compose APIs can support this use case. Party time, how often do you get to replace an apps architecture twice in one PR
.
Here's ArticleScreen after replacing ViewModel with a new arch.
https://github.com/android/compose-samples/blob/19b5e1cc163b1e4b4f2cba850cddf07766eaa019/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt#L73

Here's ArticleScreen after replacing ViewModel with a new arch.
https://github.com/android/compose-samples/blob/19b5e1cc163b1e4b4f2cba850cddf07766eaa019/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt#L73
And that's it, by following unidirectional data flow in the compose portions of JetNews all we had to change was the stateful composable.
This commit was a complete refactor of the app's architecture and the total change was one composable per screen. https://github.com/android/compose-samples/commit/19b5e1cc163b1e4b4f2cba850cddf07766eaa019
This commit was a complete refactor of the app's architecture and the total change was one composable per screen. https://github.com/android/compose-samples/commit/19b5e1cc163b1e4b4f2cba850cddf07766eaa019
Compose works with your apps architecture – and it also creates new opportunities to architect your UI code to improve reusability, testing, and avoid complexity.
To learn more check the codelab Using State in Jetpack Compose.
https://developer.android.com/codelabs/jetpack-compose-state
To learn more check the codelab Using State in Jetpack Compose.
