Orhan Tozan
03/26/2020, 3:53 PM// option 1
interface HomeViewModel {
fun onLoginButtonClick()
}
// option 2
interface HomeViewModel {
val loginButtonClicks: SendChannel<ButtonClick>
object ButtonClick
}
// option 3
interface HomeViewModel {
val loginButtonClicks: Flow<ButtonClick>
object ButtonClick
}zak.taccardi
03/26/2020, 3:54 PMzak.taccardi
03/26/2020, 3:54 PMinterface HomeViewModel {
fun send(intention: Intention)
}
sealed class Intention {
object ButtonClick : Intention()
}Orhan Tozan
03/26/2020, 3:56 PMOrhan Tozan
03/26/2020, 3:56 PMzak.taccardi
03/26/2020, 3:56 PMSendChannel<T> too, except you don’t want to expose the ability to close that channelzak.taccardi
03/26/2020, 3:57 PMsealed class representing all forms of input so you can use an exhaustive when to process itzak.taccardi
03/26/2020, 3:57 PMzak.taccardi
03/26/2020, 3:57 PMzak.taccardi
03/26/2020, 3:58 PMHomeViewModel just needs to process those input events in order and to build a Flow<State> for your UI to consumezak.taccardi
03/26/2020, 3:59 PMactor coroutine to process input events and build state and it is awesome
https://kotlinlang.org/docs/reference/coroutines/shared-mutable-state-and-concurrency.html#actorsOrhan Tozan
03/26/2020, 4:00 PMzak.taccardi
03/26/2020, 4:01 PMViewModel can expose any number, but I generally just expose 1 flow of statezak.taccardi
03/26/2020, 4:03 PMRepository classes and reduce them into a single state for your UIzak.taccardi
03/26/2020, 4:06 PMViewModel classes to a user flow rather than a UI component such as an Activity or Fragment. This allows me to inject multiple view models into each other or observe one view model for FragmentA in FragmentBOrhan Tozan
03/26/2020, 4:08 PMsend method, the view is allowed to send a dialogbuttonclickEvent when the dialog is not shown. However, I thought of the following:
interface ViewModel {
val dialogState: Flow<DialogState>
sealed class DialogState {
object NotShown : DialogState()
class Shown : DialogState() {
val buttonClicks: Flow<..>
// or
val buttonClicks: SendChannel<..>
}
}
}Orhan Tozan
03/26/2020, 4:09 PMzak.taccardi
03/26/2020, 4:10 PMState is always a data class for mezak.taccardi
03/26/2020, 4:10 PMzak.taccardi
03/26/2020, 4:11 PMsealed class can work with simple stateszak.taccardi
03/26/2020, 4:11 PMdialogbuttonclickEvent matter if the view isn’t shown?zak.taccardi
03/26/2020, 4:12 PMViewModel should process it. and it could decide to drop itOrhan Tozan
03/26/2020, 4:12 PMzak.taccardi
03/26/2020, 4:12 PMwasyl
03/26/2020, 4:13 PMOrhan Tozan
03/26/2020, 4:14 PMview.send(dialogbuttonclickEvent) , like you suggested @zak.taccardi initiallyzak.taccardi
03/26/2020, 4:15 PMOrhan Tozan
03/26/2020, 4:16 PMviewmodel.sent(dialogbuttonclickEvent) while viewmodel.dialogState == NotShownzak.taccardi
03/26/2020, 4:16 PMdialogButtonClickEvent, do not update the current State under that conditionOrhan Tozan
03/26/2020, 4:17 PMzak.taccardi
03/26/2020, 4:18 PMOrhan Tozan
03/26/2020, 4:19 PMOrhan Tozan
03/26/2020, 4:19 PMinterface ViewModel {
val dialogState: Flow<DialogState>
sealed class DialogState {
object NotShown : DialogState()
class Shown : DialogState() {
val buttonClicks: Flow<..>
// or
val buttonClicks: SendChannel<..>
}
}
}
This way, button dialog click event can only be sent if the dialog state is Shownzak.taccardi
03/26/2020, 4:20 PMclass Shown : DialogState() {
val buttonClicks: Flow<..>
How does a Flow<T> live on a State which is effectively a data class?Orhan Tozan
03/26/2020, 4:20 PMOrhan Tozan
03/26/2020, 4:20 PMzak.taccardi
03/26/2020, 4:21 PMzak.taccardi
03/26/2020, 4:22 PMState + Intention = (new) State. And the View or the ViewModel itself can send Intention objects to update that StateOrhan Tozan
03/26/2020, 4:23 PMOrhan Tozan
03/26/2020, 4:24 PMOrhan Tozan
03/26/2020, 4:25 PMzak.taccardi
03/26/2020, 4:25 PMzak.taccardi
03/26/2020, 4:25 PMzak.taccardi
03/26/2020, 4:26 PMOrhan Tozan
03/26/2020, 4:26 PMOrhan Tozan
03/26/2020, 4:27 PMzak.taccardi
03/26/2020, 4:27 PMFlow<Data> thoughOrhan Tozan
03/26/2020, 4:28 PMzak.taccardi
03/26/2020, 4:28 PMdata class State(val showDialog: Boolean)Orhan Tozan
03/26/2020, 4:28 PMOrhan Tozan
03/26/2020, 4:29 PMzak.taccardi
03/26/2020, 4:30 PMzak.taccardi
03/26/2020, 4:30 PMState anymoreOrhan Tozan
03/26/2020, 4:31 PMzak.taccardi
03/26/2020, 4:31 PMwasyl
03/26/2020, 4:31 PMOrhan Tozan
03/26/2020, 4:32 PMzak.taccardi
03/26/2020, 4:32 PMViewModel are valid. you mean the View will only be able to consider intentions appropriate for that Statezak.taccardi
03/26/2020, 4:32 PMwasyl
03/26/2020, 4:33 PMall intentions for the ViewModel are validI’d argue it’s view model’s responsibility to define which intentions are valid and which are not. View should be only an implementation detail and it shouldn’t be able to do anything that view model doesn’t expect or allow
zak.taccardi
03/26/2020, 4:34 PMIntention would be a sealed class on the ViewModel. This defines what can be used to update the `ViewModel`’s state. If it’s not able to update the `ViewModel`’s state, then it’s not a valid intentionwasyl
03/26/2020, 4:35 PMzak.taccardi
03/26/2020, 4:35 PM@Compose worldwasyl
03/26/2020, 4:35 PM@Compose is an implementation detail over UI. It shouldn’t make any difference for the view model implementation 😄Orhan Tozan
03/26/2020, 4:36 PM@Compose is used or not is a view implementation detail, like @wasyl states alsoOrhan Tozan
03/26/2020, 4:36 PMzak.taccardi
03/26/2020, 4:37 PM(<http://Intention.Xyz|Intention.Xyz>) -> Unit passed in with your State to send eventszak.taccardi
03/26/2020, 4:37 PMSendChannel<Intention.Xyz> is worth itzak.taccardi
03/26/2020, 4:38 PMsuspend from the UI would work well because it could be cancelledzak.taccardi
03/26/2020, 4:38 PMwasyl
03/26/2020, 4:39 PMwasyl
03/26/2020, 4:40 PMzak.taccardi
03/26/2020, 4:40 PMzak.taccardi
03/26/2020, 4:41 PMif (currentWhatever == newWhatever) then update UIzak.taccardi
03/26/2020, 4:42 PMFlow<EntireViewModelState> and I love it to be honestzak.taccardi
03/26/2020, 4:43 PMFlow<T> where possible as a UI implementation detailzak.taccardi
03/26/2020, 4:43 PMOrhan Tozan
03/26/2020, 4:44 PMOrhan Tozan
03/26/2020, 4:46 PM(currentWhatever == newWhatever) on everything.zak.taccardi
03/26/2020, 4:46 PMState + Intention = (new) State. Whether its a repository or a view model. I kind of see ViewModel as just a repository for a specific UI componentOrhan Tozan
03/26/2020, 4:47 PMState + (current) Intention = (new) State + (new) Intention.zak.taccardi
03/26/2020, 4:47 PMzak.taccardi
03/26/2020, 4:48 PMIntention is just an instance that describes how you update a Statezak.taccardi
03/26/2020, 4:48 PMState + Intention = State is the reducer function that just gets called repeatedly every time an Intention is sentzak.taccardi
03/26/2020, 4:49 PMIntention instances aren’t stored, they are processed then GC’dzak.taccardi
03/26/2020, 4:49 PMOrhan Tozan
03/26/2020, 4:50 PMzak.taccardi
03/26/2020, 4:50 PMFlow<Boolean> effectivelyOrhan Tozan
03/26/2020, 4:50 PMconst toggleMachine = createMachine({
id: 'toggle',
initial: 'inactive',
states: {
inactive: { on: { TOGGLE: 'active' } },
active: { on: { TOGGLE: 'inactive' } }
}
});Orhan Tozan
03/26/2020, 4:50 PMOrhan Tozan
03/26/2020, 4:51 PMzak.taccardi
03/26/2020, 4:52 PMzak.taccardi
03/26/2020, 4:55 PMOrhan Tozan
03/26/2020, 4:56 PMval clickCounter: Flow<Int> = buttonClicks.map {
clickCounter + 1
}
That's the concept, I just don't know the best way to do it with coroutineszak.taccardi
03/26/2020, 4:58 PMval states: Flow<State>
val sendInput: (Input) -> Unit
But if Input is a sealed class, there can be a pretty complex number of implementationszak.taccardi
03/26/2020, 4:59 PMval clickCounter: Flow<Int> = buttonClicks.map {
clickCounter + 1
}zak.taccardi
03/26/2020, 4:59 PMzak.taccardi
03/26/2020, 4:59 PMzak.taccardi
03/26/2020, 5:00 PMOrhan Tozan
03/26/2020, 5:01 PMOrhan Tozan
03/26/2020, 5:01 PMOrhan Tozan
03/26/2020, 5:02 PMpackage com.jakewharton.presentation
interface Presenter<ModelT : Any, EventT : Any> {
val models: ReceiveChannel<ModelT>
val events: SendChannel<EventT>
suspend fun start()
}Orhan Tozan
03/26/2020, 5:02 PMdewildte
03/26/2020, 5:56 PMzak.taccardi
03/26/2020, 5:56 PMChannel<T>. What do you mean?dewildte
03/26/2020, 5:57 PMdewildte
03/26/2020, 5:59 PMdewildte
03/26/2020, 6:00 PMdewildte
03/26/2020, 6:03 PMzak.taccardi
03/26/2020, 6:05 PMfilterIsInstance() will drop items from your busdewildte
03/26/2020, 6:05 PMdewildte
03/26/2020, 6:05 PMdewildte
03/26/2020, 6:06 PMdewildte
03/26/2020, 6:07 PMdewildte
03/26/2020, 6:07 PMOrhan Tozan
03/27/2020, 5:51 PMOrhan Tozan
03/27/2020, 5:52 PMOrhan Tozan
03/27/2020, 5:52 PM