layered structure helps design of mobile application • However, ◦ there’s no specific definition of components in each layer ◦ the diagram in previous page just shows one example of best practices 9 Clean Architecture is not a pattern
architecture: UI - Domain - Data Layer • SOLID principle, especially Single Responsibility Principle • However, it's not an implementation of Clean Architecture 11
Redux), MVP, … ◦ Not because they are not important • The guide doesn’t intend to cover every patterns and architecture that make sense in mobile design ◦ It’s curated collection of best practices and recommendations for most common use cases 12
problems caused differences between UI and Data layer. ◦ Even when you don’t need to implement domain “layer”, you need to consider about the mediator • When you focus UI layer too much, ◦ Screen-centric design: it makes difficult deal with logics beyond screen ◦ Agile side effect: elements of screen became backlog / features; then class • When you focus data layer too much, ◦ Data schema affects domain / UI layer directly → typical antipattern 14
(Mark V will come out eventually) • Warmer plate keeps the pot warm for extended time a. Put coffee grounds into the filter and slide the filter in b. Pour water in the water strainer and press Brew button c. Water is heated until boiling d. The pressure of the steam forces the water to be sprayed over the coffee grounds e. Coffee drips through the filter into the pot 17 Source: https://cleancoders.com/episode/clean-code-episode-15 Image source: https://www.cafeappliances.com/
series of coffee makers (Mark V will come out eventually) • Warmer plate keeps the pot warm for extended time a. Put coffee grounds into the filter and slide the filter in b. Pour water in the water strainer and press Brew button c. Water is heated until boiling d. The pressure of the steam forces the water to be sprayed over the coffee grounds e. Coffee drips through the filter into the pot
design • Instead, abstracting key actions and actors is more important • You can design mediator like the following: ◦ 1. DDD-ish design ◦ 1-1. Use Case (Transaction Script, which is anti-pattern in DDD) ◦ 2. non-domain mediator layer eg. Gateway, Mapper, Data Controller, Translator, … ◦ 3. put them into Data Layer: Repository 23
◦ It violates Single Responsibility Principle?! • Repository provides abstraction to be called from domain (or UI) layer ◦ It hides details of data saving / loading 25
• Liskov Substitution Principle (LSP) ◦ When replacing a subclass with another, its behavior shouldn’t be changed ▪ REST ↔ gRPC ▪ Room ↔ other ORM ▪ Real impl. ↔ Fake 26
operations like CRUD, you may not need both of Repository and Data Source • Consider a separated life cycle in Repository • Who should be the Single Source of Truth in your app? 27
minimal features to help developers implement general VM ◦ Mechanism to store state safely ▪ Safe from configuration change (by default) ▪ Safe from process kill (thru SavedStateHandler) ◦ Coroutine Scope ◦ Dependency Injection via Dagger Hilt ◦ Helper for Jetpack Navigation 29
completely your job to make VM VM-like ⇒ Adopting AAC VM doesn’t automatically mean you built a good MVVM architecture. ◦ it may not appropriate for some use cases ◦ sometimes implementation of VM seems too verbose 30
own! ◦ And AAC ViewModel source code will inspire you about how to implement Saved State Handler, its own Life Cycle, Coroutines Scope, Navigation, etc. 31
(1) View event occurred ◦ (2) ViewModel deal with it and then modify the state ◦ (3) View observes changed state, then show it to the screen 32 override fun onViewCreated(...) { // (1) binding.plusButton.setOnClickListener { _ -> viewModel.incrementCounter() } // (3) viewModel.counter.observe(viewLifeCycleOwner) { binding.plusButton.text = "Count: $it" } } class CounterViewModel { private val _counter = MutableStateFlow(0) val counter: StateFlow get() = _counter.asStateFlow() fun incrementCounter() { // (2) _counter.value += 1 // something aync... } }
many cases ◦ Interaction between View and Presenter is done by method call ◦ Declarative View can’t change the screen by one method call (without help from additional mediator) • ViewModel seems to make more sense, but.. does it? 34
me? ◦ rememberSavable: the safe state storage provided by AAC ViewModel is now possible in Composable function ◦ If repository in Data layer has independent life cycle from View, ▪ Is it necessary to yield state store to ViewModel? ▪ Some cases it works well without VM (if domain or data layer containes sufficient business logic) 35
1. It changes snapshot state in background scheduler → Crash! ◦ 2. Async task in constructor: The async block in coroutineScope.launch could be finished after the construction was completed → hard to debug when error occurred → make it hard to write test code ◦ 3. Violating Single Responsibility Principle: The main responsibility of constructor is a short and simple instantiation of fields. 38
◦ ViewModelScope uses Dispatchers.Main.immediate ▪ instead, you can now use custom CoroutineScope https://developer.android.com/jetpack/androidx/release s/lifecycle#2.5.0 (addClosable(), new constructor of VM) 42
: Closeable, CoroutineScope { override val coroutineContext: CoroutineContext = context override fun close() { coroutineContext.cancel() } } class MyViewModel( val customScope: CloseableCoroutineScope = CloseableCoroutineScope() ) : ViewModel(customScope) { // You can now use customScope in the same way as viewModelScope }
◦ Upside and downside of Dagger Hilt ◦ Alternatives • Multimodule ◦ Large scale modular architecture ◦ When is the best timing to separate modules? ◦ Why does my multi module project build so slowly? 44