Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs-seo #126

Merged
merged 11 commits into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .cursorignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
**/.kotlin/
**/.gradle/
**/.idea/
**/build
.idea
.kotlin
.build
.gradle
4 changes: 3 additions & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ jobs:
cache-dependency-path: './docs/package-lock.json'

- name: Update docs/README.md
run: cp ./README.md ./docs/docs/README.md
run: |
chmod -R +x ./scripts
./scripts/update_readme.sh

- name: Install docs deps
run: cd docs && npm ci
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ hs_err_pid*
/.idea/other.xml

### Custom rules
.kotlin
Nek-12 marked this conversation as resolved.
Show resolved Hide resolved
.firebase-service-account.json
/.idea/artifacts/**
!gradle/gradle-wrapper.jar
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
![](https://opensource.respawn.pro/FlowMVI/banner.png)
![FlowMVI Framework Banner](https://opensource.respawn.pro/FlowMVI/banner.webp)

[![CI](https://github.com/respawn-app/FlowMVI/actions/workflows/ci.yml/badge.svg)](https://github.com/respawn-app/FlowMVI/actions/workflows/ci.yml)
![License](https://img.shields.io/github/license/respawn-app/flowMVI)
Expand Down Expand Up @@ -384,7 +384,13 @@ timerPlugin(timer).test(Loading) {
IDE plugin generates code and lets you debug and control your app remotely:
[![Plugin](https://img.shields.io/jetbrains/plugin/v/25766?style=flat)](https://plugins.jetbrains.com/plugin/25766-flowmvi)

https://github.com/user-attachments/assets/05f8efdb-d125-4c4a-9bda-79875f22578f
<video
src='https://github.com/user-attachments/assets/05f8efdb-d125-4c4a-9bda-79875f22578f'
controls
width="100%"
alt="FlowMVI IDE Plugin Demo">
Your browser does not support the video element. You can view the demo at our website.
</video>

## People love the library:

Expand Down
10 changes: 10 additions & 0 deletions docs/docs/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
---
title: FlowMVI
title_meta: FlowMVI - Kotlin Architecture Framework
sidebar_label: Home
sidebar_position: 0
hide_title: true
description: Architecture Framework for Kotlin. Reuse every line of code. Handle all errors automatically. No boilerplate. Analytics, metrics, debugging in 3 lines. 50+ features.
slug: /
---

# FlowMVI

### Stub readme file, do not edit!
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/integrations/_category_.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"position": 3,
"position": 4,
"label": "Integrations",
"collapsible": true,
"collapsed": false
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/misc/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 2
sidebar_position: 3
sidebar_label: Contribution guide
---

Expand Down
116 changes: 18 additions & 98 deletions docs/docs/misc/FAQ.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
---
sidebar_position: 2
---

# FAQ

### "Cannot inline bytecode" error
### How to fix "Cannot inline bytecode" error?

The library's minimum JVM target is set to 11 (sadly still not the default in Gradle).
If you encounter an error:
Expand All @@ -20,8 +24,6 @@ kotlin {
}
}
}


```

And in your android gradle files, set:
Expand All @@ -38,36 +40,9 @@ android {
If you support Android API \<26, you will also need to
enable [desugaring](https://developer.android.com/studio/write/java8-support).

### How to name Intents, States, Actions?

### Tips:

* Avoid using `sealed class`es and use `sealed interface`s whenever possible. Not only this reduces object allocations,
but also prevents developers from putting excessive logic into their states and/or making private/protected
properties. State is a simple typed data holder, so if you want to use protected properties or override functions,
it is likely that something is wrong with your architecture.
* Use nested class imports and import aliases to clean up your code, as contract class names can be long sometimes.
* Use value classes to reduce object allocations if your Intents are being sent frequently, such as for text field
value changes or scroll events.
* You can use the `updateStateImmediate` function to optimize the
performance of the store by bypassing all checks and plugins.
* Overall, there are cases when changes are so frequent that you'll want to just leave some logic on the UI layer to
avoid polluting the heap with garbage collected objects and keep the UI performant.
* Avoid subscribing to a bunch of flows in your Store. The best way to implement a reactive UI pattern is to
use `combine(vararg flows...)` and merge all of your data streams into one flow, and then just use the `transform`
block to handle the changes.
* With this, you can be sure that your state is consistent even if you have 20 parallel data streams from different
sources e.g. database cache, network, websockets and other objects.
* Avoid using platform-level imports and code in your Store/Container/ViewModel whenever possible. This is optional, but
if you follow this rule, your **Business logic can be multiplatform**! This is also very good for the architecture.
* There is an ongoing discussion about whether to name your intents starting with the verb or with the noun.
* Case 1: `ClickedCounter`
* Case 2: `CounterClicked`
In general, this is up to your personal preference, just make sure you use a single style across all of your
Contracts. I personally like to name intents starting with the verb (Case 1) for easier autosuggestions from the
IDE.

### Opinionated naming design

There is an ongoing discussion on how to name Intents/States/Actions.
Here's an example of rules we use at [Respawn](https://respawn.pro) to name our Contract classes:

* `MVIIntent` naming should be `<TypeOfActionInPastTense><Target>`.
Expand All @@ -77,8 +52,6 @@ Here's an example of rules we use at [Respawn](https://respawn.pro) to name our
Do not include `Screen` postfix. `GoToHome`~~Screen~~.
* `MVIState`s should be named using verbs in present tense using a gerund. Examples: `EditingGame`, `DisplayingSignIn`.

## FAQ

### My intents are not reduced! When I click buttons, nothing happens, the app just hangs.

* Did you call `Store.start(scope: CoroutineScope)`?
Expand All @@ -95,27 +68,15 @@ Here's an example of rules we use at [Respawn](https://respawn.pro) to name our
subscribe to actions.
4. Try to use an `onUndeliveredIntent` handler of a plugin or install a logging plugin to debug missed events.

### Why does `updateState` and `withState` not return the resulting state? Why is there no `state` property I can access?

FlowMVI is a framework that enables you to build highly parallel, multi-threaded systems. In such systems, multiple
threads may modify the state of the `Store` in parallel, leading to data races, thread races, live locks and other
nasty problems. To prevent that, FlowMVI implements a strategy called "transaction serialization" which only allows
**one** client at a time to read or modify the state. Because of that, you can be sure that your state won't change
unexpectedly while you're working with it. However, any state that you pass outside of the scope of `withState` or
`updateState` should be **considered invalid** immediately. You can read more about serializable state transactions in
the [article](https://proandroiddev.com/how-to-safely-update-state-in-your-kotlin-apps-bf51ccebe2ef).
Difficulties that you are facing because of this likely have an easy solution that requires a bit more thinking.
As you continue working with FlowMVI, updating states safely will come naturally to you.

### In what order are intents, plugins and actions processed?

* Intents: FIFO or undefined based on the configuration parameter `parallelIntents`.
* Actions: FIFO.
* States: FIFO.
* Plugins: FIFO (Chain of Responsibility) based on installation order.
* Decorators: FIFO, but after all of the regular plugins.
* Intents: FIFO or undefined based on the configuration parameter `parallelIntents`
* Actions: FIFO
* States: FIFO
* Plugins: FIFO (Chain of Responsibility) based on installation order
* Decorators: FIFO, but after all of the regular plugins

### When I consume an Action, the other actions are delayed or do not come.
### When I consume an Action, the other actions are delayed or do not come

Since actions are processed sequentially, make sure you launch a coroutine to not prevent other actions from coming and
suspending the scope. This is particularly obvious with things like snackbars that suspend in compose.
Expand All @@ -126,13 +87,12 @@ You shouldn't. Use an Intent / Action to follow the contract, unless you are usi
In that case, expose the parent `ImmutableContainer` / `ImmutableStore` type to hide the `intent` function from
subscribers.

### How to use paging?
### How to use androidx.paging?

Well, this is a tricky one. `androidx.paging` breaks the architecture by invading all layers of your app with UI
logic. The best solution we could come up with is just passing a PagingFlow as a property in the state.
This is not good, because the state becomes mutable and non-stable, but there's nothing better we could come up with,
but it does its job, as long as you are careful not to recreate the flow and pass it around between states.
If you have an idea or a working Paging setup, let us know and we can add it to the library!

The Paging library also relies on the `cachedIn` operator which is tricky to use in `whileSubscribed`, because that
block is rerun on every subscription, recreating and re-caching the flow.
Expand All @@ -145,23 +105,13 @@ val pagingFlow by cache {
}
```

### 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?
### I have a lot of data streams. Do I subscribe to all of the flows in my store?

It's preferable to create a single flow using `combine(vararg flows...)` and produce your state based on that.
This will ensure that your state is consistent and that there are no unnecessary races in your logic.
As flows add up, it will become harder and harder to keep track of things if you use `updateState` and `collect`.

### How do I handle errors?

There are two ways to do this.

1. First one is using one of the Result wrappers, like [ApiResult](https://github.com/respawn-app/apiresult), a monad
from Arrow.io or, as the last resort, a `kotlin.Result`.
2. Second one involves using a provided `recover` plugin that will be run when an exception is
caught in plugins or child coroutines, but the plugin will be run **after** the job was already cancelled, so you
cannot continue the job execution anymore.

### But that other library allows me to define 9000 handlers, actors, processors and whatnot - and I can reuse reducers. Why not do the same?
### But that other library has 9000 handlers, reducers and whatnot. Why not do the same?

In general, a little boilerplate when duplicating intents is worth it to keep the consistency of actions and intents
of screens intact.
Expand All @@ -188,43 +138,13 @@ fun <S : MVIState, I : MVIIntent, A : MVIAction> StoreBuilder<S, I, A>.reduce(

### How to avoid class explosion?

1. Modularize your app. The library allows you to do that easily.
2. Use nested classes. For example, you can define an `object ScreenContract` and nest your state, intents, and actions
1. Modularize the app. The library allows to do that easily.
2. Use nested classes. For example, define an `object ScreenContract` and nest your state, intents, and actions
inside to make autocompletion easier.
3. Use `LambdaIntent`s. They don't require subclassing `MVIIntent`.
4. Disallow Actions for your store. Side effects are sometimes considered an anti-pattern, and you may want to disable
them if you care about the architecture this much.

### What if I have sub-states or multiple Loading states for different parts of the screen?

Create nested classes and host them in your parent state.
Example:

```kotlin
sealed interface NewsState : MVIState {
data object Loading : NewsState
data class DisplayingNews(
val suggestionsState: SuggestionsState,
val feedState: FeedState,
) : NewsState {
sealed interface SuggestionsState {
data object Loading : SuggestionsState
data class DisplayingSuggestions(val suggestions: List<Suggestion>) : SuggestionsState
}

sealed interface FeedState {
data object Loading : FeedState
data class DisplayingFeed(val news: List<News>) : FeedState
}
}
}
```

* Use `T.withType<Type>(block: Type.() -> Unit)` to cast your sub-states easier as
the `(this as? State)?.let { } ?: this` code can look ugly.
* Use `T.typed<Type>()` to perform a safe cast to the given state to clean up the code.
* 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.

### I want to use a resource or a framework dependency in my store. How can I do that?

The best solution would be to avoid using platform dependencies such as string resources.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 5
sidebar_position: 1
---


Expand Down
1 change: 1 addition & 0 deletions docs/docs/plugins/custom.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
sidebar_position: 2
sidebar_label: Making Custom Plugins
---

# Creating custom plugins
Expand Down
Loading