From 89ebcb70f032771748175b47afdd64e1ae0d018c Mon Sep 17 00:00:00 2001 From: Alejandro Serrano Date: Mon, 13 Nov 2023 10:41:32 +0100 Subject: [PATCH 1/4] Add some words about Compose --- content/docs/learn/quickstart/compose.md | 116 +++++++++++++++++++++ content/docs/learn/quickstart/migration.md | 2 +- 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 content/docs/learn/quickstart/compose.md diff --git a/content/docs/learn/quickstart/compose.md b/content/docs/learn/quickstart/compose.md new file mode 100644 index 00000000..3b33bfed --- /dev/null +++ b/content/docs/learn/quickstart/compose.md @@ -0,0 +1,116 @@ +--- +id: from-fp +title: Compose and UIs +sidebar_position: 3 +--- + +Arrow provides several features which are very interesting when developing +interactive applications, especially in combination with libraries with a +similar functional flavor, such as Compose. + +:::info Example projects + +Projects using Compose and Arrow can be found in the +[corresponding section](../../design/projects/). + +::: + +:::note Multiplatform-ready + +All the libraries under the Arrow umbrella are +[Multiplatform](https://kotlinlang.org/docs/multiplatform.html)-ready. +This means you can use them in your Android applications using +[Jetpack Compose](https://developer.android.com/jetpack/compose), +and in Desktop, iOS, or Web alongside +[Compose Multiplaftorm](https://www.jetbrains.com/lp/compose-multiplatform/). + +::: + +## Compose is functional + +As opposed to other frameworks where stateful components are the norm, the +[architecture](https://developer.android.com/jetpack/compose/architecture) +promoted by Compose brings many concepts traditionally associated with a +more functional approach. For example, the UI is defined as a _function_ +taking as arguments the _values_ from the current state. Updating the state +is also explicitly marked, and often separated in a `ViewModel`. + +As a consequence, Arrow and Compose make great dancing partners. Below we +discuss a few feature which we think are an immediately gain for Android +(and with Compose Multiplatform, other UI) developers. In the same vein, our +[design](../design/) section readily applies to projects using Compose. + +## Simpler effectful code + +Most applications don't live in a vacuum, they need to access other services +or data sources. In those cases we write _effectful_ code, where `suspend` and +coroutines become relevant. + +Arrow Fx introduces +[high-level concurrency](../../coroutines/parallel/) as a way to simplify code +where different actions must happen concurrently, ensuring that all rules +of Structured Concurrency are followed, but without the hassle. + +```kotlin +suspend fun loadUserData(userId: UserId): UserData = + parZip( + { downloadAvatar(userId) }, + { UserRepository.getById(userId) } + ) { avatarFile, user -> + // this code is called once both finish + } +``` + +Anything outside your own application is the wilderness: connections are +down, services are unavailable. Arrow's [resilience](../../resilience/) +module provides several ready-to-use patterns to better handle those situations, +including [retry policies](../../resilience/retry-and-repeat/) +and [circuit breakers](../../resilience/circuitbreaker/). + +## Updating the model + +One potential drawback of using +[immutable data to model your state](../../design/domain-modeling/) +is that updating it can become quite tiresome, because Kotlin provides +no dedicated feature other than `copy` for this task. + +```kotlin +class UserSettingsModel: ViewModel() { + private val _userData = mutableStateOf(null) + val userData: State get() = _userData + + fun updateName( + newFirstName: String, newLastName: String + ) { + _userData.value = _userData.value.copy( + details = _userData.value.details.copy( + name = _userData.value.details.name.copy( + firstName = newFirstName, lastName = newLastName + ) + ) + ) + } +} +``` + +Arrow Optics addresses these drawbacks, providing +[tools for manipulating and transforming _immutable_ data](../../immutable-data/intro/). +The code above can be rewritten without boring repetition. + +```kotlin +class UserSettingsModel: ViewModel() { + private val _userData = mutableStateOf(null) + val userData: State get() = _userData + + fun updateName( + newFirstName: String, newLastName: String + ) { + _userData.value = _userData.value.copy { + inside(UserData.details.name) { + Name.firstName set newFirstName + Name.lastName set newLastName + } + } + } +} +``` diff --git a/content/docs/learn/quickstart/migration.md b/content/docs/learn/quickstart/migration.md index 0f64a2d7..4ea5e4a3 100644 --- a/content/docs/learn/quickstart/migration.md +++ b/content/docs/learn/quickstart/migration.md @@ -2,7 +2,7 @@ id: migration title: Migration to Arrow 1.2.0 description: Migration guide to upgrade to Arrow 1.2.0. -sidebar_position: 3 +sidebar_position: 4 --- # Migration to Arrow 1.2.0 From 9655f23bddcdbe2d3346271434442c796ab5c4f5 Mon Sep 17 00:00:00 2001 From: Alejandro Serrano Date: Mon, 13 Nov 2023 21:45:53 +0100 Subject: [PATCH 2/4] Suggestions --- content/docs/learn/quickstart/compose.md | 26 ++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/content/docs/learn/quickstart/compose.md b/content/docs/learn/quickstart/compose.md index 3b33bfed..747e92a5 100644 --- a/content/docs/learn/quickstart/compose.md +++ b/content/docs/learn/quickstart/compose.md @@ -38,7 +38,7 @@ is also explicitly marked, and often separated in a `ViewModel`. As a consequence, Arrow and Compose make great dancing partners. Below we discuss a few feature which we think are an immediately gain for Android (and with Compose Multiplatform, other UI) developers. In the same vein, our -[design](../design/) section readily applies to projects using Compose. +[design](../../design/) section readily applies to projects using Compose. ## Simpler effectful code @@ -52,13 +52,23 @@ where different actions must happen concurrently, ensuring that all rules of Structured Concurrency are followed, but without the hassle. ```kotlin -suspend fun loadUserData(userId: UserId): UserData = - parZip( - { downloadAvatar(userId) }, - { UserRepository.getById(userId) } - ) { avatarFile, user -> - // this code is called once both finish - } +class UserSettingsModel: ViewModel() { + private val _userData = mutableStateOf(null) + val userData: State get() = _userData + + suspend fun loadUserData(userId: UserId) = + parZip( + { downloadAvatar(userId) }, + { UserRepository.getById(userId) } + ) { avatarFile, user -> + // this code is called once both finish + _userData.value = UserData( + id = userId, + details = user, + avatar = Avatar(avatarFile) + ) + } +} ``` Anything outside your own application is the wilderness: connections are From a1ff5248d643d777a585d8289cf4e014d01c3a22 Mon Sep 17 00:00:00 2001 From: Alejandro Serrano Date: Tue, 14 Nov 2023 13:33:50 +0100 Subject: [PATCH 3/4] Talk about Either / Ior / Outcome --- content/docs/learn/quickstart/compose.md | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/content/docs/learn/quickstart/compose.md b/content/docs/learn/quickstart/compose.md index 747e92a5..663cd69d 100644 --- a/content/docs/learn/quickstart/compose.md +++ b/content/docs/learn/quickstart/compose.md @@ -77,6 +77,37 @@ module provides several ready-to-use patterns to better handle those situations, including [retry policies](../../resilience/retry-and-repeat/) and [circuit breakers](../../resilience/circuitbreaker/). +## Built-in error types + +One key part of every application is how the domain is modelled. +Arrow emphasizes using [immutable data](../../design/domain-modeling/). +In particular, sealed hierarchies take the important role of describing the +different states. + +Although every application is unique, a common scenario in interactive +applications involve having a "success state" and an "error state". +For example, correctly loading the user data, or encountering a problem +with connection or authentication. Instead of rolling your own types, +Arrow (and our sibling library [Quiver](https://cashapp.github.io/quiver/)) +provide out-of-the-box solutions: + +- [`Either`](../../typed-errors/either-and-ior/) describes a model + in which the application has either completely succeeded, or + some amount of errors have occured. Validation is a prime example, + since we usually require for all fields to be valid before + moving forward with the data. +- [`Ior`](../../typed-errors/either-and-ior/) introduces a third + option, namely succeeding but still with some problems along the way. + This type is useful to model domains where we can work with some + erroneous or missing information. +- [`Outcome`](https://cashapp.github.io/quiver/-quiver%20-library/app.cash.quiver/-outcome/index.html) + models success, failure, and absence. The latter case is useful + when the application may be in _loading_ state: still no problems, + but no data ready either. + +Given the commonalities, Arrow provides a [uniform API](../../typed-errors/working-with-typed-errors/) +to work with values of those types. + ## Updating the model One potential drawback of using From f66e684bd8d219e119aadf6b0bcccd977454828c Mon Sep 17 00:00:00 2001 From: Alejandro Serrano Date: Thu, 30 Nov 2023 15:13:17 +0100 Subject: [PATCH 4/4] Information about arrow-optics-compose --- content/docs/learn/immutable-data/lens.md | 30 +++++++++++++++++++++++ content/docs/learn/overview.md | 1 + content/docs/learn/quickstart/compose.md | 7 +++--- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/content/docs/learn/immutable-data/lens.md b/content/docs/learn/immutable-data/lens.md index 00feec42..013db45f 100644 --- a/content/docs/learn/immutable-data/lens.md +++ b/content/docs/learn/immutable-data/lens.md @@ -242,3 +242,33 @@ fun Person.moveToAmsterdamInside(): Person = copy { } ``` + +If you are using Compose, either in [Android](https://developer.android.com/jetpack/compose) +or [Multiplaftorm](https://www.jetbrains.com/lp/compose-multiplatform/) +flavors, you often need to update a [`MutableState`](https://developer.android.com/jetpack/compose/state) +by applying some modification to the previous value. +The `arrow-optics-compose` package provides a version of +`copy` useful in those situations. + +``` +class AppViewModel: ViewModel() { + private val _personData = mutableStateOf(...) + + fun updatePersonalData( + newName: String, newAge: Int + ) { + _personData.updateCopy { + Person.name set newName + Person.age set newAge + } + } +} +``` + +:::note Compose and Snapshots + +`updateCopy` uses the [snapshot system](https://dev.to/zachklipp/introduction-to-the-compose-snapshot-system-19cn) +in Compose to ensure that all the modifications in the +block happen atomically. + +::: diff --git a/content/docs/learn/overview.md b/content/docs/learn/overview.md index 978bad1d..bc8de7f1 100644 --- a/content/docs/learn/overview.md +++ b/content/docs/learn/overview.md @@ -37,6 +37,7 @@ Each section in the documentation roughly corresponds to one of the libraries th | `arrow-resilience` | [Resilience patterns](../resilience/) | | `arrow-fx-stm` | [Software Transactional Memory](../coroutines/stm/) (STM) | | `arrow-optics` + `arrow-optics-ksp-plugin`
_Companion to [data](https://kotlinlang.org/docs/data-classes.html) and [sealed](https://kotlinlang.org/docs/sealed-classes.html) classes_ | Utilities for [immutable data](../immutable-data/intro/) | +| `arrow-optics-compose` | Extensions for Compose | | `arrow-atomic`
_Multiplatform-ready [references](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/-atomic-reference/)_ | [Atomic references](../coroutines/concurrency-primitives/#atomic) | :::note We'd love to hear from you! diff --git a/content/docs/learn/quickstart/compose.md b/content/docs/learn/quickstart/compose.md index 663cd69d..7c030ea1 100644 --- a/content/docs/learn/quickstart/compose.md +++ b/content/docs/learn/quickstart/compose.md @@ -1,5 +1,5 @@ --- -id: from-fp +id: compose title: Compose and UIs sidebar_position: 3 --- @@ -136,7 +136,8 @@ class UserSettingsModel: ViewModel() { Arrow Optics addresses these drawbacks, providing [tools for manipulating and transforming _immutable_ data](../../immutable-data/intro/). -The code above can be rewritten without boring repetition. +The code above can be rewritten without boring repetition using +the [dedicated `copy` for `MutableState`](../../immutable-data/lens/#more-powerful-copy). ```kotlin class UserSettingsModel: ViewModel() { @@ -146,7 +147,7 @@ class UserSettingsModel: ViewModel() { fun updateName( newFirstName: String, newLastName: String ) { - _userData.value = _userData.value.copy { + _userData.updateCopy { inside(UserData.details.name) { Name.firstName set newFirstName Name.lastName set newLastName