SlideShare a Scribd company logo
1 of 76
Download to read offline
1
LiveData
on Steroids
Taking LiveData to the next level
Giora Shevach &
Shahar Ben Moshe
LiveData<>
4
● Observable data holder
LiveData (vanilla)
5
LiveData (vanilla)
● Observable data holder
● Lifecycle-aware
○ Auto handling of lifecycle-related stuff
○ Avoids stupid crashes
○ Avoids memory leaks
○ App doesn’t do more work than it
6
LiveData (vanilla)
● Observable data holder
● Lifecycle-aware
○ Auto handling of lifecycle-related stuff
○ Avoids stupid crashes
○ Avoids memory leaks
○ App doesn’t do more work than it
7
8
class WeatherDashboardActivity : Activity() {
private lateinit viewModel: WeatherDashboardViewModel
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProviders.of(this)
.get(WeatherDashboardViewModel::java.class)
observeWeather()
}
private fun observeWeather() {
viewModel.weather.observe(this, Observer { // it: Weather
showTemperatureInBig(it.temperature)
})
}
}
class WeatherDashboardViewModel : ViewModel {
val weather: LiveData<Weather> = weatherRepo.weather
}
class WeatherRepo {
val weather = MutableLiveData<Weather>()
// code to manipulate weather
}
class WeatherDashboardActivity : Activity() {
private lateinit viewModel: WeatherDashboardViewModel
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProviders.of(this)
.get(WeatherDashboardViewModel::java.class)
observeWeather()
}
private fun observeWeather() {
viewModel.weather.observe(this, Observer { // it: Weather
showTemperatureInBig(it.temperature)
})
}
}
class WeatherDashboardViewModel : ViewModel {
val weather: LiveData<Weather> = weatherRepo.weather
}
class WeatherRepo {
val weather = MutableLiveData<Weather>()
// code to manipulate weather
}
class WeatherDashboardActivity : Activity() {
private lateinit viewModel: WeatherDashboardViewModel
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProviders.of(this)
.get(WeatherDashboardViewModel::java.class)
observeWeather()
}
private fun observeWeather() {
viewModel.weather.observe(this, Observer { // it: Weather
showTemperatureInBig(it.temperature)
})
}
}
class WeatherDashboardViewModel : ViewModel {
val weather: LiveData<Weather> = weatherRepo.weather
}
class WeatherRepo {
val weather = MutableLiveData<Weather>()
// code to manipulate weather
}
class WeatherDashboardActivity : Activity() {
private lateinit viewModel: WeatherDashboardViewModel
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProviders.of(this)
.get(WeatherDashboardViewModel::java.class)
observeWeather()
}
private fun observeWeather() {
viewModel.weather.observe(this, Observer { // it: Weather
showTemperatureInBig(it.temperature)
})
}
}
class WeatherDashboardViewModel : ViewModel {
val weather: LiveData<Weather> = weatherRepo.weather
}
class WeatherRepo {
val weather = MutableLiveData<Weather>()
// code to manipulate weather
}
class WeatherDashboardActivity : Activity() {
private lateinit viewModel: WeatherDashboardViewModel
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProviders.of(this)
.get(WeatherDashboardViewModel::java.class)
observeWeather()
}
private fun observeWeather() {
viewModel.weather.observe(this, Observer { // it: Weather
showTemperatureInBig(it.temperature)
})
}
}
class WeatherDashboardViewModel : ViewModel {
val weather: LiveData<Weather> = weatherRepo.weather
}
class WeatherRepo {
val weather = MutableLiveData<Weather>()
// code to manipulate weather
}
LiveData - where it falls short
15
WeatherDashboardActivity
viewModel.weather.observe(...)
WeatherDashboardViewModel
val weather: LiveData<Weather> = weatherRepo.weather
WeatherRepo
val weather = MutableLiveData<Weather>()
fun fetchNewWeather() {
// do some network stuff…
weather.value = networkResponse.data
}
AppLifecycleObserver
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onForeground() {
weatherRepo.fetchNewWeather()
}
val weatherError = MutableLiveData<Exception>()
val weatherError = weatherRepo.weatherError
viewModel.weatherError.observe(...)
fun observeWeatherAndOtherThingsAsWell() {
viewModel.weather.observe(this, Observer { weather -> // : Weather
viewModel.weatherError.observe(this, Observer { weatherError -> // : Exception
viewModel.weatherLoading.observe(this, Observer {weatherLoading -> // : Boolean
if (weatherError != null && weatherError.data != lastError) {
showErrorMessage()
lastError = weatherError.data
} else {
if (weatherLoading) {
showLoader()
} else {
if (weather != null) {
showTemperatureInBig(weather.temperature)
} else {
showErrorMessage()
}
}
}
})
})
})
Stateful.Live.Data
19
StatefulLiveData
⬢ How we built it
⬢ How to use it
⬢ How it solves common scenarios in daily app operation
⬢ Integration in existing code & libraries
⬢ Super powers
20
❏ Get updates upon data changes
❏ Be lifecycle-aware
❏ Support every data type
❏ Know when the data is retrieved successfully and ready to
use
❏ Know when the data is loading, and supply some partial data
❏ Know when an error occurred, and know what the error is
Requirements
StatefulLiveData
StatefulData
abstract class StatefulData<T> {
class Success<T>(val data: T) : StatefulData<T>()
class Error<T>(val throwable: Throwable) : StatefulData<T>()
class Loading<T>(val loadingData: Any?) : StatefulData<T>()
}
❏ Get updates upon data changes
❏ Be lifecycle-aware
❏ Support every data type
❏ Know when the data is retrieved successfully and ready to use
❏ Know when the data is loading, and supply some partial data
❏ Know when an error occurred, and know what the error is
StatefulData
abstract class StatefulData<T> {
class Success<T>(val data: T) : StatefulData<T>()
class Error<T>(val throwable: Throwable) : StatefulData<T>()
class Loading<T>(val loadingData: Any?) : StatefulData<T>()
}
❏ Get updates upon data changes
❏ Be lifecycle-aware
✓ Support every data type
✓ Know when the data is retrieved successfully and ready to use
✓ Know when the data is loading, and supply some partial data
✓ Know when an error occurred, and know what the error is
StatefulLiveData
LiveData<StatefulData<T>>
typealias StatefulLiveData<T> = LiveData<StatefulData<T>>
typealias MutableStatefulLiveData<T> = MutableLiveData<StatefulData<T>>
typealias MediatorStatefulLiveData<T> = MediatorLiveData<StatefulData<T>>
WeatherDashboardActivity
viewModel.weather.observe(...)
WeatherDashboardViewModel
val weather: LiveData<Weather> = weatherRepo.weather
WeatherRepo
val weather = MutableLiveData<Weather>()
fun fetchNewWeather() {
// do some network stuff…
weather.value = networkResponse.data
}
AppLifecycleObserver
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onForeground() {
weatherRepo.fetchNewWeather()
}
val weatherError= MutableLiveData<Exception>()
val weatherError= weatherRepo.weatherError
viewModel.weatherError.observe(...)
WeatherDashboardActivity
viewModel.weather.observe(...)
WeatherDashboardViewModel
val weather: StatefulLiveData<Weather> =
weatherRepo.weather
WeatherRepo
val weather = MutableStatefulLiveData<Weather>()
fun fetchNewWeather() {
// do some network stuff…
weather.value = networkResponse.data
}
AppLifecycleObserver
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onForeground() {
weatherRepo.fetchNewWeather()
}
class WeatherDashboardActivity : Activity()
private fun observeWeather() {
viewModel.weather.observe(this, Observer { //it: StatefulData<Weather>
When (it) {
is Success -> { // it.data: Weather
showTemperatureInBig(it.data.temperature)
}
is Loading -> { // it.loadingData: Any?
showLocationName(it.loadingData as? String)
showLoader()
}
is Error -> { // it.throwable: Throwable
showErrorMessage()
log(it.throwable)
}
}
})
}
}
class WeatherDashboardActivity : Activity()
private fun observeWeather() {
viewModel.weather.observe(this, Observer { //it: StatefulData<Weather>
When (it) {
is Success -> { // it.data: Weather
showTemperatureInBig(it.data.temperature)
}
is Loading -> { // it.loadingData: Any?
showLocationName(it.loadingData as? String)
showLoader()
}
is Error -> { // it.throwable: Throwable
showErrorMessage()
log(it.throwable)
}
}
})
}
}
class WeatherDashboardActivity : Activity()
private fun observeWeather() {
viewModel.weather.observe(this, Observer { //it: StatefulData<Weather>
When (it) {
is Success -> { // it.data: Weather
showTemperatureInBig(it.data.temperature)
}
is Loading -> { // it.loadingData: Any?
showLocationName(it.loadingData as? String)
showLoader()
}
is Error -> { // it.throwable: Throwable
showErrorMessage()
log(it.throwable)
}
}
})
}
}
class WeatherDashboardActivity : Activity()
private fun observeWeather() {
viewModel.weather.observe(this, Observer { //it: StatefulData<Weather>
When (it) {
is Success -> { // it.data: Weather
showTemperatureInBig(it.data.temperature)
}
is Loading -> { // it.loadingData: Any?
showLocationName(it.loadingData as? String)
showLoader()
}
is Error -> { // it.throwable: Throwable
showErrorMessage()
log(it.throwable)
}
}
})
}
}
31
It’s
Alive!!
(Data)
Simple State Observation
class WeatherDasboardActivity : Activity() {
private fun observeWeather() {
viewModel.weather.observe(this, Observer { //it: StatefulData<Weather>
When (it) {
is Success -> showTemperatureInBig(it.data.temperature) // it.data: Weather
is Loading -> {
showLocationName(it.loadingData as? String) // it.loadingData: Any?
showLoader()
}
is Error -> {
showErrorMessage()
log(it.throwable) // it.throwable: Throwable
}
}
})
}
}
34
observeSuccess
class WeatherDasboardActivity : Activity() {
private fun observeWeather() {
viewModel.weather.observeSuccess(this, Observer { //it: Weather
showTemperatureInBig(it.temperature)
})
}
}
35
observeLoading, observeError
class WeatherDasboardActivity : Activity() {
private fun observeWeather() {
viewModel.weather.observeLoading(this, Observer { //it: Any?
showLocationName(it as? String)
showLoader()
})
}
}
class WeatherDasboardActivity : Activity() {
private fun observeWeather() {
viewModel.weather.observeError(this, Observer { //it: Throwable
showErrorMessage()
log(it)
})
}
}
Flexible Observation
class WeatherDasboardActivity : Activity() {
private fun observeWeather() {
viewModel.weather.observe(this, Observer { //it: StatefulData<Weather>
When (it) {
is Success -> showTemperatureInBig(it.data.temperature) // it.data: Weather
is Loading -> {
showLocationName(it.loadingData as? String) // it.loadingData: Any?
showLoader()
}
is Error -> {
showErrorMessage()
log(it.throwable) // it.throwable: Throwable
}
}
})
}
}
class WeatherDasboardActivity : Activity() {
private fun observeWeather() {
viewModel.weather.subscribe(this)
.onSuccess{ // it: Weather
showTemperatureInBig(it.temperature)
}
.onError{ // it: Throwable
showErrorMessage()
log(it)
}
.observe()
}
}
class WeatherDasboardActivity : Activity() {
private fun observeWeather() {
viewModel.weather.subscribe(this)
.onSuccess{ // it: Weather
showTemperatureInBig(it.temperature)
}
.onError{ // it: Throwable
showErrorMessage()
log(it)
}
.observe()
}
}
class WeatherDasboardActivity : Activity() {
private fun observeWeather() {
viewModel.weather.subscribe(this)
.onSuccess{ // it: Weather
showTemperatureInBig(it.temperature)
}
.onError{ // it: Throwable
showErrorMessage()
log(it)
}
.observe()
}
}
class WeatherDasboardActivity : Activity() {
private fun observeWeather() {
viewModel.weather.subscribe(this)
.onSuccess{ // it: Weather
showTemperatureInBig(it.temperature)
}
.onError{ // it: Throwable
showErrorMessage()
log(it)
}
.observe()
}
}
class WeatherDasboardActivity : Activity() {
private fun observeWeather() {
viewModel.weather.subscribe(this)
.onSuccess{ // it: Weather
showTemperatureInBig(it.temperature)
}
.onError{ // it: Throwable
showErrorMessage()
log(it)
}
.observe()
}
}
44
Transformations.Map
class MyViewModel() : ViewModel() {
val temperature: StatefulLiveData<Temperature> = repo.weather.map { // it : Weather
It.temperature ^map
}
}
45
Transformations.Map
class MyViewModel() : ViewModel() {
val temperature: StatefulLiveData<Temperature> = repo.weather.map { // it : Weather
It.temperature ^map
}
}
Transformations.SwitchMap
class MyViewModel() : ViewModel() {
val radarImage: StatefulLiveData<List<RadarImage>> =
repo.weather.switchMap { // it : Weather
repo.getRadarImages(it.latLng) // ^switchMap
}
}
46
class MyViewModel() : ViewModel() {
val temperature: StatefulLiveData<Temperature> =
repo.weather.map(myCoroutineScope) { // it : Weather
It.temperature ^map
}
}
Transformations.MapWithCoroutine
Stateful.Live.Data.Mediators
48
typealias MediatorStatefulLiveData<T> = MediatorLiveData<StatefulData<T>>
StatefulLiveDataMediator
49
typealias MediatorStatefulLiveData<T> = MediatorLiveData<StatefulData<T>>
StatefulLiveDataMediator
class MyViewModel() : ViewModel() {
val fusedForecast = MediatorStatefulLiveData<Forecast>().apply {
addSource(repo.todayForecast) { // it: Forecast
// handle data
}
addSource(repo.tomorrowForecast) { // it: StatefulData<Forecast>
// handle stateful data
}
}
}
51
class WeatherDashboardViewModel : ViewModel() {
private val vanillaWeather: LiveData<Weather> =
repo.weather.mapToLiveData()
}
mapToLiveData
52
mapToLiveData
class WeatherDasboardViewModel : ViewModel() {
private val vanillaWeather: LiveData<Weather> = repo.weather
.mapToLiveData(errorMapFunction = { // it : Throwable
return Weather()
})
}
fun <T> StatefulLiveData<T>.mapToLiveData(
errorMapFunction: (Throwable) -> T? = { _ -> null },
loadingMapFunction: (Any?) -> T? = { _ -> null },
fallbackMapFunction: () -> T? = { null }
): LiveData<T>
Put
54
Put
fun <T> MutableStatefulLiveData<T>.putData(data: T)
fun <T> MutableStatefulLiveData<T>.putLoading(loadingData: Any?)? = null)
fun <T> MutableStatefulLiveData<T>.putError(error: Throwable)
55
Put
fun <T> MutableStatefulLiveData<T>.putData(data: T)
fun <T> MutableStatefulLiveData<T>.putLoading(loadingFunction: (() -> Any?)? = null)
fun <T> MutableStatefulLiveData<T>.putError(error: Throwable)
loadingData
57
class WeatherDasboardViewModel : ViewModel() {
private val mutableWeather = MutableStatefulLiveData<Weather>()
private fun setWeatherLoading() {
mutableWeather.putLoading() //loadingData = null
mutableWeather.putLoading(”Please wait...”) // loadingData: String
mutableWeather.putLoading(53) // loadingData: Int - show % progress
mutableWeather.putLoading(oldWeather) // loadingData: Weather
}
}
loadingData
58
class WeatherDasboardViewModel : ViewModel() {
private val mutableWeather = MutableStatefulLiveData<Weather>()
private fun setWeatherLoading() {
mutableWeather.putLoading() //loadingData = null
mutableWeather.putLoading(”Please wait...”) // loadingData: String
mutableWeather.putLoading(53) // loadingData: Int - show % progress
mutableWeather.putLoading(oldWeather) // loadingData: Weather
}
}
loadingData
59
class WeatherDasboardViewModel : ViewModel() {
private val mutableWeather = MutableStatefulLiveData<Weather>()
private fun setWeatherLoading() {
mutableWeather.putLoading() //loadingData = null
mutableWeather.putLoading(”Please wait...”) // loadingData: String
mutableWeather.putLoading(53) // loadingData: Int - show % progress
mutableWeather.putLoading(oldWeather) // loadingData: Weather
}
}
loadingData
60
class WeatherDasboardViewModel : ViewModel() {
private val mutableWeather = MutableStatefulLiveData<Weather>()
private fun setWeatherLoading() {
mutableWeather.putLoading() //loadingData = null
mutableWeather.putLoading(”Please wait...”) // loadingData: String
mutableWeather.putLoading(53) // loadingData: Int - show % progress
mutableWeather.putLoading(oldWeather) // loadingData: Weather
}
}
loadingData
61
fun <T> LiveData<T>.observeOnce(
observer: Observer<T>,
retainForLoadingState: Boolean = true)
observeOnce
63
Stateful.Live.Data.Call.Adapter
fun createClient(baseUrl: String): Retrofit {
return Retrofit
.Builder()
.baseUrl(baseUrl)
.addCallAdapterFactory(StatefulLiveDataCallAdapterFactory.create())
.build()
}
64
Stateful.Live.Data.Call.Adapter
fun createClient(baseUrl: String): Retrofit {
return Retrofit
.Builder()
.baseUrl(baseUrl)
.addCallAdapterFactory(StatefulLiveDataCallAdapterFactory.create())
.build()
}
65
Stateful.Live.Data.Call.Adapter
fun createClient(baseUrl: String): Retrofit {
return Retrofit
.Builder()
.baseUrl(baseUrl)
.addCallAdapterFactory(StatefulLiveDataCallAdapterFactory.create())
.build()
}
interface MyRetrofitService {
@GET("weather/current")
fun getWeather(): Call<Weather>
}
66
Stateful.Live.Data.Call.Adapter
fun createClient(baseUrl: String): Retrofit {
return Retrofit
.Builder()
.baseUrl(baseUrl)
.addCallAdapterFactory(StatefulLiveDataCallAdapterFactory.create())
.build()
}
interface MyRetrofitService {
@GET("weather/current")
fun getWeather(): StatefulLiveData<Weather>
}
67
class FirestoreRepo {
private val firestoreDb: FirebaseFirestore = … // init FirebaseFirestore
fun getUser(userId: String) : Task<User> {
val getUserTask = firestoreDb
.collection(“Users”)
.whereEqualTo(“userId”, userId)
return getUserTask
}
}
Task<T>.toStatefulLiveData
68
class FirestoreRepo {
private val firestoreDb: FirebaseFirestore = … // init FirebaseFirestore
fun getUser(userId: String) : StatefulLiveData<User> {
val getUserTask = firestoreDb
.collection(“Users”)
.whereEqualTo(“userId”, userId)
return getUserTask.toStatefulLiveData()
}
}
Task<T>.toStatefulLiveData
69
Task<T>.toStatefulLiveData
class FirestoreRepo {
private val firestoreDb: FirebaseFirestore = … // init FirebaseFirestore
fun getUser(userId: String) : StatefulLiveData<Account> {
val getUserTask = firestoreDb
.collection(“Users”)
.whereEqualTo(“userId”, userId)
return getUserTask.toStatefulLiveData { // User: task result
val userAccount: Account = … // map User -> Account
userAccount ^toStatefulLiveData
}
}
}
Extendibility
71
class LoadingWithPercent(val percent: Int) : StatefulData<Int>()
72
class LoadingWithPercent(val percent: Int) : StatefulData<Int>()
class WeatherDashboardActivity : Activity() {
private fun observeWeather() {
viewModel.weather.observe(this, Observer { //it: StatefulData<Weather>
When (it) {
is ... -> {...}
is LoadingWithPercent -> {
showProgress(it.percent) // it.percent: Int
}
}
})
}
}
73
class LoadingWithPercent(val percent: Int) : StatefulData<Int>()
fun <T> MutableStatefulLiveData<T>.putLoadingWithPercent(percent: Int) {...}
fun <T> StatefulLiveData<T>.observeLoadingWithPercent(owner: LifecycleOwner,
observer: Observer<in Any?>): StatefulLiveData<T> {...}
74
Event bus Callback listener Rx StatefulLiveData
Life cycle aware X X X
Low learning
curve
X
Lean X X
Data retention X X
Memory leaks
free
X X X
75
Use the right tool for the job
(or make one if it doesn’t exist)
76
Got get Stateful, It’s Live!
https://github.com/climacell/statefullivedata
Thank you!

More Related Content

What's hot

The Ring programming language version 1.6 book - Part 70 of 189
The Ring programming language version 1.6 book - Part 70 of 189The Ring programming language version 1.6 book - Part 70 of 189
The Ring programming language version 1.6 book - Part 70 of 189Mahmoud Samir Fayed
 
Drools 6.0 (CamelOne 2013)
Drools 6.0 (CamelOne 2013)Drools 6.0 (CamelOne 2013)
Drools 6.0 (CamelOne 2013)Mark Proctor
 
The Ring programming language version 1.5.1 book - Part 64 of 180
The Ring programming language version 1.5.1 book - Part 64 of 180The Ring programming language version 1.5.1 book - Part 64 of 180
The Ring programming language version 1.5.1 book - Part 64 of 180Mahmoud Samir Fayed
 
Swift Montevideo Meetup - iPhone, una herramienta medica
Swift Montevideo Meetup - iPhone, una herramienta medicaSwift Montevideo Meetup - iPhone, una herramienta medica
Swift Montevideo Meetup - iPhone, una herramienta medicaWashington Miranda
 
Managing parallelism using coroutines
Managing parallelism using coroutinesManaging parallelism using coroutines
Managing parallelism using coroutinesFabio Collini
 
Solid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon ItalySolid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon ItalyFabio Collini
 
The Ring programming language version 1.8 book - Part 74 of 202
The Ring programming language version 1.8 book - Part 74 of 202The Ring programming language version 1.8 book - Part 74 of 202
The Ring programming language version 1.8 book - Part 74 of 202Mahmoud Samir Fayed
 
JS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless BebopJS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless BebopJSFestUA
 
The Ring programming language version 1.7 book - Part 72 of 196
The Ring programming language version 1.7 book - Part 72 of 196The Ring programming language version 1.7 book - Part 72 of 196
The Ring programming language version 1.7 book - Part 72 of 196Mahmoud Samir Fayed
 
Android Architecture - Khoa Tran
Android Architecture -  Khoa TranAndroid Architecture -  Khoa Tran
Android Architecture - Khoa TranTu Le Dinh
 
The Ring programming language version 1.9 book - Part 72 of 210
The Ring programming language version 1.9 book - Part 72 of 210The Ring programming language version 1.9 book - Part 72 of 210
The Ring programming language version 1.9 book - Part 72 of 210Mahmoud Samir Fayed
 
Practical RxJava for Android
Practical RxJava for AndroidPractical RxJava for Android
Practical RxJava for AndroidTomáš Kypta
 
Compose Async with RxJS
Compose Async with RxJSCompose Async with RxJS
Compose Async with RxJSKyung Yeol Kim
 

What's hot (19)

Rxjs marble-testing
Rxjs marble-testingRxjs marble-testing
Rxjs marble-testing
 
The Ring programming language version 1.6 book - Part 70 of 189
The Ring programming language version 1.6 book - Part 70 of 189The Ring programming language version 1.6 book - Part 70 of 189
The Ring programming language version 1.6 book - Part 70 of 189
 
Drools 6.0 (CamelOne 2013)
Drools 6.0 (CamelOne 2013)Drools 6.0 (CamelOne 2013)
Drools 6.0 (CamelOne 2013)
 
The Ring programming language version 1.5.1 book - Part 64 of 180
The Ring programming language version 1.5.1 book - Part 64 of 180The Ring programming language version 1.5.1 book - Part 64 of 180
The Ring programming language version 1.5.1 book - Part 64 of 180
 
Latest java
Latest javaLatest java
Latest java
 
Swift Montevideo Meetup - iPhone, una herramienta medica
Swift Montevideo Meetup - iPhone, una herramienta medicaSwift Montevideo Meetup - iPhone, una herramienta medica
Swift Montevideo Meetup - iPhone, una herramienta medica
 
Analytics with Spark
Analytics with SparkAnalytics with Spark
Analytics with Spark
 
Managing parallelism using coroutines
Managing parallelism using coroutinesManaging parallelism using coroutines
Managing parallelism using coroutines
 
Solid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon ItalySolid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon Italy
 
Test
TestTest
Test
 
The Ring programming language version 1.8 book - Part 74 of 202
The Ring programming language version 1.8 book - Part 74 of 202The Ring programming language version 1.8 book - Part 74 of 202
The Ring programming language version 1.8 book - Part 74 of 202
 
droidparts
droidpartsdroidparts
droidparts
 
JS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless BebopJS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless Bebop
 
java assignment
java assignmentjava assignment
java assignment
 
The Ring programming language version 1.7 book - Part 72 of 196
The Ring programming language version 1.7 book - Part 72 of 196The Ring programming language version 1.7 book - Part 72 of 196
The Ring programming language version 1.7 book - Part 72 of 196
 
Android Architecture - Khoa Tran
Android Architecture -  Khoa TranAndroid Architecture -  Khoa Tran
Android Architecture - Khoa Tran
 
The Ring programming language version 1.9 book - Part 72 of 210
The Ring programming language version 1.9 book - Part 72 of 210The Ring programming language version 1.9 book - Part 72 of 210
The Ring programming language version 1.9 book - Part 72 of 210
 
Practical RxJava for Android
Practical RxJava for AndroidPractical RxJava for Android
Practical RxJava for Android
 
Compose Async with RxJS
Compose Async with RxJSCompose Async with RxJS
Compose Async with RxJS
 

Similar to LiveData on Steroids - Giora Shevach + Shahar Ben Moshe, Climacell

MVI - Managing State The Kotlin Way
MVI - Managing State The Kotlin WayMVI - Managing State The Kotlin Way
MVI - Managing State The Kotlin WayZeyad Gasser
 
DWR, Hibernate and Dojo.E - A Tutorial
DWR, Hibernate and Dojo.E - A TutorialDWR, Hibernate and Dojo.E - A Tutorial
DWR, Hibernate and Dojo.E - A Tutorialjbarciauskas
 
Effective Android Data Binding
Effective Android Data BindingEffective Android Data Binding
Effective Android Data BindingEric Maxwell
 
Presentation Android Architecture Components
Presentation Android Architecture ComponentsPresentation Android Architecture Components
Presentation Android Architecture ComponentsAttract Group
 
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
 
Architecture components - IT Talk
Architecture components - IT TalkArchitecture components - IT Talk
Architecture components - IT TalkConstantine Mars
 
Survive the lifecycle
Survive the lifecycleSurvive the lifecycle
Survive the lifecycleSimon Joecks
 
Working effectively with ViewModels and TDD - UA Mobile 2019
Working effectively with ViewModels and TDD - UA Mobile 2019Working effectively with ViewModels and TDD - UA Mobile 2019
Working effectively with ViewModels and TDD - UA Mobile 2019UA Mobile
 
Android Architecture Components
Android Architecture ComponentsAndroid Architecture Components
Android Architecture ComponentsBurhanuddinRashid
 
Slightly Advanced Android Wear ;)
Slightly Advanced Android Wear ;)Slightly Advanced Android Wear ;)
Slightly Advanced Android Wear ;)Alfredo Morresi
 
Async JavaScript in ES7
Async JavaScript in ES7Async JavaScript in ES7
Async JavaScript in ES7Mike North
 
Introduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingIntroduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingSamuel ROZE
 
Android Jetpack: ViewModel and Testing
Android Jetpack: ViewModel and TestingAndroid Jetpack: ViewModel and Testing
Android Jetpack: ViewModel and TestingYongjun Kim
 
Data in Motion: Streaming Static Data Efficiently
Data in Motion: Streaming Static Data EfficientlyData in Motion: Streaming Static Data Efficiently
Data in Motion: Streaming Static Data EfficientlyMartin Zapletal
 
From mvc to redux: 停看聽
From mvc to redux: 停看聽From mvc to redux: 停看聽
From mvc to redux: 停看聽Jeff Lin
 
How to become an Android dev starting from iOS (and vice versa)
How to become an Android dev starting from iOS (and vice versa)How to become an Android dev starting from iOS (and vice versa)
How to become an Android dev starting from iOS (and vice versa)Giuseppe Filograno
 

Similar to LiveData on Steroids - Giora Shevach + Shahar Ben Moshe, Climacell (20)

MVI - Managing State The Kotlin Way
MVI - Managing State The Kotlin WayMVI - Managing State The Kotlin Way
MVI - Managing State The Kotlin Way
 
DWR, Hibernate and Dojo.E - A Tutorial
DWR, Hibernate and Dojo.E - A TutorialDWR, Hibernate and Dojo.E - A Tutorial
DWR, Hibernate and Dojo.E - A Tutorial
 
Effective Android Data Binding
Effective Android Data BindingEffective Android Data Binding
Effective Android Data Binding
 
Presentation Android Architecture Components
Presentation Android Architecture ComponentsPresentation Android Architecture Components
Presentation Android Architecture Components
 
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...
 
Architecture components - IT Talk
Architecture components - IT TalkArchitecture components - IT Talk
Architecture components - IT Talk
 
Survive the lifecycle
Survive the lifecycleSurvive the lifecycle
Survive the lifecycle
 
Working effectively with ViewModels and TDD - UA Mobile 2019
Working effectively with ViewModels and TDD - UA Mobile 2019Working effectively with ViewModels and TDD - UA Mobile 2019
Working effectively with ViewModels and TDD - UA Mobile 2019
 
package org dev
package org devpackage org dev
package org dev
 
Package org dev
Package org devPackage org dev
Package org dev
 
iOS Talks 6: Unit Testing
iOS Talks 6: Unit TestingiOS Talks 6: Unit Testing
iOS Talks 6: Unit Testing
 
Android Architecture Components
Android Architecture ComponentsAndroid Architecture Components
Android Architecture Components
 
Slightly Advanced Android Wear ;)
Slightly Advanced Android Wear ;)Slightly Advanced Android Wear ;)
Slightly Advanced Android Wear ;)
 
Async JavaScript in ES7
Async JavaScript in ES7Async JavaScript in ES7
Async JavaScript in ES7
 
Introduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingIntroduction to CQRS and Event Sourcing
Introduction to CQRS and Event Sourcing
 
Saving lives with rx java
Saving lives with rx javaSaving lives with rx java
Saving lives with rx java
 
Android Jetpack: ViewModel and Testing
Android Jetpack: ViewModel and TestingAndroid Jetpack: ViewModel and Testing
Android Jetpack: ViewModel and Testing
 
Data in Motion: Streaming Static Data Efficiently
Data in Motion: Streaming Static Data EfficientlyData in Motion: Streaming Static Data Efficiently
Data in Motion: Streaming Static Data Efficiently
 
From mvc to redux: 停看聽
From mvc to redux: 停看聽From mvc to redux: 停看聽
From mvc to redux: 停看聽
 
How to become an Android dev starting from iOS (and vice versa)
How to become an Android dev starting from iOS (and vice versa)How to become an Android dev starting from iOS (and vice versa)
How to become an Android dev starting from iOS (and vice versa)
 

More from DroidConTLV

Mobile Development in the Information Age - Yossi Elkrief, Nike
Mobile Development in the Information Age - Yossi Elkrief, NikeMobile Development in the Information Age - Yossi Elkrief, Nike
Mobile Development in the Information Age - Yossi Elkrief, NikeDroidConTLV
 
Doing work in the background - Darryn Campbell, Zebra Technologies
Doing work in the background - Darryn Campbell, Zebra TechnologiesDoing work in the background - Darryn Campbell, Zebra Technologies
Doing work in the background - Darryn Campbell, Zebra TechnologiesDroidConTLV
 
No more video loss - Alex Rivkin, Motorola Solutions
No more video loss - Alex Rivkin, Motorola SolutionsNo more video loss - Alex Rivkin, Motorola Solutions
No more video loss - Alex Rivkin, Motorola SolutionsDroidConTLV
 
Mobile at Scale: from startup to a big company - Dor Samet, Booking.com
Mobile at Scale: from startup to a big company - Dor Samet, Booking.comMobile at Scale: from startup to a big company - Dor Samet, Booking.com
Mobile at Scale: from startup to a big company - Dor Samet, Booking.comDroidConTLV
 
MVVM In real life - Lea Cohen Tannoudji, Lightricks
MVVM In real life - Lea Cohen Tannoudji, LightricksMVVM In real life - Lea Cohen Tannoudji, Lightricks
MVVM In real life - Lea Cohen Tannoudji, LightricksDroidConTLV
 
Best Practices for Using Mobile SDKs - Lilach Wagner, SafeDK (AppLovin)
Best Practices for Using Mobile SDKs - Lilach Wagner, SafeDK (AppLovin)Best Practices for Using Mobile SDKs - Lilach Wagner, SafeDK (AppLovin)
Best Practices for Using Mobile SDKs - Lilach Wagner, SafeDK (AppLovin)DroidConTLV
 
Building Apps with Flutter - Hillel Coren, Invoice Ninja
Building Apps with Flutter - Hillel Coren, Invoice NinjaBuilding Apps with Flutter - Hillel Coren, Invoice Ninja
Building Apps with Flutter - Hillel Coren, Invoice NinjaDroidConTLV
 
New Android Project: The Most Important Decisions - Vasiliy Zukanov
New Android Project: The Most Important Decisions - Vasiliy ZukanovNew Android Project: The Most Important Decisions - Vasiliy Zukanov
New Android Project: The Most Important Decisions - Vasiliy ZukanovDroidConTLV
 
Designing a Design System - Shai Mishali, Gett
Designing a Design System - Shai Mishali, GettDesigning a Design System - Shai Mishali, Gett
Designing a Design System - Shai Mishali, GettDroidConTLV
 
The Mighty Power of the Accessibility Service - Guy Griv, Pepper
The Mighty Power of the Accessibility Service - Guy Griv, PepperThe Mighty Power of the Accessibility Service - Guy Griv, Pepper
The Mighty Power of the Accessibility Service - Guy Griv, PepperDroidConTLV
 
Kotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDev
Kotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDevKotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDev
Kotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDevDroidConTLV
 
Flutter State Management - Moti Bartov, Tikal
Flutter State Management - Moti Bartov, TikalFlutter State Management - Moti Bartov, Tikal
Flutter State Management - Moti Bartov, TikalDroidConTLV
 
Reactive UI in android - Gil Goldzweig Goldbaum, 10bis
Reactive UI in android - Gil Goldzweig Goldbaum, 10bisReactive UI in android - Gil Goldzweig Goldbaum, 10bis
Reactive UI in android - Gil Goldzweig Goldbaum, 10bisDroidConTLV
 
Fun with flutter animations - Divyanshu Bhargava, GoHighLevel
Fun with flutter animations - Divyanshu Bhargava, GoHighLevelFun with flutter animations - Divyanshu Bhargava, GoHighLevel
Fun with flutter animations - Divyanshu Bhargava, GoHighLevelDroidConTLV
 
DroidconTLV 2019
DroidconTLV 2019DroidconTLV 2019
DroidconTLV 2019DroidConTLV
 
Ok google, it's time to bot! - Hadar Franco, Albert + Stav Levi, Monday
Ok google, it's time to bot! - Hadar Franco, Albert + Stav Levi, MondayOk google, it's time to bot! - Hadar Franco, Albert + Stav Levi, Monday
Ok google, it's time to bot! - Hadar Franco, Albert + Stav Levi, MondayDroidConTLV
 
Introduction to React Native - Lev Vidrak, Wix
Introduction to React Native - Lev Vidrak, WixIntroduction to React Native - Lev Vidrak, Wix
Introduction to React Native - Lev Vidrak, WixDroidConTLV
 
Bang-Bang, you have been hacked - Yonatan Levin, KolGene
Bang-Bang, you have been hacked - Yonatan Levin, KolGeneBang-Bang, you have been hacked - Yonatan Levin, KolGene
Bang-Bang, you have been hacked - Yonatan Levin, KolGeneDroidConTLV
 
Educating your app – adding ML edge to your apps - Maoz Tamir
Educating your app – adding ML edge to your apps - Maoz TamirEducating your app – adding ML edge to your apps - Maoz Tamir
Educating your app – adding ML edge to your apps - Maoz TamirDroidConTLV
 
Constraint-ly motion - making your app dance - John Hoford, Google
Constraint-ly motion - making your app dance - John Hoford, GoogleConstraint-ly motion - making your app dance - John Hoford, Google
Constraint-ly motion - making your app dance - John Hoford, GoogleDroidConTLV
 

More from DroidConTLV (20)

Mobile Development in the Information Age - Yossi Elkrief, Nike
Mobile Development in the Information Age - Yossi Elkrief, NikeMobile Development in the Information Age - Yossi Elkrief, Nike
Mobile Development in the Information Age - Yossi Elkrief, Nike
 
Doing work in the background - Darryn Campbell, Zebra Technologies
Doing work in the background - Darryn Campbell, Zebra TechnologiesDoing work in the background - Darryn Campbell, Zebra Technologies
Doing work in the background - Darryn Campbell, Zebra Technologies
 
No more video loss - Alex Rivkin, Motorola Solutions
No more video loss - Alex Rivkin, Motorola SolutionsNo more video loss - Alex Rivkin, Motorola Solutions
No more video loss - Alex Rivkin, Motorola Solutions
 
Mobile at Scale: from startup to a big company - Dor Samet, Booking.com
Mobile at Scale: from startup to a big company - Dor Samet, Booking.comMobile at Scale: from startup to a big company - Dor Samet, Booking.com
Mobile at Scale: from startup to a big company - Dor Samet, Booking.com
 
MVVM In real life - Lea Cohen Tannoudji, Lightricks
MVVM In real life - Lea Cohen Tannoudji, LightricksMVVM In real life - Lea Cohen Tannoudji, Lightricks
MVVM In real life - Lea Cohen Tannoudji, Lightricks
 
Best Practices for Using Mobile SDKs - Lilach Wagner, SafeDK (AppLovin)
Best Practices for Using Mobile SDKs - Lilach Wagner, SafeDK (AppLovin)Best Practices for Using Mobile SDKs - Lilach Wagner, SafeDK (AppLovin)
Best Practices for Using Mobile SDKs - Lilach Wagner, SafeDK (AppLovin)
 
Building Apps with Flutter - Hillel Coren, Invoice Ninja
Building Apps with Flutter - Hillel Coren, Invoice NinjaBuilding Apps with Flutter - Hillel Coren, Invoice Ninja
Building Apps with Flutter - Hillel Coren, Invoice Ninja
 
New Android Project: The Most Important Decisions - Vasiliy Zukanov
New Android Project: The Most Important Decisions - Vasiliy ZukanovNew Android Project: The Most Important Decisions - Vasiliy Zukanov
New Android Project: The Most Important Decisions - Vasiliy Zukanov
 
Designing a Design System - Shai Mishali, Gett
Designing a Design System - Shai Mishali, GettDesigning a Design System - Shai Mishali, Gett
Designing a Design System - Shai Mishali, Gett
 
The Mighty Power of the Accessibility Service - Guy Griv, Pepper
The Mighty Power of the Accessibility Service - Guy Griv, PepperThe Mighty Power of the Accessibility Service - Guy Griv, Pepper
The Mighty Power of the Accessibility Service - Guy Griv, Pepper
 
Kotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDev
Kotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDevKotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDev
Kotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDev
 
Flutter State Management - Moti Bartov, Tikal
Flutter State Management - Moti Bartov, TikalFlutter State Management - Moti Bartov, Tikal
Flutter State Management - Moti Bartov, Tikal
 
Reactive UI in android - Gil Goldzweig Goldbaum, 10bis
Reactive UI in android - Gil Goldzweig Goldbaum, 10bisReactive UI in android - Gil Goldzweig Goldbaum, 10bis
Reactive UI in android - Gil Goldzweig Goldbaum, 10bis
 
Fun with flutter animations - Divyanshu Bhargava, GoHighLevel
Fun with flutter animations - Divyanshu Bhargava, GoHighLevelFun with flutter animations - Divyanshu Bhargava, GoHighLevel
Fun with flutter animations - Divyanshu Bhargava, GoHighLevel
 
DroidconTLV 2019
DroidconTLV 2019DroidconTLV 2019
DroidconTLV 2019
 
Ok google, it's time to bot! - Hadar Franco, Albert + Stav Levi, Monday
Ok google, it's time to bot! - Hadar Franco, Albert + Stav Levi, MondayOk google, it's time to bot! - Hadar Franco, Albert + Stav Levi, Monday
Ok google, it's time to bot! - Hadar Franco, Albert + Stav Levi, Monday
 
Introduction to React Native - Lev Vidrak, Wix
Introduction to React Native - Lev Vidrak, WixIntroduction to React Native - Lev Vidrak, Wix
Introduction to React Native - Lev Vidrak, Wix
 
Bang-Bang, you have been hacked - Yonatan Levin, KolGene
Bang-Bang, you have been hacked - Yonatan Levin, KolGeneBang-Bang, you have been hacked - Yonatan Levin, KolGene
Bang-Bang, you have been hacked - Yonatan Levin, KolGene
 
Educating your app – adding ML edge to your apps - Maoz Tamir
Educating your app – adding ML edge to your apps - Maoz TamirEducating your app – adding ML edge to your apps - Maoz Tamir
Educating your app – adding ML edge to your apps - Maoz Tamir
 
Constraint-ly motion - making your app dance - John Hoford, Google
Constraint-ly motion - making your app dance - John Hoford, GoogleConstraint-ly motion - making your app dance - John Hoford, Google
Constraint-ly motion - making your app dance - John Hoford, Google
 

Recently uploaded

Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupFlorian Wilhelm
 
My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024The Digital Insurer
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsMemoori
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Commit University
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 3652toLead Limited
 
Build your next Gen AI Breakthrough - April 2024
Build your next Gen AI Breakthrough - April 2024Build your next Gen AI Breakthrough - April 2024
Build your next Gen AI Breakthrough - April 2024Neo4j
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationSlibray Presentation
 
costume and set research powerpoint presentation
costume and set research powerpoint presentationcostume and set research powerpoint presentation
costume and set research powerpoint presentationphoebematthew05
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):comworks
 
Science&tech:THE INFORMATION AGE STS.pdf
Science&tech:THE INFORMATION AGE STS.pdfScience&tech:THE INFORMATION AGE STS.pdf
Science&tech:THE INFORMATION AGE STS.pdfjimielynbastida
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitecturePixlogix Infotech
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationRidwan Fadjar
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 
New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024
New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024
New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024BookNet Canada
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...Fwdays
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsMiki Katsuragi
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebUiPathCommunity
 
Artificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning eraArtificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning eraDeakin University
 

Recently uploaded (20)

Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project Setup
 
My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial Buildings
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 
Hot Sexy call girls in Panjabi Bagh 🔝 9953056974 🔝 Delhi escort Service
Hot Sexy call girls in Panjabi Bagh 🔝 9953056974 🔝 Delhi escort ServiceHot Sexy call girls in Panjabi Bagh 🔝 9953056974 🔝 Delhi escort Service
Hot Sexy call girls in Panjabi Bagh 🔝 9953056974 🔝 Delhi escort Service
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
 
Build your next Gen AI Breakthrough - April 2024
Build your next Gen AI Breakthrough - April 2024Build your next Gen AI Breakthrough - April 2024
Build your next Gen AI Breakthrough - April 2024
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck Presentation
 
costume and set research powerpoint presentation
costume and set research powerpoint presentationcostume and set research powerpoint presentation
costume and set research powerpoint presentation
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):
 
Science&tech:THE INFORMATION AGE STS.pdf
Science&tech:THE INFORMATION AGE STS.pdfScience&tech:THE INFORMATION AGE STS.pdf
Science&tech:THE INFORMATION AGE STS.pdf
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC Architecture
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 Presentation
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 
New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024
New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024
New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering Tips
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio Web
 
Artificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning eraArtificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning era
 

LiveData on Steroids - Giora Shevach + Shahar Ben Moshe, Climacell

  • 1. 1 LiveData on Steroids Taking LiveData to the next level Giora Shevach & Shahar Ben Moshe
  • 2.
  • 4. 4 ● Observable data holder LiveData (vanilla)
  • 5. 5 LiveData (vanilla) ● Observable data holder ● Lifecycle-aware ○ Auto handling of lifecycle-related stuff ○ Avoids stupid crashes ○ Avoids memory leaks ○ App doesn’t do more work than it
  • 6. 6 LiveData (vanilla) ● Observable data holder ● Lifecycle-aware ○ Auto handling of lifecycle-related stuff ○ Avoids stupid crashes ○ Avoids memory leaks ○ App doesn’t do more work than it
  • 7. 7
  • 8. 8
  • 9. class WeatherDashboardActivity : Activity() { private lateinit viewModel: WeatherDashboardViewModel override fun onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) viewModel = ViewModelProviders.of(this) .get(WeatherDashboardViewModel::java.class) observeWeather() } private fun observeWeather() { viewModel.weather.observe(this, Observer { // it: Weather showTemperatureInBig(it.temperature) }) } } class WeatherDashboardViewModel : ViewModel { val weather: LiveData<Weather> = weatherRepo.weather } class WeatherRepo { val weather = MutableLiveData<Weather>() // code to manipulate weather }
  • 10. class WeatherDashboardActivity : Activity() { private lateinit viewModel: WeatherDashboardViewModel override fun onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) viewModel = ViewModelProviders.of(this) .get(WeatherDashboardViewModel::java.class) observeWeather() } private fun observeWeather() { viewModel.weather.observe(this, Observer { // it: Weather showTemperatureInBig(it.temperature) }) } } class WeatherDashboardViewModel : ViewModel { val weather: LiveData<Weather> = weatherRepo.weather } class WeatherRepo { val weather = MutableLiveData<Weather>() // code to manipulate weather }
  • 11. class WeatherDashboardActivity : Activity() { private lateinit viewModel: WeatherDashboardViewModel override fun onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) viewModel = ViewModelProviders.of(this) .get(WeatherDashboardViewModel::java.class) observeWeather() } private fun observeWeather() { viewModel.weather.observe(this, Observer { // it: Weather showTemperatureInBig(it.temperature) }) } } class WeatherDashboardViewModel : ViewModel { val weather: LiveData<Weather> = weatherRepo.weather } class WeatherRepo { val weather = MutableLiveData<Weather>() // code to manipulate weather }
  • 12. class WeatherDashboardActivity : Activity() { private lateinit viewModel: WeatherDashboardViewModel override fun onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) viewModel = ViewModelProviders.of(this) .get(WeatherDashboardViewModel::java.class) observeWeather() } private fun observeWeather() { viewModel.weather.observe(this, Observer { // it: Weather showTemperatureInBig(it.temperature) }) } } class WeatherDashboardViewModel : ViewModel { val weather: LiveData<Weather> = weatherRepo.weather } class WeatherRepo { val weather = MutableLiveData<Weather>() // code to manipulate weather }
  • 13. class WeatherDashboardActivity : Activity() { private lateinit viewModel: WeatherDashboardViewModel override fun onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) viewModel = ViewModelProviders.of(this) .get(WeatherDashboardViewModel::java.class) observeWeather() } private fun observeWeather() { viewModel.weather.observe(this, Observer { // it: Weather showTemperatureInBig(it.temperature) }) } } class WeatherDashboardViewModel : ViewModel { val weather: LiveData<Weather> = weatherRepo.weather } class WeatherRepo { val weather = MutableLiveData<Weather>() // code to manipulate weather }
  • 14. LiveData - where it falls short
  • 15. 15 WeatherDashboardActivity viewModel.weather.observe(...) WeatherDashboardViewModel val weather: LiveData<Weather> = weatherRepo.weather WeatherRepo val weather = MutableLiveData<Weather>() fun fetchNewWeather() { // do some network stuff… weather.value = networkResponse.data } AppLifecycleObserver @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun onForeground() { weatherRepo.fetchNewWeather() } val weatherError = MutableLiveData<Exception>() val weatherError = weatherRepo.weatherError viewModel.weatherError.observe(...)
  • 16. fun observeWeatherAndOtherThingsAsWell() { viewModel.weather.observe(this, Observer { weather -> // : Weather viewModel.weatherError.observe(this, Observer { weatherError -> // : Exception viewModel.weatherLoading.observe(this, Observer {weatherLoading -> // : Boolean if (weatherError != null && weatherError.data != lastError) { showErrorMessage() lastError = weatherError.data } else { if (weatherLoading) { showLoader() } else { if (weather != null) { showTemperatureInBig(weather.temperature) } else { showErrorMessage() } } } }) }) })
  • 18.
  • 19. 19 StatefulLiveData ⬢ How we built it ⬢ How to use it ⬢ How it solves common scenarios in daily app operation ⬢ Integration in existing code & libraries ⬢ Super powers
  • 20. 20 ❏ Get updates upon data changes ❏ Be lifecycle-aware ❏ Support every data type ❏ Know when the data is retrieved successfully and ready to use ❏ Know when the data is loading, and supply some partial data ❏ Know when an error occurred, and know what the error is Requirements StatefulLiveData
  • 21. StatefulData abstract class StatefulData<T> { class Success<T>(val data: T) : StatefulData<T>() class Error<T>(val throwable: Throwable) : StatefulData<T>() class Loading<T>(val loadingData: Any?) : StatefulData<T>() } ❏ Get updates upon data changes ❏ Be lifecycle-aware ❏ Support every data type ❏ Know when the data is retrieved successfully and ready to use ❏ Know when the data is loading, and supply some partial data ❏ Know when an error occurred, and know what the error is
  • 22. StatefulData abstract class StatefulData<T> { class Success<T>(val data: T) : StatefulData<T>() class Error<T>(val throwable: Throwable) : StatefulData<T>() class Loading<T>(val loadingData: Any?) : StatefulData<T>() } ❏ Get updates upon data changes ❏ Be lifecycle-aware ✓ Support every data type ✓ Know when the data is retrieved successfully and ready to use ✓ Know when the data is loading, and supply some partial data ✓ Know when an error occurred, and know what the error is
  • 23. StatefulLiveData LiveData<StatefulData<T>> typealias StatefulLiveData<T> = LiveData<StatefulData<T>> typealias MutableStatefulLiveData<T> = MutableLiveData<StatefulData<T>> typealias MediatorStatefulLiveData<T> = MediatorLiveData<StatefulData<T>>
  • 24.
  • 25. WeatherDashboardActivity viewModel.weather.observe(...) WeatherDashboardViewModel val weather: LiveData<Weather> = weatherRepo.weather WeatherRepo val weather = MutableLiveData<Weather>() fun fetchNewWeather() { // do some network stuff… weather.value = networkResponse.data } AppLifecycleObserver @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun onForeground() { weatherRepo.fetchNewWeather() } val weatherError= MutableLiveData<Exception>() val weatherError= weatherRepo.weatherError viewModel.weatherError.observe(...)
  • 26. WeatherDashboardActivity viewModel.weather.observe(...) WeatherDashboardViewModel val weather: StatefulLiveData<Weather> = weatherRepo.weather WeatherRepo val weather = MutableStatefulLiveData<Weather>() fun fetchNewWeather() { // do some network stuff… weather.value = networkResponse.data } AppLifecycleObserver @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun onForeground() { weatherRepo.fetchNewWeather() }
  • 27. class WeatherDashboardActivity : Activity() private fun observeWeather() { viewModel.weather.observe(this, Observer { //it: StatefulData<Weather> When (it) { is Success -> { // it.data: Weather showTemperatureInBig(it.data.temperature) } is Loading -> { // it.loadingData: Any? showLocationName(it.loadingData as? String) showLoader() } is Error -> { // it.throwable: Throwable showErrorMessage() log(it.throwable) } } }) } }
  • 28. class WeatherDashboardActivity : Activity() private fun observeWeather() { viewModel.weather.observe(this, Observer { //it: StatefulData<Weather> When (it) { is Success -> { // it.data: Weather showTemperatureInBig(it.data.temperature) } is Loading -> { // it.loadingData: Any? showLocationName(it.loadingData as? String) showLoader() } is Error -> { // it.throwable: Throwable showErrorMessage() log(it.throwable) } } }) } }
  • 29. class WeatherDashboardActivity : Activity() private fun observeWeather() { viewModel.weather.observe(this, Observer { //it: StatefulData<Weather> When (it) { is Success -> { // it.data: Weather showTemperatureInBig(it.data.temperature) } is Loading -> { // it.loadingData: Any? showLocationName(it.loadingData as? String) showLoader() } is Error -> { // it.throwable: Throwable showErrorMessage() log(it.throwable) } } }) } }
  • 30. class WeatherDashboardActivity : Activity() private fun observeWeather() { viewModel.weather.observe(this, Observer { //it: StatefulData<Weather> When (it) { is Success -> { // it.data: Weather showTemperatureInBig(it.data.temperature) } is Loading -> { // it.loadingData: Any? showLocationName(it.loadingData as? String) showLoader() } is Error -> { // it.throwable: Throwable showErrorMessage() log(it.throwable) } } }) } }
  • 33. class WeatherDasboardActivity : Activity() { private fun observeWeather() { viewModel.weather.observe(this, Observer { //it: StatefulData<Weather> When (it) { is Success -> showTemperatureInBig(it.data.temperature) // it.data: Weather is Loading -> { showLocationName(it.loadingData as? String) // it.loadingData: Any? showLoader() } is Error -> { showErrorMessage() log(it.throwable) // it.throwable: Throwable } } }) } }
  • 34. 34 observeSuccess class WeatherDasboardActivity : Activity() { private fun observeWeather() { viewModel.weather.observeSuccess(this, Observer { //it: Weather showTemperatureInBig(it.temperature) }) } }
  • 35. 35 observeLoading, observeError class WeatherDasboardActivity : Activity() { private fun observeWeather() { viewModel.weather.observeLoading(this, Observer { //it: Any? showLocationName(it as? String) showLoader() }) } } class WeatherDasboardActivity : Activity() { private fun observeWeather() { viewModel.weather.observeError(this, Observer { //it: Throwable showErrorMessage() log(it) }) } }
  • 37. class WeatherDasboardActivity : Activity() { private fun observeWeather() { viewModel.weather.observe(this, Observer { //it: StatefulData<Weather> When (it) { is Success -> showTemperatureInBig(it.data.temperature) // it.data: Weather is Loading -> { showLocationName(it.loadingData as? String) // it.loadingData: Any? showLoader() } is Error -> { showErrorMessage() log(it.throwable) // it.throwable: Throwable } } }) } }
  • 38. class WeatherDasboardActivity : Activity() { private fun observeWeather() { viewModel.weather.subscribe(this) .onSuccess{ // it: Weather showTemperatureInBig(it.temperature) } .onError{ // it: Throwable showErrorMessage() log(it) } .observe() } }
  • 39. class WeatherDasboardActivity : Activity() { private fun observeWeather() { viewModel.weather.subscribe(this) .onSuccess{ // it: Weather showTemperatureInBig(it.temperature) } .onError{ // it: Throwable showErrorMessage() log(it) } .observe() } }
  • 40. class WeatherDasboardActivity : Activity() { private fun observeWeather() { viewModel.weather.subscribe(this) .onSuccess{ // it: Weather showTemperatureInBig(it.temperature) } .onError{ // it: Throwable showErrorMessage() log(it) } .observe() } }
  • 41. class WeatherDasboardActivity : Activity() { private fun observeWeather() { viewModel.weather.subscribe(this) .onSuccess{ // it: Weather showTemperatureInBig(it.temperature) } .onError{ // it: Throwable showErrorMessage() log(it) } .observe() } }
  • 42. class WeatherDasboardActivity : Activity() { private fun observeWeather() { viewModel.weather.subscribe(this) .onSuccess{ // it: Weather showTemperatureInBig(it.temperature) } .onError{ // it: Throwable showErrorMessage() log(it) } .observe() } }
  • 43.
  • 44. 44 Transformations.Map class MyViewModel() : ViewModel() { val temperature: StatefulLiveData<Temperature> = repo.weather.map { // it : Weather It.temperature ^map } }
  • 45. 45 Transformations.Map class MyViewModel() : ViewModel() { val temperature: StatefulLiveData<Temperature> = repo.weather.map { // it : Weather It.temperature ^map } } Transformations.SwitchMap class MyViewModel() : ViewModel() { val radarImage: StatefulLiveData<List<RadarImage>> = repo.weather.switchMap { // it : Weather repo.getRadarImages(it.latLng) // ^switchMap } }
  • 46. 46 class MyViewModel() : ViewModel() { val temperature: StatefulLiveData<Temperature> = repo.weather.map(myCoroutineScope) { // it : Weather It.temperature ^map } } Transformations.MapWithCoroutine
  • 48. 48 typealias MediatorStatefulLiveData<T> = MediatorLiveData<StatefulData<T>> StatefulLiveDataMediator
  • 49. 49 typealias MediatorStatefulLiveData<T> = MediatorLiveData<StatefulData<T>> StatefulLiveDataMediator class MyViewModel() : ViewModel() { val fusedForecast = MediatorStatefulLiveData<Forecast>().apply { addSource(repo.todayForecast) { // it: Forecast // handle data } addSource(repo.tomorrowForecast) { // it: StatefulData<Forecast> // handle stateful data } } }
  • 50.
  • 51. 51 class WeatherDashboardViewModel : ViewModel() { private val vanillaWeather: LiveData<Weather> = repo.weather.mapToLiveData() } mapToLiveData
  • 52. 52 mapToLiveData class WeatherDasboardViewModel : ViewModel() { private val vanillaWeather: LiveData<Weather> = repo.weather .mapToLiveData(errorMapFunction = { // it : Throwable return Weather() }) } fun <T> StatefulLiveData<T>.mapToLiveData( errorMapFunction: (Throwable) -> T? = { _ -> null }, loadingMapFunction: (Any?) -> T? = { _ -> null }, fallbackMapFunction: () -> T? = { null } ): LiveData<T>
  • 53. Put
  • 54. 54 Put fun <T> MutableStatefulLiveData<T>.putData(data: T) fun <T> MutableStatefulLiveData<T>.putLoading(loadingData: Any?)? = null) fun <T> MutableStatefulLiveData<T>.putError(error: Throwable)
  • 55. 55 Put fun <T> MutableStatefulLiveData<T>.putData(data: T) fun <T> MutableStatefulLiveData<T>.putLoading(loadingFunction: (() -> Any?)? = null) fun <T> MutableStatefulLiveData<T>.putError(error: Throwable)
  • 57. 57 class WeatherDasboardViewModel : ViewModel() { private val mutableWeather = MutableStatefulLiveData<Weather>() private fun setWeatherLoading() { mutableWeather.putLoading() //loadingData = null mutableWeather.putLoading(”Please wait...”) // loadingData: String mutableWeather.putLoading(53) // loadingData: Int - show % progress mutableWeather.putLoading(oldWeather) // loadingData: Weather } } loadingData
  • 58. 58 class WeatherDasboardViewModel : ViewModel() { private val mutableWeather = MutableStatefulLiveData<Weather>() private fun setWeatherLoading() { mutableWeather.putLoading() //loadingData = null mutableWeather.putLoading(”Please wait...”) // loadingData: String mutableWeather.putLoading(53) // loadingData: Int - show % progress mutableWeather.putLoading(oldWeather) // loadingData: Weather } } loadingData
  • 59. 59 class WeatherDasboardViewModel : ViewModel() { private val mutableWeather = MutableStatefulLiveData<Weather>() private fun setWeatherLoading() { mutableWeather.putLoading() //loadingData = null mutableWeather.putLoading(”Please wait...”) // loadingData: String mutableWeather.putLoading(53) // loadingData: Int - show % progress mutableWeather.putLoading(oldWeather) // loadingData: Weather } } loadingData
  • 60. 60 class WeatherDasboardViewModel : ViewModel() { private val mutableWeather = MutableStatefulLiveData<Weather>() private fun setWeatherLoading() { mutableWeather.putLoading() //loadingData = null mutableWeather.putLoading(”Please wait...”) // loadingData: String mutableWeather.putLoading(53) // loadingData: Int - show % progress mutableWeather.putLoading(oldWeather) // loadingData: Weather } } loadingData
  • 61. 61 fun <T> LiveData<T>.observeOnce( observer: Observer<T>, retainForLoadingState: Boolean = true) observeOnce
  • 62.
  • 63. 63 Stateful.Live.Data.Call.Adapter fun createClient(baseUrl: String): Retrofit { return Retrofit .Builder() .baseUrl(baseUrl) .addCallAdapterFactory(StatefulLiveDataCallAdapterFactory.create()) .build() }
  • 64. 64 Stateful.Live.Data.Call.Adapter fun createClient(baseUrl: String): Retrofit { return Retrofit .Builder() .baseUrl(baseUrl) .addCallAdapterFactory(StatefulLiveDataCallAdapterFactory.create()) .build() }
  • 65. 65 Stateful.Live.Data.Call.Adapter fun createClient(baseUrl: String): Retrofit { return Retrofit .Builder() .baseUrl(baseUrl) .addCallAdapterFactory(StatefulLiveDataCallAdapterFactory.create()) .build() } interface MyRetrofitService { @GET("weather/current") fun getWeather(): Call<Weather> }
  • 66. 66 Stateful.Live.Data.Call.Adapter fun createClient(baseUrl: String): Retrofit { return Retrofit .Builder() .baseUrl(baseUrl) .addCallAdapterFactory(StatefulLiveDataCallAdapterFactory.create()) .build() } interface MyRetrofitService { @GET("weather/current") fun getWeather(): StatefulLiveData<Weather> }
  • 67. 67 class FirestoreRepo { private val firestoreDb: FirebaseFirestore = … // init FirebaseFirestore fun getUser(userId: String) : Task<User> { val getUserTask = firestoreDb .collection(“Users”) .whereEqualTo(“userId”, userId) return getUserTask } } Task<T>.toStatefulLiveData
  • 68. 68 class FirestoreRepo { private val firestoreDb: FirebaseFirestore = … // init FirebaseFirestore fun getUser(userId: String) : StatefulLiveData<User> { val getUserTask = firestoreDb .collection(“Users”) .whereEqualTo(“userId”, userId) return getUserTask.toStatefulLiveData() } } Task<T>.toStatefulLiveData
  • 69. 69 Task<T>.toStatefulLiveData class FirestoreRepo { private val firestoreDb: FirebaseFirestore = … // init FirebaseFirestore fun getUser(userId: String) : StatefulLiveData<Account> { val getUserTask = firestoreDb .collection(“Users”) .whereEqualTo(“userId”, userId) return getUserTask.toStatefulLiveData { // User: task result val userAccount: Account = … // map User -> Account userAccount ^toStatefulLiveData } } }
  • 71. 71 class LoadingWithPercent(val percent: Int) : StatefulData<Int>()
  • 72. 72 class LoadingWithPercent(val percent: Int) : StatefulData<Int>() class WeatherDashboardActivity : Activity() { private fun observeWeather() { viewModel.weather.observe(this, Observer { //it: StatefulData<Weather> When (it) { is ... -> {...} is LoadingWithPercent -> { showProgress(it.percent) // it.percent: Int } } }) } }
  • 73. 73 class LoadingWithPercent(val percent: Int) : StatefulData<Int>() fun <T> MutableStatefulLiveData<T>.putLoadingWithPercent(percent: Int) {...} fun <T> StatefulLiveData<T>.observeLoadingWithPercent(owner: LifecycleOwner, observer: Observer<in Any?>): StatefulLiveData<T> {...}
  • 74. 74 Event bus Callback listener Rx StatefulLiveData Life cycle aware X X X Low learning curve X Lean X X Data retention X X Memory leaks free X X X
  • 75. 75 Use the right tool for the job (or make one if it doesn’t exist)
  • 76. 76 Got get Stateful, It’s Live! https://github.com/climacell/statefullivedata Thank you!