You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
description: Architecture Framework for Kotlin. Reuse every line of code. Handle all errors automatically. No boilerplate. Analytics, metrics, debugging in 3 lines. 50+ features.
* Avoid using `sealed class`es and use `sealed interface`s whenever possible. Not only this reduces object allocations,
45
-
but also prevents developers from putting excessive logic into their states and/or making private/protected
46
-
properties. State is a simple typed data holder, so if you want to use protected properties or override functions,
47
-
it is likely that something is wrong with your architecture.
48
-
* Use nested class imports and import aliases to clean up your code, as contract class names can be long sometimes.
49
-
* Use value classes to reduce object allocations if your Intents are being sent frequently, such as for text field
50
-
value changes or scroll events.
51
-
* You can use the `updateStateImmediate` function to optimize the
52
-
performance of the store by bypassing all checks and plugins.
53
-
* Overall, there are cases when changes are so frequent that you'll want to just leave some logic on the UI layer to
54
-
avoid polluting the heap with garbage collected objects and keep the UI performant.
55
-
* Avoid subscribing to a bunch of flows in your Store. The best way to implement a reactive UI pattern is to
56
-
use `combine(vararg flows...)` and merge all of your data streams into one flow, and then just use the `transform`
57
-
block to handle the changes.
58
-
* With this, you can be sure that your state is consistent even if you have 20 parallel data streams from different
59
-
sources e.g. database cache, network, websockets and other objects.
60
-
* Avoid using platform-level imports and code in your Store/Container/ViewModel whenever possible. This is optional, but
61
-
if you follow this rule, your **Business logic can be multiplatform**! This is also very good for the architecture.
62
-
* There is an ongoing discussion about whether to name your intents starting with the verb or with the noun.
63
-
* Case 1: `ClickedCounter`
64
-
* Case 2: `CounterClicked`
65
-
In general, this is up to your personal preference, just make sure you use a single style across all of your
66
-
Contracts. I personally like to name intents starting with the verb (Case 1) for easier autosuggestions from the
67
-
IDE.
68
-
69
-
### Opinionated naming design
70
-
45
+
There is an ongoing discussion on how to name Intents/States/Actions.
71
46
Here's an example of rules we use at [Respawn](https://respawn.pro) to name our Contract classes:
72
47
73
48
*`MVIIntent` naming should be `<TypeOfActionInPastTense><Target>`.
@@ -77,8 +52,6 @@ Here's an example of rules we use at [Respawn](https://respawn.pro) to name our
77
52
Do not include `Screen` postfix. `GoToHome`~~Screen~~.
78
53
*`MVIState`s should be named using verbs in present tense using a gerund. Examples: `EditingGame`, `DisplayingSignIn`.
79
54
80
-
## FAQ
81
-
82
55
### My intents are not reduced! When I click buttons, nothing happens, the app just hangs.
83
56
84
57
* Did you call `Store.start(scope: CoroutineScope)`?
@@ -95,27 +68,15 @@ Here's an example of rules we use at [Respawn](https://respawn.pro) to name our
95
68
subscribe to actions.
96
69
4. Try to use an `onUndeliveredIntent` handler of a plugin or install a logging plugin to debug missed events.
97
70
98
-
### Why does `updateState` and `withState` not return the resulting state? Why is there no `state` property I can access?
99
-
100
-
FlowMVI is a framework that enables you to build highly parallel, multi-threaded systems. In such systems, multiple
101
-
threads may modify the state of the `Store` in parallel, leading to data races, thread races, live locks and other
102
-
nasty problems. To prevent that, FlowMVI implements a strategy called "transaction serialization" which only allows
103
-
**one** client at a time to read or modify the state. Because of that, you can be sure that your state won't change
104
-
unexpectedly while you're working with it. However, any state that you pass outside of the scope of `withState` or
105
-
`updateState` should be **considered invalid** immediately. You can read more about serializable state transactions in
106
-
the [article](https://proandroiddev.com/how-to-safely-update-state-in-your-kotlin-apps-bf51ccebe2ef).
107
-
Difficulties that you are facing because of this likely have an easy solution that requires a bit more thinking.
108
-
As you continue working with FlowMVI, updating states safely will come naturally to you.
109
-
110
71
### In what order are intents, plugins and actions processed?
111
72
112
-
* Intents: FIFO or undefined based on the configuration parameter `parallelIntents`.
113
-
* Actions: FIFO.
114
-
* States: FIFO.
115
-
* Plugins: FIFO (Chain of Responsibility) based on installation order.
116
-
* Decorators: FIFO, but after all of the regular plugins.
73
+
* Intents: FIFO or undefined based on the configuration parameter `parallelIntents`
74
+
* Actions: FIFO
75
+
* States: FIFO
76
+
* Plugins: FIFO (Chain of Responsibility) based on installation order
77
+
* Decorators: FIFO, but after all of the regular plugins
117
78
118
-
### When I consume an Action, the other actions are delayed or do not come.
79
+
### When I consume an Action, the other actions are delayed or do not come
119
80
120
81
Since actions are processed sequentially, make sure you launch a coroutine to not prevent other actions from coming and
121
82
suspending the scope. This is particularly obvious with things like snackbars that suspend in compose.
@@ -126,13 +87,12 @@ You shouldn't. Use an Intent / Action to follow the contract, unless you are usi
126
87
In that case, expose the parent `ImmutableContainer` / `ImmutableStore` type to hide the `intent` function from
127
88
subscribers.
128
89
129
-
### How to use paging?
90
+
### How to use androidx.paging?
130
91
131
92
Well, this is a tricky one. `androidx.paging` breaks the architecture by invading all layers of your app with UI
132
93
logic. The best solution we could come up with is just passing a PagingFlow as a property in the state.
133
94
This is not good, because the state becomes mutable and non-stable, but there's nothing better we could come up with,
134
95
but it does its job, as long as you are careful not to recreate the flow and pass it around between states.
135
-
If you have an idea or a working Paging setup, let us know and we can add it to the library!
136
96
137
97
The Paging library also relies on the `cachedIn` operator which is tricky to use in `whileSubscribed`, because that
138
98
block is rerun on every subscription, recreating and re-caching the flow.
@@ -145,23 +105,13 @@ val pagingFlow by cache {
145
105
}
146
106
```
147
107
148
-
### I have like a half-dozen various flows or coroutines and I want to make my state from those data streams. Do I subscribe to all of those flows in my store?
108
+
### I have a lot of data streams. Do I subscribe to all of the flows in my store?
149
109
150
110
It's preferable to create a single flow using `combine(vararg flows...)` and produce your state based on that.
151
111
This will ensure that your state is consistent and that there are no unnecessary races in your logic.
152
112
As flows add up, it will become harder and harder to keep track of things if you use `updateState` and `collect`.
153
113
154
-
### How do I handle errors?
155
-
156
-
There are two ways to do this.
157
-
158
-
1. First one is using one of the Result wrappers, like [ApiResult](https://github.com/respawn-app/apiresult), a monad
159
-
from Arrow.io or, as the last resort, a `kotlin.Result`.
160
-
2. Second one involves using a provided `recover` plugin that will be run when an exception is
161
-
caught in plugins or child coroutines, but the plugin will be run **after** the job was already cancelled, so you
162
-
cannot continue the job execution anymore.
163
-
164
-
### But that other library allows me to define 9000 handlers, actors, processors and whatnot - and I can reuse reducers. Why not do the same?
114
+
### But that other library has 9000 handlers, reducers and whatnot. Why not do the same?
165
115
166
116
In general, a little boilerplate when duplicating intents is worth it to keep the consistency of actions and intents
167
117
of screens intact.
@@ -188,43 +138,13 @@ fun <S : MVIState, I : MVIIntent, A : MVIAction> StoreBuilder<S, I, A>.reduce(
188
138
189
139
### How to avoid class explosion?
190
140
191
-
1. Modularize your app. The library allows you to do that easily.
192
-
2. Use nested classes. For example, you can define an `object ScreenContract` and nest your state, intents, and actions
141
+
1. Modularize the app. The library allows to do that easily.
142
+
2. Use nested classes. For example, define an `object ScreenContract` and nest your state, intents, and actions
193
143
inside to make autocompletion easier.
194
144
3. Use `LambdaIntent`s. They don't require subclassing `MVIIntent`.
195
145
4. Disallow Actions for your store. Side effects are sometimes considered an anti-pattern, and you may want to disable
196
146
them if you care about the architecture this much.
197
147
198
-
### What if I have sub-states or multiple Loading states for different parts of the screen?
199
-
200
-
Create nested classes and host them in your parent state.
201
-
Example:
202
-
203
-
```kotlin
204
-
sealedinterfaceNewsState : MVIState {
205
-
data objectLoading : NewsState
206
-
data classDisplayingNews(
207
-
valsuggestionsState:SuggestionsState,
208
-
valfeedState:FeedState,
209
-
) : NewsState {
210
-
sealedinterfaceSuggestionsState {
211
-
data objectLoading : SuggestionsState
212
-
data classDisplayingSuggestions(valsuggestions:List<Suggestion>) : SuggestionsState
213
-
}
214
-
215
-
sealedinterfaceFeedState {
216
-
data objectLoading : FeedState
217
-
data classDisplayingFeed(valnews:List<News>) : FeedState
218
-
}
219
-
}
220
-
}
221
-
```
222
-
223
-
* Use `T.withType<Type>(block: Type.() -> Unit)` to cast your sub-states easier as
224
-
the `(this as? State)?.let { } ?: this` code can look ugly.
225
-
* Use `T.typed<Type>()` to perform a safe cast to the given state to clean up the code.
226
-
* You don't have to have a top-level sealed interface. If it's simpler, you can just use a data class on the top level.
227
-
228
148
### I want to use a resource or a framework dependency in my store. How can I do that?
229
149
230
150
The best solution would be to avoid using platform dependencies such as string resources.
0 commit comments