Simplified Environment management with ComposableEnvironment #554
tgrapperon
started this conversation in
Show and tell
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hello everyone!
I've just open-sourced
ComposableEnvironment
.I'm working on a consequent project with TCA and the bookkeeping of dependencies has started to be quite annoying. When I'm adding an unplanned dependency to a leaf feature, I'm forced to update all the initializers and environments up to the root Environment. In some areas, that's up to 10 environments to update. I've written this simple library a few days ago and it simplified greatly my dependencies management in the app (I didn't update all the environments in my app yet, but it's not mandatory for the library to work). It's working fine enough to open-source it.
I took inspiration from
SwiftUI
and the way they manage their environment. You can find a little more info in the (ReadMe)[https://github.com/tgrapperon/swift-composable-environment/blob/main/README.md]. In a few words, you declare dependencies like you declareEnvironmentValue
's in SwiftUI, with adefaultValue
and a computed property in aComposableDepencies
struct. You can then use this dependency like you would use an@Environment
value in a SwiftUIView
, with a keyPath. You can also declare derived environments in your environment and their dependencies will automatically in sync dependencies with their parent's one.You don't have to bind explicitly the child environment in its parent. You can update a dependency with a
with(keypath, value)
chaining API and these changes will only affect the environments downstream (like in SwiftUI)This works well with testing and with SwiftUI previews., as you can quickly construct an environment with a specific configuration with the chaining API.
Here is an example of the API in action, declaring and sharing a
mainQueue
dependency:The only noticeable difference with canonical TCA is that your environment needs to be a class (a subclass of
ComposableEnvironment
to be more specific). This is forced by the obligation to go up to the hosting instance in the property wrappers (it works only for reference types). Otherwise, one would be forced to use reflection at the expense of performance (and even then, I'm not even sure it can work at the end). As a positive side effect, child environment instances are cached, and dependencies are shared. There should be no impact on performances.Please note that, like in SwiftUI, dependencies are passing through environments, even if they are not explicitly declared by an inner link of the chain. That is, in the chain
root -> child1 -> child2
of environments, you can declare atime
dependency inroot
and retrieve it inchild2
, even ifchild1
doesn't declare this dependency. In other words, you don't have to stuff your environments with dependencies they only keep to pass to child environments. This is one of the main key points of the library.To sum up, your environment needs to be a subclass of
ComposableEnvironment
(no additional constraint), you declare dependencies in them with the@Dependency
property wrapper and child environments with the@DerivedEnvironment
property wrapper. You register your dependencies à la SwiftUI, withDependencyKey
andComposableDependencies
, and you can update environment's dependencies with thewith(keyPath, value)
chaining API. As a bonus, you can directly use the derived environment'sKeyPath
when pulling back reducers. The library can instantiate childComposableEnvironment
's by itself, but it will also use any instance you provide to the@DerivedEnvironment
wrapper. It also preserves undecorated properties in yourComposableEnvironment
subclasses.Please let me know if you have ideas for improvement!
Beta Was this translation helpful? Give feedback.
All reactions