SlideShare ist ein Scribd-Unternehmen logo
1 von 51
Reactive State Management
Using Jetpack Components
Gabor Varadi
@zhuinden
What is the problem we are trying to
solve?
• Save/restore minimal state across process
death
• Execute data loading asynchronously based on
state
– Should be kept across configuration changes
• Minimize „moving parts”
– Mutations should be controlled
– Mutations should be observed
• Save/restore minimal state across process
death
• Execute data loading asynchronously based
on state
– Should be kept across configuration changes
• Minimize „moving parts”
– Mutations should be controlled
– Mutations should be observed
• Save/restore minimal state across process
death
• Execute data loading asynchronously based on
state
– Should be kept across configuration changes
• Minimize „moving parts”
– Mutations should be controlled
– Mutations should be observed
Process death
(aka: how Android apps actually work)
Core App Quality Guidelines
From https://developer.android.com/docs/quality-guidelines/core-app-quality#fn
„When returning to the foreground, the app
must restore the preserved state and any
significant stateful transaction that was
pending, such as changes to editable fields,
game progress, menus, videos, and other
sections of the app.
How to induce process death?
• Step 1: put app in background with HOME
• Step 2: press „Terminate application”
• Step 3: restart app from launcher
For apps you don’t own
https://play.google.com/store/apps/details?id=me.empirical.android.application.fillme
mory&hl=en
Common mistakes
• Assuming that static variables initialized on one
screen (like data loading on a Splash screen) stay
set on the other screen (nope)
• Assuming that if(savedInstanceState == null) {
is true at least once (nope)
• Holding a reference to a Fragment instance
without using findFragmentByTag first (for
example ”ViewPagerAdapter.addFragment()”)
(don’t)
How to detect process death?
if (savedInstanceState != null
&& lastNonConfigurationInstance == null) {
}
Or in BaseActivity:
companion object {
var isRestored: Boolean = false
}
if (savedInstanceState !=null) {
if (!isRestored) {
isRestored = true
// here
}
}
Saving UI states
What needs to be persisted across
process death?
• Navigation state is already managed by the
system on Android out of the box
– Empty ctor + using intent extras / fragment arguments
• Screen state is partially managed by the system
– Views with IDs have their state persisted
– Complex state (f.ex. RecyclerView selection) are not
persisted automatically
– Dynamically added views should be recreatable (!)
What SHOULDN’T be persisted?
• Data
– Bundle has a size limit
– Data should be fetched asynchronously, off the UI
thread
• Transient state
– „Loading” state: computed from progress of side-
effect („something is happening”, but is it really?)
Example for saving/restoring state
(the „old way”)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
selectedSportId = savedInstanceState.getLong("selectedSportId")
selectedPosition = savedInstanceState.getInt("selectedPosition")
selectedTags.clear()
selectedTags.addAll(
savedInstanceState.getStringArrayList("selectedTags"))
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putLong("selectedSportId", selectedSportId)
outState.putInt("selectedPosition", selectedPosition)
outState.putStringArrayList("selectedTags", ArrayList(selectedTags))
}
Loading data
• Asynchronous loading should either begin on
initialization, or when observed
• Data can be loaded via a transformation chain
from the observable source that stores the
state – changes trigger new data load
(switchMap, flatMapLatest)
How can Jetpack help?
• Surviving configuration changes
– ViewModel
• Lifecycle-aware dataloading
– Lifecycle (livedata / lifecycle-ktx)
• Persistent storage (observable queries)
– Room
– DataStore (beta)
– *Paging (beta)
• Background job processing
– WorkManager
• State restoration across process death
– SavedStateRegistry (savedstate)
– SavedStateHandle (viewmodel-savedstate)
• Connecting UI to state model
– Databinding
– Compose (beta)
• Scoping data/state between screens + argument
passing
– Navigation
• Piece together all the things
– Hilt
ViewModel
• Stored across configuration changes in a
ViewModelStore (ComponentActivity, Fragment,
and NavBackStackEntry are ViewModelStoreOwner)
• They have their own lifecycle for when the
ViewModelStore is destroyed (onCleared(),
viewModelScope)
• State and asynchronous operations go here
ViewModel
• Originally:
val viewModel = ViewModelProvider(
viewModelStoreOwner,
viewModelProviderFactory
).get(MyViewModel::class.java)
• KTX (fragment-ktx):
private val viewModel by viewModels<MyViewModel>(
ownerProducer = { viewModelStoreOwner },
factoryProducer = { viewModelProviderFactory },
)
Lifecycle-ktx
• LiveData / MutableLiveData /
MediatorLiveData
• liveData(coroutineContext, timeoutMs) { coroutine
builder (CoroutineLiveData)
• liveData.asFlow() (FlowLiveData)
Also Lifecycle-ktx
• LifecycleOwner.lifecycleScope
(fragment.viewLifecycleOwner.lifecycle
Scope)
• ViewModel.viewModelScope
• LifecycleCoroutineScope.launchWhenStarted,
Lifecycle.whenStateAtLeast
• LifecycleOwner.addRepeatingJob,
Lifecycle.repeatOnLifecycle (alpha)
Room
• Exposes observable queries
– if the database is written to, then the queries are
re-evaluated
• Integrations:
– LiveData (ComputableLiveData)
– Flowable/Observable (RxJava 2.x)
– Flow (kotlinx.coroutines)
WorkManager
• Schedule and eventually execute background jobs
– Constraint support: execute when network is available, etc
– OneTimeWorkRequest, unique work
– Can be used to download data for offline support
• Worker types:
– ListenableWorker
– CoroutineWorker
– RxWorker
SavedState
• SavedStateRegistry allows saving state into a Bundle
– Not commonly used directly, but heavily used by ViewModel-
SavedState (and Jetpack Compose’s
saveableStateHolder.SaveableStateProvider)
• Important methods:
– registerSavedStateProvider(String, SavedStateProvider)
– unregisterSavedStateProvider(String)
– consumeRestoredStateForKey(String): Bundle
ViewModel-SavedState
• SavedStateHandle allows saving ViewModel’s state into a
Bundle
– Provides get(), set(), and most importantly
getLiveData() (SavingStateLiveData)
– Custom types can be saved with setSavedStateProvider()
– Initialized with arguments by default
– Originally created by AbstractSavedStateViewModelFactory
Databinding
• (Prefer ViewBinding if possible)
• Allows directly binding against state via binding
expressions
– one-way bindings (=”@{}”) vs two-way bindings (=”@={}”)
– supports ObservableField / MutableLiveData /
MutableStateFlow
– Can be useful for forms
• Binding adapters to support custom observable
properties from XML in custom views
(Jetpack Compose)
• (Completely new UI framework, beta)
• (Rendering happens by invoking @Composable functions)
• (Changes in function arguments of @Composable functions
are tracked by Compose’s Kotlin compiler plugin)
• (When function arguments change, the functions that depend
on said state get re-evaluated and therefore re-rendered,
„recomposition”)
(Jetpack Compose)
Navigation
• Created with the intention to simplify using 1 Activity for the
whole app
• Theoretically, the single Activity could be
class MainActivity: AppCompatActivity(R.layout.activity_main)
and no further code (see NavHostFragment)
• Track navigation state, handle argument passing
• Allow defining scopes shared between Fragments (see
NavBackStackEntry)
Hilt
• Dependency injection framework written on top of
Dagger
• Simplify injection of Android components via global
configuration and „automatic” injection
• Integration with ViewModel (+ SavedState) and
Navigation (by hiltNavGraphViewModels)
@HiltAndroidApp
class CustomApplication: Application()
@AndroidEntryPoint
class MainActivity: AppCompatActivity(R.layout.main_activity)
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation_graph"/>
</FrameLayout>
@AndroidEntryPoint
class MyFragment: Fragment(R.layout.my_fragment) {
private val viewModel by viewModels<MainViewModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle) {
super.onViewCreated(view, savedInstanceState)
val binding = MyFragmentBinding.bind(view)
val viewModel = viewModel
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.sport.collectLatest { sport ->
// bind views
}
}
}
}
@HiltViewModel
class MyViewModel @Inject constructor(
private val sportDao: SportDao,
private val handle: SavedStateHandle
): ViewModel() {
private val selectedSportId: MutableLiveData<String> =
handle.getLiveData("selectedSportId")
val sport: Flow<Sport> = selectedSportId.asFlow().flatMapLatest { id ->
sportDao.getSport(id)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
}
Reactive State Management
Reactive State Management
• Goal: Minimizing moving parts
– No mutation can happen without being notified
of it
• Data classes shouldn’t have var! (only val)
• Observable data holder shouldn’t have either mutable
classes or mutable collections!
– State synchronization is idempotent
• observe over a data holder should not execute one-off
actions that cause a different effect on re-execution
Don’ts
MutableLiveData<ArrayList<T>>
Flow<ArrayList<T>>
public fun someMethod(): ArrayList<T>
@Parcelize data class SomeClass(
var prop: String
): Parcelable
Do
MutableLiveData<List<T>>,
Flow<List<T>>
public List<T> doSomething() {
ArrayList<T> copy = new ArrayList<T>(original);
…
return Collections.unmodifiableList(copy);
}
@Parcelize data class SomeClass(
val prop: String
): Parcelable
Then
Imagine a spreadsheet with cells of data,
and formulas to combine them
And
private val b1 = savedStateHandle.getLiveData("b1", 1)
private val b2 = savedStateHandle.getLiveData("b2", 2)
private val b3 = savedStateHandle.getLiveData("b3", 3)
private val b4 = savedStateHandle.getLiveData("b4", 4)
private val b5 = savedStateHandle.getLiveData("b5", 5)
private val c3 = combineTuple(
b1.asFlow(),
b2.asFlow(),
b3.asFlow(),
b4.asFlow(),
b5.asFlow(),
).map { (b1, b2, b3, b4, b5) ->
b1 * b2 * b3 * b4 * b5
}.stateIn(…)
Now you can do
// validation
val username: MutableLiveData<String> =
savedStateHandle.getLiveData("username", "")
val password: MutableLiveData<String> =
savedStateHandle.getLiveData("password", "")
val isRegisterAndLoginEnabled : LiveData<Boolean> =
validateBy(
username.map { it.isNotBlank() },
password.map { it.isNotBlank() },
)
Or
// state management
private val currentQuery = savedStateHandle.getLiveData("currentQuery", "")
private val isLoading = MutableStateFlow(false)
private val results: Flow<List<Sport>> = currentQuery.asFlow()
.distinctUntilChanged()
.debounce(125L)
.onEach { query ->
if (query.isNotEmpty()) {
isLoading.value = true
}
}
.flatMapLatest { query ->
when {
query.isEmpty() -> emptyFlow()
else -> sportDao.getSports(query)
}
}.onEach {
isLoading.value = false
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), listOf())
val state: Flow<ViewState> = combineTuple(
currentQuery,
isLoading,
results,
).map { (query, isLoading, results) ->
ViewState(query, isLoading, results) // no copying needed!
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
But
Try to avoid
abstract class BaseViewModel<S: BaseViewModel.State>( // do not trust
initialState: S
): ViewModel() {
abstract class State
private val flowState: MutableStateFlow<S> = // no
MutableStateFlow(initialState)
// or
private val liveDataState: MutableLiveData<S> = // no
MutableLiveData(initialState)
abstract val state: StateFlow<S> // can be ok with getter
// (but complicated and not needed)
}
class MyViewModel: BaseViewModel<MyViewModel.State>() {
@Parcelize
data class State( // no
val allData: List<T> = emptyList(), // no
val allFields: String = "",
val transientStates: Boolean = false, // no
): BaseViewModel.State()
// state.value = state.value.copy(someState = it.someState.copy(…))
}
Some reactive helpers you can use
• Rx-CombineTuple-Kt
• Rx-ValidateBy-Kt
• LiveData-CombineTuple-Kt
• LiveData-ValidateBy-Kt
• Flow-CombineTuple-Kt
• Flow-ValidateBy-Kt
• LiveData-CombineUtil-Java
Other resources
• Understand Kotlin Coroutines on Android
(Google I/O'19)
• LiveData with Coroutines and Flow (Android
Dev Summit '19)
• Android Coroutines: How to manage async
tasks in Kotlin - Manuel Vivo
• Building Reactive UIs with LiveData and
SavedStateHandle (or equivalent approaches like
Rx)
Thank you for your attention!
Q/A?

Weitere ähnliche Inhalte

Was ist angesagt?

Springboot introduction
Springboot introductionSpringboot introduction
Springboot introductionSagar Verma
 
Clean architectures with fast api pycones
Clean architectures with fast api   pyconesClean architectures with fast api   pycones
Clean architectures with fast api pyconesAlvaro Del Castillo
 
서버와 클라이언트 같은 엔진 사용하기
서버와 클라이언트 같은 엔진 사용하기서버와 클라이언트 같은 엔진 사용하기
서버와 클라이언트 같은 엔진 사용하기YEONG-CHEON YOU
 
Android jetpack compose | Declarative UI
Android jetpack compose | Declarative UI Android jetpack compose | Declarative UI
Android jetpack compose | Declarative UI Ajinkya Saswade
 
05_Reliable UDP 구현
05_Reliable UDP 구현05_Reliable UDP 구현
05_Reliable UDP 구현noerror
 
Threading Made Easy! A Busy Developer’s Guide to Kotlin Coroutines
Threading Made Easy! A Busy Developer’s Guide to Kotlin CoroutinesThreading Made Easy! A Busy Developer’s Guide to Kotlin Coroutines
Threading Made Easy! A Busy Developer’s Guide to Kotlin CoroutinesLauren Yew
 
クラスローダーについて
クラスローダーについてクラスローダーについて
クラスローダーについてSuguru ARAKAWA
 
[2021] kotlin built-in higher-order functions
[2021] kotlin built-in higher-order functions[2021] kotlin built-in higher-order functions
[2021] kotlin built-in higher-order functionsWei-Shen Lu
 
Declarative UIs with Jetpack Compose
Declarative UIs with Jetpack ComposeDeclarative UIs with Jetpack Compose
Declarative UIs with Jetpack ComposeRamon Ribeiro Rabello
 
JavaScript - Chapter 6 - Basic Functions
 JavaScript - Chapter 6 - Basic Functions JavaScript - Chapter 6 - Basic Functions
JavaScript - Chapter 6 - Basic FunctionsWebStackAcademy
 
Introduction to React Hooks
Introduction to React HooksIntroduction to React Hooks
Introduction to React HooksFelicia O'Garro
 
Kotlin Jetpack Tutorial
Kotlin Jetpack TutorialKotlin Jetpack Tutorial
Kotlin Jetpack TutorialSimplilearn
 

Was ist angesagt? (20)

Springboot introduction
Springboot introductionSpringboot introduction
Springboot introduction
 
Clean architectures with fast api pycones
Clean architectures with fast api   pyconesClean architectures with fast api   pycones
Clean architectures with fast api pycones
 
React js
React jsReact js
React js
 
서버와 클라이언트 같은 엔진 사용하기
서버와 클라이언트 같은 엔진 사용하기서버와 클라이언트 같은 엔진 사용하기
서버와 클라이언트 같은 엔진 사용하기
 
React JS - Introduction
React JS - IntroductionReact JS - Introduction
React JS - Introduction
 
Android jetpack compose | Declarative UI
Android jetpack compose | Declarative UI Android jetpack compose | Declarative UI
Android jetpack compose | Declarative UI
 
Why Vue.js?
Why Vue.js?Why Vue.js?
Why Vue.js?
 
05_Reliable UDP 구현
05_Reliable UDP 구현05_Reliable UDP 구현
05_Reliable UDP 구현
 
Threading Made Easy! A Busy Developer’s Guide to Kotlin Coroutines
Threading Made Easy! A Busy Developer’s Guide to Kotlin CoroutinesThreading Made Easy! A Busy Developer’s Guide to Kotlin Coroutines
Threading Made Easy! A Busy Developer’s Guide to Kotlin Coroutines
 
React Django Presentation
React Django PresentationReact Django Presentation
React Django Presentation
 
Spring Security 5
Spring Security 5Spring Security 5
Spring Security 5
 
React js t2 - jsx
React js   t2 - jsxReact js   t2 - jsx
React js t2 - jsx
 
クラスローダーについて
クラスローダーについてクラスローダーについて
クラスローダーについて
 
[2021] kotlin built-in higher-order functions
[2021] kotlin built-in higher-order functions[2021] kotlin built-in higher-order functions
[2021] kotlin built-in higher-order functions
 
Declarative UIs with Jetpack Compose
Declarative UIs with Jetpack ComposeDeclarative UIs with Jetpack Compose
Declarative UIs with Jetpack Compose
 
Java swing
Java swingJava swing
Java swing
 
JavaScript - Chapter 6 - Basic Functions
 JavaScript - Chapter 6 - Basic Functions JavaScript - Chapter 6 - Basic Functions
JavaScript - Chapter 6 - Basic Functions
 
Introduction to React Hooks
Introduction to React HooksIntroduction to React Hooks
Introduction to React Hooks
 
Kotlin Jetpack Tutorial
Kotlin Jetpack TutorialKotlin Jetpack Tutorial
Kotlin Jetpack Tutorial
 
Angular Lifecycle Hooks
Angular Lifecycle HooksAngular Lifecycle Hooks
Angular Lifecycle Hooks
 

Ähnlich wie Reactive state management with Jetpack Components

Architecting Single Activity Applications (With or Without Fragments)
Architecting Single Activity Applications (With or Without Fragments)Architecting Single Activity Applications (With or Without Fragments)
Architecting Single Activity Applications (With or Without Fragments)Gabor Varadi
 
MVP Community Camp 2014 - How to use enhanced features of Windows 8.1 Store ...
MVP Community Camp 2014 - How to useenhanced features of Windows 8.1 Store ...MVP Community Camp 2014 - How to useenhanced features of Windows 8.1 Store ...
MVP Community Camp 2014 - How to use enhanced features of Windows 8.1 Store ...Akira Hatsune
 
State management in android applications
State management in android applicationsState management in android applications
State management in android applicationsGabor Varadi
 
[Final] ReactJS presentation
[Final] ReactJS presentation[Final] ReactJS presentation
[Final] ReactJS presentation洪 鹏发
 
Android architecture components - how they fit in good old architectural patt...
Android architecture components - how they fit in good old architectural patt...Android architecture components - how they fit in good old architectural patt...
Android architecture components - how they fit in good old architectural patt...DroidConTLV
 
Building React Applications with Redux
Building React Applications with ReduxBuilding React Applications with Redux
Building React Applications with ReduxFITC
 
Redux data flow with angular
Redux data flow with angularRedux data flow with angular
Redux data flow with angularGil Fink
 
Introduction to Honeycomb APIs - Android Developer Lab 2011 Q3
Introduction to Honeycomb APIs - Android Developer Lab 2011 Q3Introduction to Honeycomb APIs - Android Developer Lab 2011 Q3
Introduction to Honeycomb APIs - Android Developer Lab 2011 Q3Paris Android User Group
 
Android Architecture Components
Android Architecture ComponentsAndroid Architecture Components
Android Architecture ComponentsDarshan Parikh
 
Core Data with Swift 3.0
Core Data with Swift 3.0Core Data with Swift 3.0
Core Data with Swift 3.0Korhan Bircan
 
Windows Store app using XAML and C#: Enterprise Product Development
Windows Store app using XAML and C#: Enterprise Product Development Windows Store app using XAML and C#: Enterprise Product Development
Windows Store app using XAML and C#: Enterprise Product Development Mahmoud Hamed Mahmoud
 
Redux data flow with angular
Redux data flow with angularRedux data flow with angular
Redux data flow with angularGil Fink
 
What 100M downloads taught us about iOS architectures
What 100M downloads taught us about iOS architecturesWhat 100M downloads taught us about iOS architectures
What 100M downloads taught us about iOS architecturesFrancesco Di Lorenzo
 
Redux data flow with angular 2
Redux data flow with angular 2Redux data flow with angular 2
Redux data flow with angular 2Gil Fink
 
Evan Schultz - Angular Summit - 2016
Evan Schultz - Angular Summit - 2016Evan Schultz - Angular Summit - 2016
Evan Schultz - Angular Summit - 2016Evan Schultz
 

Ähnlich wie Reactive state management with Jetpack Components (20)

Architecting Single Activity Applications (With or Without Fragments)
Architecting Single Activity Applications (With or Without Fragments)Architecting Single Activity Applications (With or Without Fragments)
Architecting Single Activity Applications (With or Without Fragments)
 
Conductor vs Fragments
Conductor vs FragmentsConductor vs Fragments
Conductor vs Fragments
 
MVP Community Camp 2014 - How to use enhanced features of Windows 8.1 Store ...
MVP Community Camp 2014 - How to useenhanced features of Windows 8.1 Store ...MVP Community Camp 2014 - How to useenhanced features of Windows 8.1 Store ...
MVP Community Camp 2014 - How to use enhanced features of Windows 8.1 Store ...
 
State management in android applications
State management in android applicationsState management in android applications
State management in android applications
 
[Final] ReactJS presentation
[Final] ReactJS presentation[Final] ReactJS presentation
[Final] ReactJS presentation
 
Android architecture components - how they fit in good old architectural patt...
Android architecture components - how they fit in good old architectural patt...Android architecture components - how they fit in good old architectural patt...
Android architecture components - how they fit in good old architectural patt...
 
Building React Applications with Redux
Building React Applications with ReduxBuilding React Applications with Redux
Building React Applications with Redux
 
Redux data flow with angular
Redux data flow with angularRedux data flow with angular
Redux data flow with angular
 
Introduction to Honeycomb APIs - Android Developer Lab 2011 Q3
Introduction to Honeycomb APIs - Android Developer Lab 2011 Q3Introduction to Honeycomb APIs - Android Developer Lab 2011 Q3
Introduction to Honeycomb APIs - Android Developer Lab 2011 Q3
 
Android Architecture Components
Android Architecture ComponentsAndroid Architecture Components
Android Architecture Components
 
Core Data with Swift 3.0
Core Data with Swift 3.0Core Data with Swift 3.0
Core Data with Swift 3.0
 
Windows Store app using XAML and C#: Enterprise Product Development
Windows Store app using XAML and C#: Enterprise Product Development Windows Store app using XAML and C#: Enterprise Product Development
Windows Store app using XAML and C#: Enterprise Product Development
 
Redux js
Redux jsRedux js
Redux js
 
Fragment
Fragment Fragment
Fragment
 
Redux data flow with angular
Redux data flow with angularRedux data flow with angular
Redux data flow with angular
 
Mobile Application Development class 006
Mobile Application Development class 006Mobile Application Development class 006
Mobile Application Development class 006
 
What 100M downloads taught us about iOS architectures
What 100M downloads taught us about iOS architecturesWhat 100M downloads taught us about iOS architectures
What 100M downloads taught us about iOS architectures
 
Redux data flow with angular 2
Redux data flow with angular 2Redux data flow with angular 2
Redux data flow with angular 2
 
Evan Schultz - Angular Summit - 2016
Evan Schultz - Angular Summit - 2016Evan Schultz - Angular Summit - 2016
Evan Schultz - Angular Summit - 2016
 
JS Essence
JS EssenceJS Essence
JS Essence
 

Kürzlich hochgeladen

Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company OdishaBalasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odishasmiwainfosol
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...OnePlan Solutions
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmSujith Sukumaran
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Velvetech LLC
 
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte GermanySuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte GermanyChristoph Pohl
 
Post Quantum Cryptography – The Impact on Identity
Post Quantum Cryptography – The Impact on IdentityPost Quantum Cryptography – The Impact on Identity
Post Quantum Cryptography – The Impact on Identityteam-WIBU
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesŁukasz Chruściel
 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesPhilip Schwarz
 
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxUI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxAndreas Kunz
 
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Cizo Technology Services
 
How to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationHow to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationBradBedford3
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...OnePlan Solutions
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceBrainSell Technologies
 
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...Angel Borroy López
 
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfGOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfAlina Yurenko
 
Xen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfXen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfStefano Stabellini
 
Simplifying Microservices & Apps - The art of effortless development - Meetup...
Simplifying Microservices & Apps - The art of effortless development - Meetup...Simplifying Microservices & Apps - The art of effortless development - Meetup...
Simplifying Microservices & Apps - The art of effortless development - Meetup...Rob Geurden
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Matt Ray
 
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsSensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsChristian Birchler
 
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...Akihiro Suda
 

Kürzlich hochgeladen (20)

Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company OdishaBalasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalm
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...
 
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte GermanySuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
 
Post Quantum Cryptography – The Impact on Identity
Post Quantum Cryptography – The Impact on IdentityPost Quantum Cryptography – The Impact on Identity
Post Quantum Cryptography – The Impact on Identity
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New Features
 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a series
 
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxUI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
 
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
 
How to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationHow to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion Application
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. Salesforce
 
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
 
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfGOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
 
Xen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfXen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdf
 
Simplifying Microservices & Apps - The art of effortless development - Meetup...
Simplifying Microservices & Apps - The art of effortless development - Meetup...Simplifying Microservices & Apps - The art of effortless development - Meetup...
Simplifying Microservices & Apps - The art of effortless development - Meetup...
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
 
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsSensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
 
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
 

Reactive state management with Jetpack Components

  • 1. Reactive State Management Using Jetpack Components Gabor Varadi @zhuinden
  • 2. What is the problem we are trying to solve?
  • 3. • Save/restore minimal state across process death • Execute data loading asynchronously based on state – Should be kept across configuration changes • Minimize „moving parts” – Mutations should be controlled – Mutations should be observed
  • 4. • Save/restore minimal state across process death • Execute data loading asynchronously based on state – Should be kept across configuration changes • Minimize „moving parts” – Mutations should be controlled – Mutations should be observed
  • 5. • Save/restore minimal state across process death • Execute data loading asynchronously based on state – Should be kept across configuration changes • Minimize „moving parts” – Mutations should be controlled – Mutations should be observed
  • 6. Process death (aka: how Android apps actually work)
  • 7.
  • 8.
  • 9.
  • 10. Core App Quality Guidelines From https://developer.android.com/docs/quality-guidelines/core-app-quality#fn „When returning to the foreground, the app must restore the preserved state and any significant stateful transaction that was pending, such as changes to editable fields, game progress, menus, videos, and other sections of the app.
  • 11. How to induce process death? • Step 1: put app in background with HOME • Step 2: press „Terminate application” • Step 3: restart app from launcher
  • 12. For apps you don’t own https://play.google.com/store/apps/details?id=me.empirical.android.application.fillme mory&hl=en
  • 13. Common mistakes • Assuming that static variables initialized on one screen (like data loading on a Splash screen) stay set on the other screen (nope) • Assuming that if(savedInstanceState == null) { is true at least once (nope) • Holding a reference to a Fragment instance without using findFragmentByTag first (for example ”ViewPagerAdapter.addFragment()”) (don’t)
  • 14. How to detect process death? if (savedInstanceState != null && lastNonConfigurationInstance == null) { } Or in BaseActivity: companion object { var isRestored: Boolean = false } if (savedInstanceState !=null) { if (!isRestored) { isRestored = true // here } }
  • 16. What needs to be persisted across process death? • Navigation state is already managed by the system on Android out of the box – Empty ctor + using intent extras / fragment arguments • Screen state is partially managed by the system – Views with IDs have their state persisted – Complex state (f.ex. RecyclerView selection) are not persisted automatically – Dynamically added views should be recreatable (!)
  • 17. What SHOULDN’T be persisted? • Data – Bundle has a size limit – Data should be fetched asynchronously, off the UI thread • Transient state – „Loading” state: computed from progress of side- effect („something is happening”, but is it really?)
  • 18. Example for saving/restoring state (the „old way”) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState != null) { selectedSportId = savedInstanceState.getLong("selectedSportId") selectedPosition = savedInstanceState.getInt("selectedPosition") selectedTags.clear() selectedTags.addAll( savedInstanceState.getStringArrayList("selectedTags")) } } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putLong("selectedSportId", selectedSportId) outState.putInt("selectedPosition", selectedPosition) outState.putStringArrayList("selectedTags", ArrayList(selectedTags)) }
  • 19. Loading data • Asynchronous loading should either begin on initialization, or when observed • Data can be loaded via a transformation chain from the observable source that stores the state – changes trigger new data load (switchMap, flatMapLatest)
  • 21. • Surviving configuration changes – ViewModel • Lifecycle-aware dataloading – Lifecycle (livedata / lifecycle-ktx) • Persistent storage (observable queries) – Room – DataStore (beta) – *Paging (beta) • Background job processing – WorkManager
  • 22. • State restoration across process death – SavedStateRegistry (savedstate) – SavedStateHandle (viewmodel-savedstate) • Connecting UI to state model – Databinding – Compose (beta) • Scoping data/state between screens + argument passing – Navigation • Piece together all the things – Hilt
  • 23. ViewModel • Stored across configuration changes in a ViewModelStore (ComponentActivity, Fragment, and NavBackStackEntry are ViewModelStoreOwner) • They have their own lifecycle for when the ViewModelStore is destroyed (onCleared(), viewModelScope) • State and asynchronous operations go here
  • 24. ViewModel • Originally: val viewModel = ViewModelProvider( viewModelStoreOwner, viewModelProviderFactory ).get(MyViewModel::class.java) • KTX (fragment-ktx): private val viewModel by viewModels<MyViewModel>( ownerProducer = { viewModelStoreOwner }, factoryProducer = { viewModelProviderFactory }, )
  • 25. Lifecycle-ktx • LiveData / MutableLiveData / MediatorLiveData • liveData(coroutineContext, timeoutMs) { coroutine builder (CoroutineLiveData) • liveData.asFlow() (FlowLiveData)
  • 26. Also Lifecycle-ktx • LifecycleOwner.lifecycleScope (fragment.viewLifecycleOwner.lifecycle Scope) • ViewModel.viewModelScope • LifecycleCoroutineScope.launchWhenStarted, Lifecycle.whenStateAtLeast • LifecycleOwner.addRepeatingJob, Lifecycle.repeatOnLifecycle (alpha)
  • 27.
  • 28. Room • Exposes observable queries – if the database is written to, then the queries are re-evaluated • Integrations: – LiveData (ComputableLiveData) – Flowable/Observable (RxJava 2.x) – Flow (kotlinx.coroutines)
  • 29. WorkManager • Schedule and eventually execute background jobs – Constraint support: execute when network is available, etc – OneTimeWorkRequest, unique work – Can be used to download data for offline support • Worker types: – ListenableWorker – CoroutineWorker – RxWorker
  • 30. SavedState • SavedStateRegistry allows saving state into a Bundle – Not commonly used directly, but heavily used by ViewModel- SavedState (and Jetpack Compose’s saveableStateHolder.SaveableStateProvider) • Important methods: – registerSavedStateProvider(String, SavedStateProvider) – unregisterSavedStateProvider(String) – consumeRestoredStateForKey(String): Bundle
  • 31. ViewModel-SavedState • SavedStateHandle allows saving ViewModel’s state into a Bundle – Provides get(), set(), and most importantly getLiveData() (SavingStateLiveData) – Custom types can be saved with setSavedStateProvider() – Initialized with arguments by default – Originally created by AbstractSavedStateViewModelFactory
  • 32. Databinding • (Prefer ViewBinding if possible) • Allows directly binding against state via binding expressions – one-way bindings (=”@{}”) vs two-way bindings (=”@={}”) – supports ObservableField / MutableLiveData / MutableStateFlow – Can be useful for forms • Binding adapters to support custom observable properties from XML in custom views
  • 33. (Jetpack Compose) • (Completely new UI framework, beta) • (Rendering happens by invoking @Composable functions) • (Changes in function arguments of @Composable functions are tracked by Compose’s Kotlin compiler plugin) • (When function arguments change, the functions that depend on said state get re-evaluated and therefore re-rendered, „recomposition”)
  • 35. Navigation • Created with the intention to simplify using 1 Activity for the whole app • Theoretically, the single Activity could be class MainActivity: AppCompatActivity(R.layout.activity_main) and no further code (see NavHostFragment) • Track navigation state, handle argument passing • Allow defining scopes shared between Fragments (see NavBackStackEntry)
  • 36. Hilt • Dependency injection framework written on top of Dagger • Simplify injection of Android components via global configuration and „automatic” injection • Integration with ViewModel (+ SavedState) and Navigation (by hiltNavGraphViewModels)
  • 37. @HiltAndroidApp class CustomApplication: Application() @AndroidEntryPoint class MainActivity: AppCompatActivity(R.layout.main_activity) <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.fragment.app.FragmentContainerView android:id="@+id/nav_host" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/navigation_graph"/> </FrameLayout>
  • 38. @AndroidEntryPoint class MyFragment: Fragment(R.layout.my_fragment) { private val viewModel by viewModels<MainViewModel>() override fun onViewCreated(view: View, savedInstanceState: Bundle) { super.onViewCreated(view, savedInstanceState) val binding = MyFragmentBinding.bind(view) val viewModel = viewModel viewLifecycleOwner.lifecycleScope.launchWhenStarted { viewModel.sport.collectLatest { sport -> // bind views } } } } @HiltViewModel class MyViewModel @Inject constructor( private val sportDao: SportDao, private val handle: SavedStateHandle ): ViewModel() { private val selectedSportId: MutableLiveData<String> = handle.getLiveData("selectedSportId") val sport: Flow<Sport> = selectedSportId.asFlow().flatMapLatest { id -> sportDao.getSport(id) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) }
  • 40. Reactive State Management • Goal: Minimizing moving parts – No mutation can happen without being notified of it • Data classes shouldn’t have var! (only val) • Observable data holder shouldn’t have either mutable classes or mutable collections! – State synchronization is idempotent • observe over a data holder should not execute one-off actions that cause a different effect on re-execution
  • 41. Don’ts MutableLiveData<ArrayList<T>> Flow<ArrayList<T>> public fun someMethod(): ArrayList<T> @Parcelize data class SomeClass( var prop: String ): Parcelable
  • 42. Do MutableLiveData<List<T>>, Flow<List<T>> public List<T> doSomething() { ArrayList<T> copy = new ArrayList<T>(original); … return Collections.unmodifiableList(copy); } @Parcelize data class SomeClass( val prop: String ): Parcelable
  • 43. Then Imagine a spreadsheet with cells of data, and formulas to combine them
  • 44. And private val b1 = savedStateHandle.getLiveData("b1", 1) private val b2 = savedStateHandle.getLiveData("b2", 2) private val b3 = savedStateHandle.getLiveData("b3", 3) private val b4 = savedStateHandle.getLiveData("b4", 4) private val b5 = savedStateHandle.getLiveData("b5", 5) private val c3 = combineTuple( b1.asFlow(), b2.asFlow(), b3.asFlow(), b4.asFlow(), b5.asFlow(), ).map { (b1, b2, b3, b4, b5) -> b1 * b2 * b3 * b4 * b5 }.stateIn(…)
  • 45. Now you can do // validation val username: MutableLiveData<String> = savedStateHandle.getLiveData("username", "") val password: MutableLiveData<String> = savedStateHandle.getLiveData("password", "") val isRegisterAndLoginEnabled : LiveData<Boolean> = validateBy( username.map { it.isNotBlank() }, password.map { it.isNotBlank() }, )
  • 46. Or // state management private val currentQuery = savedStateHandle.getLiveData("currentQuery", "") private val isLoading = MutableStateFlow(false) private val results: Flow<List<Sport>> = currentQuery.asFlow() .distinctUntilChanged() .debounce(125L) .onEach { query -> if (query.isNotEmpty()) { isLoading.value = true } } .flatMapLatest { query -> when { query.isEmpty() -> emptyFlow() else -> sportDao.getSports(query) } }.onEach { isLoading.value = false }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), listOf()) val state: Flow<ViewState> = combineTuple( currentQuery, isLoading, results, ).map { (query, isLoading, results) -> ViewState(query, isLoading, results) // no copying needed! }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
  • 47. But
  • 48. Try to avoid abstract class BaseViewModel<S: BaseViewModel.State>( // do not trust initialState: S ): ViewModel() { abstract class State private val flowState: MutableStateFlow<S> = // no MutableStateFlow(initialState) // or private val liveDataState: MutableLiveData<S> = // no MutableLiveData(initialState) abstract val state: StateFlow<S> // can be ok with getter // (but complicated and not needed) } class MyViewModel: BaseViewModel<MyViewModel.State>() { @Parcelize data class State( // no val allData: List<T> = emptyList(), // no val allFields: String = "", val transientStates: Boolean = false, // no ): BaseViewModel.State() // state.value = state.value.copy(someState = it.someState.copy(…)) }
  • 49. Some reactive helpers you can use • Rx-CombineTuple-Kt • Rx-ValidateBy-Kt • LiveData-CombineTuple-Kt • LiveData-ValidateBy-Kt • Flow-CombineTuple-Kt • Flow-ValidateBy-Kt • LiveData-CombineUtil-Java
  • 50. Other resources • Understand Kotlin Coroutines on Android (Google I/O'19) • LiveData with Coroutines and Flow (Android Dev Summit '19) • Android Coroutines: How to manage async tasks in Kotlin - Manuel Vivo • Building Reactive UIs with LiveData and SavedStateHandle (or equivalent approaches like Rx)
  • 51. Thank you for your attention! Q/A?