I'm using the latest Observation framework with SwiftUI introduced at WWDC 2023. It seems that the new @Environment
property wrapper does not allow to use protocols. Here is what my service implementations looks like:
protocol UserServiceRepresentation {
var user: User { get }
}
// Service implementation
@Observable
final class UserService: UserServiceRepresentable {
private(set) var user: User
func fetchUser() async { ... }
}
// Service mock
@Observable
final class UserServiceMock: UserServiceRepresentable {
let user: User = .mock
}
Previously, it was possible using @EnvironmentObject
to declare a protocol so you can pass your mocked service as your environment object to the view.
struct UserView: View {
// This is possible using EnvironmentObject.
// But it require UserService to conforms to ObservableObject.
@EnvironmentObject private var userService: UserService
var body: some View { ... }
}
#Preview {
UserView()
.environmentObject(UserServiceMock()) // this allows your previews not to rely on production data.
}
Now, with @Environment
, I can't do something like this:
struct UserView: View {
// This is not possible
// Throw compiler error: No exact matches in call to initializer
@Environment(UserService.self) private var userService
var body: some View { ... }
}
I would have been nice to inject my mocked user service like so:
#Preview {
UserView()
.environment(UserServiceMock())
}
Am I missing something or doing it the wrong way? It seems that the new @Environment
property wrapper was not built for this kind of usage. I can't find anything about it on the web. Thanks in advance for your help.
No, the
@Environment
initialisers take a concrete type that isObservable
, and there is nothing wrong with keep usingEnvironmentObject
andObservableObject
. If it ain't broke, don't fix it.If you really like
@Observable
for some reason, you can create a custom environment key that stores the user service. The value for the environment key can be an existential protocol type.Then you can call the
Environment
initialiser that takes a keypath.Note that if the instance of
UserServiceRepresentation
is notObservable
, this will not work. It might be better to require anObservable
conformance.