Slide 1

Slide 1 text

Architectural Patterns for Large-Scale Jetpack Compose Apps. Ahmedabad

Slide 2

Slide 2 text

Ahmedabad Who Am I? 󰟲 ● Android Developer ● Technical Author in DroidCon Academy ● Technical Blogger : Medium @bhoomigadhiya ● Technical Speaker in community ● Empowering Women In Tech

Slide 3

Slide 3 text

Architecture Patterns Jetpack Compose Large-Scale apps

Slide 4

Slide 4 text

Jetpack Compose Ahmedabad

Slide 5

Slide 5 text

Jetpack Compose Features ● Declarative UI ● Reactive UI ● Kotlin-Based ● State Management

Slide 6

Slide 6 text

What About The Real-World? Ahmedabad

Slide 7

Slide 7 text

Reference: developer.android.com/jetpack/compose/adopt

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

What is Architectural Pattern? Ahmedabad

Slide 10

Slide 10 text

Architectural Patterns Separation Of Concern No Hard Dependency

Slide 11

Slide 11 text

Architectural Patterns ● MVC ● MVP ● MVVM ● MVI ● And many more... On Column Title

Slide 12

Slide 12 text

MVC (Model-View-Controller) Model View Controller

Slide 13

Slide 13 text

Model View Presenter Interface MVP (Model-View-Presenter)

Slide 14

Slide 14 text

ViewModel Model MVVM (Model-View-ViewModel) View Observables

Slide 15

Slide 15 text

View ViewModel Model

Slide 16

Slide 16 text

MVVM ● View Notifies the ViewModel about different actions ● View has reference to the ViewModel but ViewModel has NO information about View! ● The Consumer(View) of the data should know about the producer(ViewModel), but producer doesn’t care, who consumes the data!

Slide 17

Slide 17 text

ViewModel class MyViewModel : ViewModel() { private val _items = MutableLiveData() val items: LiveData get() = _items init { fetchItems() } private fun fetchItems() { viewModelScope.launch { delay(2000) //Network call _items.value = generateItemList() } } }

Slide 18

Slide 18 text

View @Composable fun MyComposable(viewModel: MyViewModel) { val items by viewModel.items.observeAsState(emptyList()) LazyColumn { items.forEach { item -> item { Text(text = item.name) } } } }

Slide 19

Slide 19 text

MVVM class MyViewModel : ViewModel() { // Different LiveData for representing different UI states private val _isLoading = MutableLiveData() val isLoading: LiveData get() = _isLoading private val _contentData = MutableLiveData() val contentData: LiveData get() = _contentData private val _errorData = MutableLiveData() val errorData: LiveData get() = _errorData }

Slide 20

Slide 20 text

MVI (Model-View-Intent) Model Intent View User triggers an action Send Intent to Model View observes State Display States

Slide 21

Slide 21 text

sealed class MyViewState { object Loading(val isLoading: Boolean) : MyViewState() data class Content(val contentData: String) : MyViewState() data class Error(val errorData: String) : MyViewState() } MVI (Model-View-Intent)

Slide 22

Slide 22 text

Architectural Pattern + Jetpack Compose Ahmedabad

Slide 23

Slide 23 text

MVC + Jetpack Compose

Slide 24

Slide 24 text

MVP + Jetpack Compose

Slide 25

Slide 25 text

MVVM + Jetpack Compose

Slide 26

Slide 26 text

MVVM + Compose ● Reactive and declarative UI ● Jetpack integration ● State Management ● Easy migration

Slide 27

Slide 27 text

MVI + Jetpack Compose

Slide 28

Slide 28 text

MVI + Compose ● Unidirectional Data Flow ● Immutable State ● Reactive and declarative UI

Slide 29

Slide 29 text

Best for Large-scale app?

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

Problems while implementing these patterns Ahmedabad

Slide 32

Slide 32 text

Problems ● Unnecessary Recomposition

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

Use remember class MyViewModel : ViewModel() { private val _cachedResult = mutableStateOf("Initial Value") val cachedResult: State get() = _cachedResult fun heavyCalculation() { val result = //some heavy operation _cachedResult.value = result } }

Slide 35

Slide 35 text

Use remember class MyViewModel : ViewModel() { private val _cachedResult = mutableStateOf("Initial Value") val cachedResult: State get() = _cachedResult fun heavyCalculation() { val result = //some heavy operation _cachedResult.value = result } }

Slide 36

Slide 36 text

Use remember @Composable fun MyComposable(viewModel: MyViewModel) { val cachedResult by remember { viewModel.cachedResult } Column{ Button(onClick = { viewModel.heavyCalculation() }) { Text("Calculate Result") } Text(text = "Cached Result: $cachedResult") } }

Slide 37

Slide 37 text

Use remember @Composable fun MyComposable(viewModel: MyViewModel) { val cachedResult by remember { viewModel.cachedResult } Column{ Button(onClick = { viewModel.heavyCalculation() }) { Text("Calculate Result") } Text(text = "Cached Result: $cachedResult") } }

Slide 38

Slide 38 text

Use Side Effects class MyViewModel : ViewModel() { private val _result = MutableLiveData() val result: LiveData get() = _result fun performAsyncOperation() { viewModelScope.launch { // Asynchronous operation delay(2000) _result.value = "Async operation completed: ${System.currentTimeMillis()}" } } }

Slide 39

Slide 39 text

Use Side Effects class MyViewModel : ViewModel() { private val _result = MutableLiveData() val result: LiveData get() = _result fun performAsyncOperation() { viewModelScope.launch { // Asynchronous operation delay(2000) _result.value = "Async operation completed: ${System.currentTimeMillis()}" } } }

Slide 40

Slide 40 text

Use Side Effects @Composable fun MyComposable(viewModel: MyViewModel) { val result by viewModel.result.observeAsState() // Trigger asynchronous operation when needed LaunchedEffect(result) { viewModel.performAsyncOperation() } Text(text = result /: "Loading//.") }

Slide 41

Slide 41 text

Use Side Effects @Composable fun MyComposable(viewModel: MyViewModel) { val result by viewModel.result.observeAsState() // Trigger asynchronous operation when needed LaunchedEffect(result) { viewModel.performAsyncOperation() } Text(text = result /: "Loading//.") }

Slide 42

Slide 42 text

Lazy Loading Without Key LazyColumn { items(items.size) { //content } } LazyColumn { items(items.size, key = { it }) { //content } } Lazy Loading With Key

Slide 43

Slide 43 text

Lazy Loading Without Key LazyColumn { items(items.size) { //content } } LazyColumn { items(items.size, key = { it }) { //content } } Lazy Loading With Key

Slide 44

Slide 44 text

Problems ● Unnecessary Recomposition ● State hoisting issues

Slide 45

Slide 45 text

Where to hoist the state?

Slide 46

Slide 46 text

For UI-Specific State: @Composable fun MyComposable() { var isExpanded by remember { mutableStateOf(false) } // Use isExpanded locally within this composable }

Slide 47

Slide 47 text

For Business Logic & Data Management: class MyViewModel : ViewModel() { private val _items = mutableStateOf(emptyList()) val items: State get() = _items // Business logic to update items fun updateItems(newItems: List) { _items.value = newItems } }

Slide 48

Slide 48 text

Problems ● Unnecessary Recomposition ● State hoisting issues ● Inefficient state management

Slide 49

Slide 49 text

@Composable fun MyComposable(viewModel: MyViewModel) { val items by remember { mutableStateOf(listOf( //items ))} LazyColumn { //show the items } Button { //Add the item } } Avoid using MutableStateOf

Slide 50

Slide 50 text

@Composable fun MyComposable(viewModel: MyViewModel) { val items by remember { mutableStateOf(listOf( //items ))} LazyColumn { //show the items } Button { //Add the item } } Avoid using MutableStateOf

Slide 51

Slide 51 text

@Composable fun MyComposable(viewModel: MyViewModel) { val items by viewModel.items.collectAsState() // Observe state changes LazyColumn { //show the items } Button { //Add the item } } Instead, use ViewModel

Slide 52

Slide 52 text

@Composable fun MyComposable(viewModel: MyViewModel) { val items by viewModel.items.collectAsState() // Observe state changes LazyColumn { //show the items } Button { //Add the item } } Instead, use ViewModel

Slide 53

Slide 53 text

class MyViewModel : ViewModel() { val item = MutableLiveData(MyItem(1,"Item 1")) ////. } @Composable fun MyComposable(viewModel: MyViewModel) { val item = viewModel.item //Direct access to LiveData Text(item.value/!.name) //Accessing LiveData value directly ////. } Avoid direct ViewModel access from Composable

Slide 54

Slide 54 text

class MyViewModel : ViewModel() { val item = MutableLiveData(MyItem(1,"Item 1")) ////. } @Composable fun MyComposable(viewModel: MyViewModel) { val item = viewModel.item //Direct access to LiveData Text(item.value/!.name) //Accessing LiveData value directly ////. } Avoid direct ViewModel access from Composable

Slide 55

Slide 55 text

class MyViewModel : ViewModel() { private val _item = MutableLiveData(MyItem(1,"Item 1")) val item : LiveData get() = _item } @Composable fun MyComposable(viewModel: MyViewModel) { val item by viewModel.item.observeAsState() // Observe state changes Text(item.name) } Instead, observe state changes

Slide 56

Slide 56 text

class MyViewModel : ViewModel() { private val _item = MutableLiveData(MyItem(1,"Item 1")) val item : LiveData get() = _item } @Composable fun MyComposable(viewModel: MyViewModel) { val item by viewModel.item.observeAsState() // Observe state changes Text(item.name) } Instead, observe state changes

Slide 57

Slide 57 text

The choice is yours!

Slide 58

Slide 58 text

Ahmedabad Resources ● https://developer.android.com/jetpack/compose/performance/be stpractices ● https://www.youtube.com/watch?v=cnU2zMnmmpg ● https://www.youtube.com/watch?v=97BRLkicQd0 ● https://speakerdeck.com/bkinya/modern-android-development-us ing-mvi-architecture ● https://speakerdeck.com/lupsyn/mvvm-compose-utp-a-killer-com bination-for-successful-deliveries

Slide 59

Slide 59 text

Ahmedabad Thank you!

Slide 60

Slide 60 text

@bhoomigadhiya