[xilem] `SwiftUI` like `Enviorments` via `Provide`/`Inject` pattern
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 9.7k
- Forks
- 565
- PR merge metrics
- No merged PRs in 30d
Description
as mentioned briefly in [PR:2170](https://github.com/linebender/druid/pull/2170) `SwiftUI`s way of [using `Environment`](https://developer.apple.com/documentation/swiftui/view/environment(_:_:)) to pass data from parent view to its children, notably styling information, is desirable in `xilem` as well. Below i present an idea for typesafe implementatio, loosely based on [`vue.js`' `Provide`/`Inject` pattern](https://vuejs.org/guide/components/provide-inject.html#provide).

the code samples below are rust flavored pseudo-code, so please don't focus on the exact syntax.
# Proposal
lets start from developer facing api, by means of a code sample.
```rust
struct Prop(u32);
impl Default for Prop {...}
fn user() -> impl View<_> {
inject::(|prop| {
format!("prop value is {}", prop)
})
}
fn provider() -> impl View<_> {
provide(Prop(1), user())
}
```
Two new helper functions are provided.
```rust
struct Inject {}
impl View for Inject {}
fn inject() -> Inject<_>
fn provide() -> Inject<_>
```
Here `T` is a tuple type of the injected/ provided values. e.g.
```rust
let wants_prop: Inject<(Prop), _> = inject::(|prop| {
format!("prop value is {}", prop)
});
let wants_prop_and_foo: Inject<(Prop, Foo), _> = inject::(|prop| {
inject::(|foo| {
format!("prop value is {}, foo is {}", prop, foo)
})
});
```
While `Inject` "adds" to the tuple type, `Provide` "subtracts"
```rust
let _: View<_> = provide(Prop(1), wants_prop);
let wants_foo: Inject<(Foo), _> = provide(Prop(1), wants_prop_and_foo);
let _: Inject<(Prop), _> = provide(Foo(), wants_prop_and_foo);
```
Note how by using the "newtype" idiom we can use the typecheck for resolution at compile time, instead of using something like HashMap as storage and resolving at runtime. This also closely mirrors the way `SwiftUI` handles [custom `EnvironmentKey`s](https://developer.apple.com/documentation/swiftui/environmentkey). The developer should also be required to `impl Default for` their custom types, similar to the [requirement `SwiftUI` places](https://developer.apple.com/documentation/swiftui/environmentkey/defaultvalue).
## Styling
By offering the developer `xilem::environment_key::ButtonStyle(Box` they can provide a custom "drawer" for the button widget, again, [similar to how `SwiftUI` handles it](https://developer.apple.com/documentation/swiftui/buttonstyle).
```rust
mod xilem {
mod views {
mod button {
trait ButtonStyle {
type Configuration;
type Body: View;
fn make_body(configuration: Configuration) -> Body;
}
}
}
mod environment_key {
struct ButtonStyle(Box)
}
fn button(
label: impl Into,
clicked: impl Fn(&mut T) -> A + 'static
) -> ... {
Inject::::new(|button_style| {
Interactive::::new(
button_style.make_body(params),
|event, _controller, data: &mut AppData| {
clicked()
}
)
}
}
struct FancyStyle {}
impl xilem::views::button::ButtonStyle for FancyStyle {}
app() -> _ {
use xilem::{button, environment_key::ButtonStyle};
provide(
ButtonStyle(FancyStyle{}),
button("so fancy", || {
print!("fancy click");
})
)
}
```
## upward dataflow
By using `struct MyPreference(Box)` in the `Provide`/`Inject` pattern, it is also possible to pass data from a child to its ancestors, similar [how `PreferenceKey`s in `SwiftUI` work](https://developer.apple.com/documentation/swiftui/preferencekey).
A motivating example is to use `LayoutObserver` to report the measurement upwards.
# Implementation
To best of my understanding of current `xilem` codebase, a new type of context, which is passed through the view tree, is needed. Some logic could be borrowed from the `Adapt` implementation. Additionally a type "addition" and "subtraction" machinery to manage the tuple type `T` of the `Inject` structs is needed.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading PR 2170 and the existing Adapt implementation in the xilem codebase. Define the required view-tree context and the type addition/subtraction machinery for Provide and Inject, then validate the proposal's parent-to-child styling and upward dataflow examples. Done means the proposed environment pattern is implemented and usable by xilem views.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- frontend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100