Skip to content

Commit

Permalink
enable linuxArm64
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielittner committed May 15, 2024
1 parent 93543cf commit 1b40d74
Show file tree
Hide file tree
Showing 27 changed files with 1,597 additions and 1 deletion.
2 changes: 1 addition & 1 deletion compose/compose.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ kotlin {
}

linuxX64()
// TODO linuxArm64()
linuxArm64()

iosArm64()
iosX64()
Expand Down
64 changes: 64 additions & 0 deletions compose/docs/compose.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Extensions for Jetpack Compose

This package provides some functions that may be useful if you use Jetpack Compose.


### `rememberStateAndDispatch()`

Let's say we have a very basic address book UI build with Jetpack Compose
and with a FlowRedux powered state machine.

Let's take a look at this over-simplified code sample:

```kotlin
val stateMachine = AddressBookStateMachine()

@Compose
fun AddressBookUi(){
// Extension function that is provided by this artifact
val (state, dispatchAction) = stateMachine.rememberStateAndDispatch()
Column {
SearchBoxUi(state.searchQuery, dispatch)
}

LazyColumn {
items(state.contacts) { contact : Contact ->
ContactUi(contact, dispatchAction)
}
}
}

@Compose
fun SearchBoxUi(searchQuery : String, dispatchAction: (AddressBookAction) -> Unit) {
Column {
TextField(
value = searchQuery,
// dispatches action async to state machine
onValueChange = { text -> dispatchAction(SearchQueryChangedAction(text)) }
)
}
}
```

`rememberStateAndDispatch()`, as the name already suggests, is remembered across recompositions.

### `rememberState()`

If you only need state of from your stateMachine but not an async way to dispatch actions
then `rememberState()` extension is what you are looking for.

```kotlin
import androidx.compose.runtime.State

val stateMachine = AddressBookStateMachine()

@Compose
fun MyUi(){
val state : State<AddressBookState> = stateMachine.rememberState() // this returns Compose State
LazyColumn {
items(state.contacts) { contact : Contact ->
ContactUi(contact)
}
}
}
```
17 changes: 17 additions & 0 deletions compose/docs/contributing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Contributing

Contribution is more than welcome!
If you would like to contribute code to FlowRedux you can do so through GitHub by forking the repository and sending a pull request.

## Ktlint
The project uses [Ktlint](https://pinterest.github.io/ktlint) to make the code base consistent.
You can run the formatter by using these commands:

```shell
# This runs the lint formatter
./kotlinw .kts/ktlint.main.kts

# This runs the lint checker
./kotlinw .kts/ktlint.main.kts --fail-on-changes
```
The CI runs the check, too.
44 changes: 44 additions & 0 deletions compose/docs/css/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@font-face {
font-family: aktiv-grotesk;
src: url("https://assets.freeletics.com/packages/web-package-particle/fonts-v1/aktivgrotesk/aktivgrotesk-rg.woff2") format("woff2");
font-weight: 400;
font-style: normal
}

@font-face {
font-family: aktiv-grotesk;
src: url("https://assets.freeletics.com/packages/web-package-particle/fonts-v1/aktivgrotesk/aktivgrotesk-md.woff2") format("woff2");
font-weight: 500;
font-style: normal
}

@font-face {
font-family: aktiv-grotesk;
src: url("https://assets.freeletics.com/packages/web-package-particle/fonts-v1/aktivgrotesk/aktivgrotesk-bd.woff2") format("woff2");
font-weight: 700;
font-style: normal
}

body, input {
font-family: aktiv-grotesk,"Helvetica Neue",helvetica,sans-serif;
}

.md-typeset h1, .md-typeset h2, .md-typeset h3, .md-typeset h4 {
font-family: aktiv-grotesk,"Helvetica Neue",helvetica,sans-serif;
line-height: normal;
font-weight: bold;
color: #283237;
}

.md-typeset a {
color: #3375b8;
}

:root {
--md-primary-fg-color: #283237;
--md-primary-fg-color--light: #283237;
--md-primary-fg-color--dark: #283237;

--md-primary-accent-color: #3375b8;
--md-primary-accent-color--transparent: #3375b8;
}
96 changes: 96 additions & 0 deletions compose/docs/dsl-cheatsheet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
hide:
- navigation
- toc
---

# DSL Cheatsheet

If you want to learn more about a particular part of the DSL, we recommend taking a look at the [user guide](/user-guide/).

The following section describes the syntax and usage of the DSL blocks:

```kotlin
spec {
inState<State1>{ // inState is always a "top level" element

// Handle external "input", called Actions
on<Action1>{ action, state -> ... } // Handle an action
on<Action2>(ExecutionPolicy){ action, state -> ... } // You can have multiple on<Action> blocks. Optionally specify ExecutionPolicy

// Do something when you enter the state.
onEnter{ state -> ... } // Called exactly one time when the given state has been entered
onEnter{ state -> ... } // You can have multiple onEnter blocks

// Collect a Flow (from kotlinx.coroutines) as long as the state machine is in the state (see inState<State>)
collectWhileInstate(flow1) { valueEmitedFromFlow, state -> ... } // stops flow collection when state is left
collectWhileInstate(flow2) { valueEmitedFromFlow, state -> ... } // You can have multiple collectWhileInstate

// Effects to do something without changing the state (i.e. logging, analytics, ...)
onActionEffect<Action1>{ action, state -> ... } // You can have multiple onActionEffect
onEnterEffect{ state -> ... } // You can have multiple onEnterEffect
collectWhileInStateEffect(flow1){ valueEmitedFromFlow, state -> ... } // You can have multiple collectWhileInstate

// Hierarchical state machines
onEnterStartStateMachine(
stateMachineFactory = { stateSnapshot : State1 -> OtherStateMachine() },
stateMapper = { state : State<State1>, otherStateMachineState : OtherState -> ... }
)
onActionStartStateMachine<Action1>(
stateMachineFactory = { action, stateSnapshot : State1 -> OtherStateMachine() },
stateMapper = { state : State<State1>, otherStateMachineState :OtherState -> ... }
)
onEnterStartStateMachine(...) // You can have multiple onEnterStartStateMachine
onActionStartStateMachine(...) // You can have multiple onActionStartStateMachine

unitlIdentityChanged({ state.id }) {
// Everyhting inside this block executes only as long as the "identity" (in this example state.id)
// doesn't change. When it changes, then the previous executions will be canceled and
// this block starts again but with the changed state

// you can have multiple of the dsl blocks, i.e. multiple on<Action> blocks and so on.
on<Action3>{ action, state -> ... } // you can have multiple on<Action>
onEnter{ state -> ... }
collectWhileInState(flow){ valueEmitedFromFlow, state -> ... }
onActionEffect{ action, state -> ...}
onEnterEffect{ state -> ... }
collectWhileInStateEffect(flow){ valueEmitedFromFlow, state -> ... }
onEnterStartStateMachine(...)
onActionStartStateMachine(...)
}

// Custom conditions
condition({ state.someString == "Hello" }){
// Everything inside this block only executes if the surounding condition is met
// and the state machine is in the state as specified by the top level inState<State1>.

// You can have each DSL block multiple times, i.e. multiple on<Action> blocks and so on.
on<Action3>{ action, state -> ... }
onEnter{ state -> ... }
collectWhileInState(flow){ valueEmitedFromFlow, state -> ... }
onActionEffect{ action, state -> ...}
onEnterEffect{ state -> ... }
collectWhileInStateEffect(flow){ valueEmitedFromFlow, state -> ... }
onEnterStartStateMachine(...)
onActionStartStateMachine(...)

untilIdentityChanged(...) { // version of untilIdentityChanged that is only ran if the condition block is active
on<Action3>{ action, state -> ... }
onEnter{ state -> ... }
collectWhileInState(flow){ valueEmitedFromFlow, state -> ... }
onActionEffect{ action, state -> ...}
onEnterEffect{ state -> ... }
collectWhileInStateEffect(flow){ valueEmitedFromFlow, state -> ... }
onEnterStartStateMachine(...)
onActionStartStateMachine(...)

// Please note that you cannot have a condition block inside an untilIdentityChanged block
}

// Please note that you cannot have nested conditions inside a condition block
}
}

inState<State2> { ... } // You can have multiple "top level" inState blocks
}
```
Binary file added compose/docs/images/email-master-detail.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added compose/docs/images/email-master-detail.webp
Binary file not shown.
Binary file added compose/docs/images/error-countdown.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added compose/docs/images/favorite-state-list.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions compose/docs/images/icon-freeletics.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added compose/docs/images/item-favorite-state.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added compose/docs/images/lce.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions compose/docs/images/logo-freeletics.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
77 changes: 77 additions & 0 deletions compose/docs/user-guide/10_accross-multiple-states.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Acting across multiple states

Let's assume we have our state modeled like this:

```kotlin
sealed interface ListState
object Loading : ListState
data class ShowContent : ListState
data class Error (val message : String) : ListState
```

If for whatever reason you want to trigger a state change for all states you can achieve that by
using `inState<>` on a base class.

```kotlin
// DSL specs
spec {
inState<ListState> {
// on, onEnter, collectWhileInState for all states because
// ListState is the base class, thus these never get cancelled
}

inState<Loading> {
// on, onEnter, collectWhileInState specific to Loading state only
}

inState<ShowContent> {
// on, onEnter, collectWhileInState specific to ShowContent state only
}

...
}
```

In case you want to trigger state changes from a subset of states you could introduce another
level to your state class hierarchy. For example the following would allow you to have a
`inState<PostLoading>` block to share actions between `ShowContent` and `Error`:

```kotlin
sealed interface ListState {

// Shows a loading indicator on screen
object Loading : ListState

sealed interface PostLoading : ListState

// List of items loaded successfully, show it on screen
data class ShowContent(val items: List<Item>) : PostLoading

// Error while loading happened
data class Error(val message: String) : PostLoading
}
```

```
// DSL specs
spec {
inState<PostLoading> {
// on, onEnter, collectWhileInState for all PostLoading states.
// It means as long as we are in ShowContent or ErrorState this DSL block
// is active
}
inState<ListState> {
// on, onEnter, collectWhileInState for all ListState states, so for all states of this state machine.
}
inState<Loading> {
// on, onEnter, collectWhileInState specific to Loading state only
}
inState<ShowContent> {
// on, onEnter, collectWhileInState specific to ShowContent state only
}
...
}
```
53 changes: 53 additions & 0 deletions compose/docs/user-guide/11_ExecutionPolicy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# ExecutionPolicy

Have you ever wondered what would happen if you would execute `Action` very fast 1 after another?
For example:

```kotlin
spec {
inState<FooState> {
on<BarAction> { action, state : State<FooState> ->
delay(5000) // wait for 5 seconds
state.override { OtherState() }
}
}
}
```

The example above shows a problem with async. state machines like FlowRedux:
If our state machine is in `FooState` and a `BarAction` got triggered, we wait for 5 seconds and then set the state to another state.
What if while waiting 5 seconds (i.e. let's say after 3 seconds of waiting) another `BarAction` gets
triggered.
That is possible, right?
With `ExecutionPolicy` you can specify what should happen in that case.
There are three options to choose from:

- `CANCEL_PREVIOUS`: **This is the default one automatically applied unless specified otherwise.** It would cancel any previous execution and just run the latest one. In the example mentioned it means the previous still running `BarAction` handler gets canceled and a new one with the laster `BarAction` starts.
- `UNORDERED`: Choosing this causes all the blocks to continue running but there are no guarantees in which order. For example:

```kotlin
spec {
inState<FooState> {
on<BarAction>(executionPolicy = FlapMapPolicy.UNORDERED) { _, state : State<FooState> ->
delay(randomInt()) // wait for some random time
state.override { OtherState }
}
}
}
```

Let's assume that we trigger `BarAction` two times.
We use random amount of seconds for waiting.
Since we use `UNORDERED` as policy `on<BarAction>` the handler block gets executed 2 times without canceling the previous one (that is the difference to `CANCEL_PREVIOUS`).
Moreover, `UNORDERED` doesn't make any promise on order of execution of the block (see `ORDERED` if you need promises on order).
If `on<BarAction>` gets executed two times it will run in parallel and the the second execution
could complete before the first execution (because using a random time of waiting).

- `ORDERED`: In contrast to `UNORDERED` and `CANCEL_PREVIOUS`, `ORDERED` will not run `on<BarAction>` in parallel and will not cancel any previous execution. Instead, `ORDERED` will preserve the order.

`on<Action>` and `collectWhileInstate()` can specify an `ExecutionPolicy`:

- `on<Action>(executionPolicy = ExecutionPolicy.CANCEL_PREVIOUS) { ... }`
- `collectWhileInState(executionPolicy = ExecutionPolicy.CANCEL_PREVIOUS) { ... }`

Please note that `onEnter` doesn't have the option to specify `ExecutionPolicy`.
Loading

0 comments on commit 1b40d74

Please sign in to comment.