Il 0% ha trovato utile questo documento (0 voti)
3 visualizzazioni51 pagine

Android LAB03 - ViewModel, DataStore, Dependency Injection

Il documento tratta dell'architettura delle app Compose, focalizzandosi su elementi come ViewModel, DataStore e Dependency Injection. Viene presentato un laboratorio pratico che include la creazione di un'app per gestire temi e colori, l'implementazione di un ViewModel per la gestione dello stato e l'uso di DataStore per il salvataggio delle preferenze. Inoltre, si discute l'importanza della separazione delle preoccupazioni nel codice per facilitare la manutenzione e i test.

Caricato da

AlionEmini
Copyright
© All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Il 0% ha trovato utile questo documento (0 voti)
3 visualizzazioni51 pagine

Android LAB03 - ViewModel, DataStore, Dependency Injection

Il documento tratta dell'architettura delle app Compose, focalizzandosi su elementi come ViewModel, DataStore e Dependency Injection. Viene presentato un laboratorio pratico che include la creazione di un'app per gestire temi e colori, l'implementazione di un ViewModel per la gestione dello stato e l'uso di DataStore per il salvataggio delle preferenze. Inoltre, si discute l'importanza della separazione delle preoccupazioni nel codice per facilitare la manutenzione e i test.

Caricato da

AlionEmini
Copyright
© All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd

ViewModel , DataStore, Dependency Injection

Architettura e stato delle app Compose

Laboratorio 3
Programmazione di Sistemi Mobile – AA 2025/2026 1
Architettura di un’app Compose

Definiscono gli elementi dell’interfaccia

Tengono traccia dello stato dell’app

Definiscono parti riutilizzabili di business logic

Modellano i dati e la business logic

Fanno da interfaccia tra l’app e il sistema


operativo per le operazioni sui dati
= ha un riferimento a

Programmazione di Sistemi Mobile – AA 2025/2026 2


Laboratorio di oggi
• Esercitazione guidata
1. Creazione di una semplice app per cambiare il tema e la palette di
colori
2. Aggiunta all’app di un ViewModel per gestire lo stato dell’interfaccia
3. Salvataggio dell’opzione selezionata tramite DataStore
• Con dependency injection
• Esercitazione libera
4. Aggiunta all’app TravelDiary di ViewModel, DataStore e dependency
injection

Programmazione di Sistemi Mobile – AA 2025/2026 3


1. ThemeToggle
• Creiamo una semplice applicazione che
permetta, tramite dei radio button, di:
– Selezionare il tema tra chiaro, scuro e di
sistema
– Selezionare la palette di colori tra quella di
sistema e quella custom definita dall'app
• Step
1. Importazione progetto di partenza
2. Enum per rappresentare i tre temi
3. Variabili per memorizzare lo stato
4. Applicazione del tema e della palette
5. Fix vari

Programmazione di Sistemi Mobile – AA 2025/2026 4


1. ThemeToggle

Palette definita
dal sistema Android

Palette definita
dall'app in [Link]

Programmazione di Sistemi Mobile – AA 2025/2026 5


1.1. Struttura progetto
• Scaricare [Link] dal materiale del laboratorio 3 su
Virtuale
• Estrarre lo zip e aprire il progetto su Android Studio
• Risultato:

Programmazione di Sistemi Mobile – AA 2025/2026 6


1.2. Enum per rappresentare i tre temi
• Possiamo utilizzare un enum per definire i tre temi selezionabili

enum class Theme { Light, Dark, System }

Programmazione di Sistemi Mobile – AA 2025/2026 7


1.3. Variabili per memorizzare lo stato
var selectedTheme by remember { mutableStateOf([Link]) }
var dynamicColor by remember { mutableStateOf(true) }

• Tramite remember l’app non perde traccia del tema in seguito


alle recomposition
• Tramite mutableStateOf l’app fa scattare una recomposition
al cambiare del valore di selectedTheme (stessa cosa per
dynamicColor)
• Scegliamo come default il tema di sistema e il colore di accento
dinamico

Programmazione di Sistemi Mobile – AA 2025/2026 8


1.4. Applicazione del tema
• In ThemeScreen, accettiamo come parametri theme,
dynamicColor e le callback da richiamare alla loro modifica
Scaffold(/* ... */) { innerPadding ->
Column([Link](innerPadding).selectableGroup()) {
Text(/* ... */)
[Link] { theme ->
RadioListItem(
@Composable label = [Link](),
fun ThemeScreen( selected = (theme == selectedTheme),
selectedTheme: Theme, onClick = { onThemeChange(theme) },
onThemeChange: (Theme) -> Unit, )
dynamicColor: Boolean, }
onDynamicColorChange: (Boolean) -> Unit Text(/* ... */)
) { /* ... */ } listOf(true, false).forEach { dynamicColorEnabled ->
RadioListItem(
label = if (dynamicColorEnabled) "System colors" else "Custom colors",
selected = (dynamicColorEnabled == dynamicColor),
onClick = { onDynamicColorChange(dynamicColorEnabled) },
)
}
}
}
Programmazione di Sistemi Mobile –}AA 2025/2026 9
1.4. Applicazione del tema
• Nella MainActivity:
– Impostiamo i parametri darkTheme e dynamicColor del composable
che definisce il tema
– Aggiorniamo la chiamata a ThemeScreen
ThemeToggleTheme(
darkTheme = when (selectedTheme) {
[Link] -> false
[Link] -> true
[Link] -> isSystemInDarkTheme()
},
dynamicColor = dynamicColor ThemeScreen(
) {
selectedTheme = selectedTheme,
// ...
onThemeChange = { selectedTheme = it },
}
dynamicColor = dynamicColor,
onDynamicColorChange = { dynamicColor = it }
Programmazione di Sistemi Mobile – AA 2025/2026 ) 10
1.5. Fix per i cambi di configurazione
• C'è un problema: in seguito a un cambio di configurazione, il
tema e la palette vengono resettati ai valori di default
– Ad esempio se ruotiamo il dispositivo
• Questo perché il valore memorizzato da remember sopravvive
alle recomposition, ma non ai cambi di configurazione
• Utilizzando rememberSaveable possiamo gestire anche
questa casistica
var selectedTheme by rememberSaveable { mutableStateOf([Link]) }
var dynamicColor by rememberSaveable { mutableStateOf(true) }

Programmazione di Sistemi Mobile – AA 2025/2026 11


1.5. Fix per il colore della status bar
• Abbiamo un altro problema: il contenuto della status bar diventa
bianco se impostiamo il tema scuro a livello di sistema, mentre
rimane nero se lo facciamo tramite l'app
• Soluzione: impostare il colore manualmente in seguito ad ogni
recomposition
[Link] - ThemeToggleTheme Le icone sono
poco visibili
val view = [Link]
if (![Link]) {
SideEffect {
val window = ([Link] as Activity).window
[Link] = [Link]()
WindowCompat
.getInsetsController(window, view)
.isAppearanceLightStatusBars = !darkTheme
}
}

Programmazione di Sistemi Mobile – AA 2025/2026 12


1.5. Fix per il colore della status bar

[Link] - ThemeToggleTheme
val view = [Link]
// Salta l'operazione se stiamo visualizzando il componente tramite dei devtools (es. preview)
if (![Link]) {
SideEffect { // Esegue il blocco al termine di ogni recomposition
val window = ([Link] as Activity).window
// Cambio del colore della status bar per Android <= 14
[Link] = [Link]()
// Cambio del colore della status bar per Android 15+
WindowCompat
.getInsetsController(window, view)
.isAppearanceLightStatusBars = !darkTheme
}
}

Programmazione di Sistemi Mobile – AA 2025/2026 13


2. Aggiunta ViewModel
• L’app che abbiamo creato è semplice e
funzionale
• Tuttavia, con l’implementazione di nuove
feature sarà sempre più complicata da
manutenere e testare
– Interfaccia, stato e dati coesistono nello stesso file,
all’interno delle stesse funzioni
• Soluzione: definiamo una struttura con una
migliore separation of concerns
– Il primo passaggio è l’aggiunta di un ViewModel

Programmazione di Sistemi Mobile – AA 2025/2026 14


Recap: ViewModel
• Incapsula ed espone lo stato dell’interfaccia utente di una
parte dell’app (es. un’Activity, uno screen, ...)
• Espone metodi per modificare lo stato
• Sopravvive alle recomposition e ai cambi di configurazione del
dispositivo (es. rotazione)
• Il suo ciclo di vita è legato automaticamente a quello del suo
owner (es. un’Activity, una parte del grafo di navigazione, ...)

Programmazione di Sistemi Mobile – AA 2025/2026 15


Recap: StateFlow
• È un observable con una singola proprietà value, che può
essere modificata facendo scattare un’aggiornamento a tutti i
subscriber
• È uno dei metodi più indicati per tracciare i cambiamenti di
stato all’interno di un ViewModel
• Fornisce vari operatori per l’aggiornamento di value, tutti
atomici e utilizzabili in contesti concorrenti e multi-thread

Programmazione di Sistemi Mobile – AA 2025/2026 16


2. Aggiunta ViewModel
• Step
1. Installazione dipendenze
2. Modifica della struttura del progetto
3. Creazione classe ViewModel
4. Modifica della schermata dell’app
5. Creazione istanza ViewModel

Programmazione di Sistemi Mobile – AA 2025/2026 17


2.1. Installazione dipendenze
• Aggiungere le seguenti dipendenze al file [Link]
(modulo :app)
implementation("[Link]:lifecycle-viewmodel-compose:2.10.0")
implementation("[Link]:lifecycle-runtime-compose:2.10.0")

Replace with new library catalog


declaration, reusing version
variable lifecycleRuntimeKtx

Programmazione di Sistemi Mobile – AA 2025/2026 18


2.2. Struttura progetto

Spostiamo l'enum
Theme in un file
dedicato

Creiamo
ThemeViewModel

Programmazione di Sistemi Mobile – AA 2025/2026 19


2.3. Creazione classe ViewModel
[Link]
data class ThemeState( • È spesso conveniente raggruppare
val theme: Theme,
val dynamicColor: Boolean lo stato del ViewModel in una
)
classe State e le azioni di modifica in
data class ThemeActions(
val setTheme: (Theme) -> Unit,
una classe Actions
)
val setDynamicColor: (Boolean) -> Unit • Utilizziamo un MutableStateFlow
come backing property per lo stato,
class ThemeViewModel : ViewModel() {
private val _state = così che possa essere modificato
MutableStateFlow(ThemeState([Link], true))
val state = _state.asStateFlow()
internamente al ViewModel,
val actions = ThemeActions(
mentre esponiamo uno StateFlow
setTheme = { theme -> non modificabile esternamente
_state.update { [Link](theme = theme) }
},
setDynamicColor = { enabled ->
• Esponiamo le funzioni setTheme e
_state.update { [Link](dynamicColor = enabled) } setDynamicColor per modificare
)
}
lo stato
}

Programmazione di Sistemi Mobile – AA 2025/2026 20


2.4. Modifica della schermata dell’app
• Possiamo semplificare ThemeScreen, accettando come
parametri lo stato dell'interfaccia e le azioni per modificarlo
Scaffold(/* ... */) { innerPadding ->
Column([Link](innerPadding).selectableGroup()) {
Text(/* ... */)
[Link] { theme ->
RadioListItem(
label = [Link](),
@Composable selected = (theme == [Link]),
fun ThemeScreen( onClick = { [Link](theme) },
themeState: ThemeState, )
themeActions: ThemeActions }
) { /* ... */ } Text(/* ... */)
listOf(true, false).forEach { dynamicColorEnabled ->
RadioListItem(
label = if (dynamicColorEnabled) "System colors" else "Custom colors",
selected = (dynamicColorEnabled == [Link]),
onClick = { [Link](dynamicColorEnabled) },
)
}
}
}
Programmazione di Sistemi Mobile –}AA 2025/2026 21
2.5. Creazione istanza ViewModel
[Link]
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
[Link](savedInstanceState)
enableEdgeToEdge()
setContent {
val themeViewModel = viewModel<ThemeViewModel>()
val themeState by [Link]()

ThemeToggleTheme(
darkTheme = when ([Link]) {
[Link] -> false
[Link] -> true
[Link] -> isSystemInDarkTheme()
},
dynamicColor = [Link]
) {
ThemeScreen(themeState, [Link])
}
}
}
}

Programmazione di Sistemi Mobile – AA 2025/2026 22


2.5. Creazione istanza ViewModel
[Link]
Creiamo l’istanza tramite
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { l’helper viewModel()
[Link](savedInstanceState)
enableEdgeToEdge()
setContent {
val themeViewModel = viewModel<ThemeViewModel>()
val themeState by [Link]() Convertiamo lo StateFlow in
State
ThemeToggleTheme(
darkTheme = when ([Link]) {
[Link] -> false
[Link] -> true
[Link] -> isSystemInDarkTheme()
},
dynamicColor = [Link] Passiamo lo state alla
) { schermata, assieme alle azioni
ThemeScreen(themeState, [Link]) per modificarlo
}
}
}
}

Programmazione di Sistemi Mobile – AA 2025/2026 23


3. Aggiunta Repository con DataStore
• All’app che abbiamo creato manca una
feature importante: il tema selezionato viene
salvato solo in memory
– Quindi va perso alla chiusura dell’app!
• Aggiungiamo un data layer tramite cui
memorizzare il tema sul dispositivo
– Tramite DataStore

Programmazione di Sistemi Mobile – AA 2025/2026 24


Recap: DataStore
• È una soluzione per lo storage di dati che permette di salvare
coppie chiave-valore o oggetti tipizzati
• È perfettamente integrato nell’ecosistema di Compose,
utilizzando i Flow e le coroutine di Kotlin
• Fornsce due API:
– Preferences DataStore: per il salvataggio di coppie chiave-valore (lo
utilizzeremo ora)
– Proto DataStore: per memorizzare tipi di dati custom

Programmazione di Sistemi Mobile – AA 2025/2026 25


3. Aggiunta DataStore
• Step - DataStore
1. Installazione dipendenze per DataStore
2. Creazione repository
3. Aggiunta della repository al ViewModel
• Step - Dependency Injection
4. Installazione dipendenze per dependency injection con Koin
5. Creazione AppModule
6. Avvio di Koin
7. Creazione del ViewModel tramite Koin

Programmazione di Sistemi Mobile – AA 2025/2026 26


3.1. Installazione dipendenze per DataStore
• Aggiungere la seguente dipendenza al file [Link]
(modulo :app)
implementation("[Link]:datastore-preferences:1.1.3")

Replace with new library


catalog declaration

Programmazione di Sistemi Mobile – AA 2025/2026 27


3.2. Creazione repository
[Link]
class ThemeRepository(
private val dataStore: DataStore<Preferences>
) {
companion object { Accettiamo il DataStore
private val THEME_KEY = stringPreferencesKey("theme") come parametro
private val DYNAMIC_COLOR_KEY = booleanPreferencesKey("dynamicColor")
}

val theme = [Link] { preferences -> Definiamo le chiavi con cui


try { salvare le due variabili nel
[Link](preferences[THEME_KEY] ?: "System")
} catch (_: Exception) {
DataStore
[Link]
}
}

val dynamicColor = [Link] { preferences ->


preferences[DYNAMIC_COLOR_KEY] ?: false
}

suspend fun setTheme(theme: Theme) = [Link] { preferences ->


preferences[THEME_KEY] = [Link]()
}

suspend fun setDynamicColor(enabled: Boolean) = [Link] { preferences ->


preferences[DYNAMIC_COLOR_KEY] = enabled
}
}

Programmazione di Sistemi Mobile – AA 2025/2026 28


3.2. Creazione repository
[Link]
class ThemeRepository(
private val dataStore: DataStore<Preferences>
) {
companion object {
private val THEME_KEY = stringPreferencesKey("theme")
private val DYNAMIC_COLOR_KEY = booleanPreferencesKey("dynamicColor") Sottoscriviamo al Flow
}
del DataStore
val theme = [Link] { preferences ->
try {
[Link](preferences[THEME_KEY] ?: "System") Convertiamo la stringa del
} catch (_: Exception) {
[Link] tema in enum
}
}
Se la conversione produce
val dynamicColor = [Link] { preferences ->
preferences[DYNAMIC_COLOR_KEY] ?: false errori (es. la stringa non
} corrisponde a un valore
suspend fun setTheme(theme: Theme) = [Link] { preferences ->
dell’enum) utilizziamo il
preferences[THEME_KEY] = [Link]() tema di sistema
}

suspend fun setDynamicColor(enabled: Boolean) = [Link] { preferences ->


preferences[DYNAMIC_COLOR_KEY] = enabled
}
}

Programmazione di Sistemi Mobile – AA 2025/2026 29


3.2. Creazione repository
[Link]
class ThemeRepository(
private val dataStore: DataStore<Preferences>
) {
companion object {
private val THEME_KEY = stringPreferencesKey("theme")
private val DYNAMIC_COLOR_KEY = booleanPreferencesKey("dynamicColor")
}

val theme = [Link] { preferences ->


try {
[Link](preferences[THEME_KEY] ?: "System")
} catch (_: Exception) {
[Link]
}
Definiamo una suspend
} function per modificare il
val dynamicColor = [Link] { preferences ->
valore nello store da una
preferences[DYNAMIC_COLOR_KEY] ?: false coroutine
}

suspend fun setTheme(theme: Theme) = [Link] { preferences ->


preferences[THEME_KEY] = [Link]()
}

suspend fun setDynamicColor(enabled: Boolean) = [Link] { preferences ->


preferences[DYNAMIC_COLOR_KEY] = enabled
}
}

Programmazione di Sistemi Mobile – AA 2025/2026 30


3.3. Aggiunta repository al ViewModel
• Modifichiamo il ViewModel per leggere e scrivere i dati dalla
repository
class ThemeViewModel(repository: ThemeRepository) : ViewModel() {
val state = combine(
[Link],
[Link]
) { theme, dynamicColor -> ThemeState(theme, dynamicColor) }
.stateIn(
scope = viewModelScope,
started = [Link](),
initialValue = ThemeState([Link], false)
)

val actions = ThemeActions(


setTheme = { theme ->
[Link] { [Link](theme) }
},
setDynamicColor = { enabled ->
[Link] { [Link](enabled) }
}
)
}

Programmazione di Sistemi Mobile – AA 2025/2026 31


3.3. Aggiunta repository al ViewModel
• Modifichiamo il ViewModel per leggere e scrivere i dati dalla
repository Accettiamo la repository
come parametro
class ThemeViewModel(repository: ThemeRepository) : ViewModel() {
val state = combine(
[Link],
[Link]
) { theme, dynamicColor -> ThemeState(theme, dynamicColor) } Combiniamo i due Flow della
.stateIn( repository in uno
scope = viewModelScope,
started = [Link](), StateFlow di ThemeState
initialValue = ThemeState([Link], false)
)

val actions = ThemeActions(


setTheme = { theme -> Utilizziamo delle coroutine
[Link] { [Link](theme) } nello scope del ViewModel
},
setDynamicColor = { enabled ->
per modificare il tema nella
[Link] { [Link](enabled) } repository
}
)
}

Programmazione di Sistemi Mobile – AA 2025/2026 32


Dependency injection
• Problema: come passiamo i parametri necessari ai costruttori
di ViewModel (che necessita la repository) e repository (a cui
serve il DataStore)?
• Potremmo farlo manualmente, ma:
– Le istanze passate sarebbero dei singleton?
– Sarebbe semplice fare refactoring del codice?
– Sarebbe semplice testare separatamente i vari componenti?
– ...
• Soluzione: dependency injection

Programmazione di Sistemi Mobile – AA 2025/2026 33


Dependency injection
• La dependency injection (DI) è una tecnica molto utilizzata che
fornisce vari vantaggi all'architettura di un sistema, tra cui:
– Riusabilità del codice
– Facilità di refactoring
– Facilità di testing

Programmazione di Sistemi Mobile – AA 2025/2026 34


Gestione delle dipendenze
• Le classi spesso richiedono riferimenti ad altre classi. Per esempio,
una classe Car potrebbe aver bisogno di un riferimento a una
classe Engine. Queste classi necessarie sono chiamate dipendenze
e, in questo esempio, la classe Car dipende dalla presenza di
un'istanza della classe Engine per funzionare
• Una classe può ottenere un oggetto di cui ha bisogno in tre modi:
a. La classe stessa crea un’istanza della dipendenza di cui ha bisogno
b. La classe ottiene un riferimento alla dipendenza da qualche altra parte,
tramite API come Context e getSystemService
c. La dipendenza viene fornita come parametro. L'applicazione può passare
queste dipendenze quando la classe viene istanziata o passarle ai metodi
della classe che ne hanno bisogno

Programmazione di Sistemi Mobile – AA 2025/2026 35


Gestione delle dipendenze
• Le classi spesso richiedono riferimenti ad altre classi. Per esempio,
una classe Car potrebbe aver bisogno di un riferimento a una
classe Engine. Queste classi necessarie sono chiamate dipendenze
e, in questo esempio, la classe Car dipende dalla presenza di
un'istanza della classe Engine per funzionare
• Una classe può ottenere un oggetto di cui ha bisogno in tre modi:
a. La classe stessa crea un’istanza della dipendenza di cui ha bisogno
b. La classe ottiene un riferimento alla dipendenza da qualche altra parte,
tramite API come Context e getSystemService
c. La dipendenza viene fornita come parametro. L'applicazione può passare
queste dipendenze quando la classe viene istanziata o passarle ai metodi
della classe che ne hanno bisogno
Dependency injection

Programmazione di Sistemi Mobile – AA 2025/2026 36


Senza dependency injection

• Car e Engine sono tightly coupled


• Non possiamo istanziare Car senza creare anche un oggetto
Engine
• Non possiamo quindi testare Car separatamente da Engine

Programmazione di Sistemi Mobile – AA 2025/2026 37


Con dependency injection

• Car e Engine possono essere istanziati e testati separatamente


• In più, tutte le dipendenze del programma sono raggruppate all'interno di
un modulo di dependency injection, semplificandone la gestione
all'aumentare delle dimensioni del sistema

Programmazione di Sistemi Mobile – AA 2025/2026 38


Dependency injection con Koin
• Koin è una libreria open source, semplice e modulare per la
gestione automatica della dependency injection
• Non è specifica per Android, può funzionare praticamente in
qualsiasi progetto Kotlin
• Tramite Koin, è possibile organizzare le dipendenze della nostra app
in moduli
• Ogni modulo può definire varie tipologie di oggetto. Nel nostro
caso utilizzeremo:
– single per la creazione di generici singleton
– viewModel per istanziare appunto i singleton contenenti i ViewModel

Programmazione di Sistemi Mobile – AA 2025/2026 39


DI con Koin - Esercizio di oggi

Programmazione di Sistemi Mobile – AA 2025/2026 40


3.4. Installazione dipendenze per DI
• Aggiungere la seguente dipendenza al file [Link]
(modulo :app)
implementation("[Link]-koin:koin-androidx-compose:4.0.2")

Replace with new library


catalog declaration

Programmazione di Sistemi Mobile – AA 2025/2026 41


3.5. Creazione AppModule
[Link] • Il DataStore va creato come
val [Link] by extension di Context, di
preferencesDataStore("theme") conseguenza dobbiamo
val appModule = module { inizializzarlo top-level
single { get<Context>().dataStore }
• Con la funzione get() possiamo
single { ThemeRepository(get()) } dire a Koin di andare a cercare la
viewModel { ThemeViewModel(get()) }
dipendenza in questione all’interno
} dei suoi moduli
• Ma come fa Koin a reperire
un’istanza di Context?
– Gli viene fornita in fase di avvio
dell’app (vedi prossima slide)

Programmazione di Sistemi Mobile – AA 2025/2026 42


3.6. Avvio di Koin
• La DI di Koin va avviata a livello [Link]
class ThemeToggleApplication : Application() {
globale di applicazione override fun onCreate() {
[Link]()

• Per farlo, possiamo creare una startKoin {


androidLogger()
classe nella root del progetto che androidContext(this@ThemeToggleApplication)
modules(appModule)

eredita da Application e fare }


}

}
override del metodo onCreate
[Link]
• La classe va aggiunta al file <application
manifest android:name=".ThemeToggleApplication"

Programmazione di Sistemi Mobile – AA 2025/2026 43


3.7. Creazione ViewModel tramite Koin
• L’ultimo passaggio è quello di sostituire l’helper viewModel
con quello fornito da Koin per istanziare il ViewModel

val themeViewModel = viewModel<ThemeViewModel>()

val themeViewModel = koinViewModel<ThemeViewModel>()

Programmazione di Sistemi Mobile – AA 2025/2026 44


4. TravelDiary - Struttura avanzata progetto
• Modificare l’architettura dell’app TravelDiary in base a quanto
visto negli esercizi di oggi:
– Configurare la dependency injection con Koin
– Memorizzare lo stato di AddTravelScreen e SettingsScreen in dei
corrispondenti ViewModel:
• AddTravelViewModel
• SettingsViewModel
– Memorizzare l’username della pagina Settings nel DataStore tramite
repository
• SettingsRepository

Programmazione di Sistemi Mobile – AA 2025/2026 45


4. TravelDiary - Struttura avanzata progetto
• Hint: navigation e creazione ViewModel
– Il punto migliore per la creazione del ViewModel di una schermata è
il blocco composable della rotta corrispondente
composable<[Link]> {
val settingsVm = koinViewModel<SettingsViewModel>()
SettingsScreen([Link], settingsVm::setUsername, navController)
}

– Non dimenticate .collectAsStateWithLifecycle() se lo stato del


ViewModel è uno StateFlow (non lo è nell’esempio sopra)

Programmazione di Sistemi Mobile – AA 2025/2026 46


4. TravelDiary - Struttura avanzata progetto
• Hint: ViewModel con molti metodi
– In base all’implementazione, AddTravelViewModel potrebbe
contenere vari metodi, ad esempio: setDestination, setDate,
setDescription
– In questi casi, può essere una buona idea raggruppare tutti i metodi
in un oggetto actions all’interno del ViewModel, così da non doverli
passare individualmente dal ViewModel allo screen
[Link] [Link]
interface AddTravelActions { AddTravelScreen(
fun setDestination(title: String) state,
fun setDate(date: String) [Link],
fun setDescription(description: String) navController
} )

Programmazione di Sistemi Mobile – AA 2025/2026 47


4. TravelDiary - Struttura avanzata progetto
• Hint: TextField e Flow (schermata Settings)
– Modificare il valore contenuto in un TextField mentre l’utente sta
scrivendo resetta la posizione del cursore, creando una bad user
experience
– Per questo motivo, il valore di un TextField non dovrebbe mai
provenire da un Flow, che può emettere nuovi valori in qualsiasi
momento

Programmazione di Sistemi Mobile – AA 2025/2026 48


4. TravelDiary - Struttura avanzata progetto
• Hint: TextField e Flow (schermata Settings) pt. 2
– Come facciamo allora a sincronizzare il valore di un TextField con il DataStore?
• Alla creazione del ViewModel, leggiamo il valore presente nel DataStore e lo salviamo in una
variabile non Flow (es. mutableStateOf)
• Ogni volta che il valore della variabile viene modificato, lo scriviamo nel DataStore
– Come eseguiamo codice alla creazione del ViewModel?
• Con il blocco init di Kotlin
class SettingsViewModel (private val repository: SettingsRepository) : ViewModel() {

// ...

init {
println("SettingsViewModel created")
}
}

Programmazione di Sistemi Mobile – AA 2025/2026 49


Tocca a voi!

Programmazione di Sistemi Mobile – AA 2025/2026 50


Riferimenti
• State in Compose
[Link]
• ViewModel
[Link]
• StateFlow
[Link]
• Dependency Injection
[Link]
• DataStore
[Link]
• Koin su Android
[Link]

Programmazione di Sistemi Mobile – AA 2025/2026 51

Potrebbero piacerti anche