@EnvironmentObject and Protocols?

I would like to declare an @EnvironmentObject as a protocol, namely as

Code Block
protocol AccessTokenProvider: Authenticator {
    func accessToken() -> AccessTokenPublisher
}


where Authenticator is

Code Block
public class Authenticator: NSObject, ObservableObject


However, when I add this to the environment as

Code Block
var authenticator: AccessTokenProvider = MyAuthenticator()
[…]
ContentView().environmentObject(authenticator)

the compiler throws an error at:
Code Block
struct ContentView: View {
// error: property type 'AccessTokenProvider' does not match that of the 'wrappedValue' property of its wrapper type 'EnvironmentObject'
    @EnvironmentObject var authenticator: AccessTokenProvider
                           ^


Is this even a good idea? If so, what am I doing wrong?
                           ^

Accepted Reply

what am I doing wrong?

The type of @EnvirontmentObject must conform to ObservableObject.
But, in Swift, usual protocols used as type cannot conform to any protocols.
(In your declaration of AccessTokenProvider, : Authenticator works as a requirement of the protocol, but it does not mean AccessTokenProvider conforming to ObservableObject.)

Under the current restriction of the type system of Swift, you cannot use protocol type for @EnvirontmentObject.

You can start some discussion about this in forums.swift.org .
  • Has anyone indeed started this discussion? I can't find one, and I would very much like to understand the justification behind this limitation.

Add a Comment

Replies

what am I doing wrong?

The type of @EnvirontmentObject must conform to ObservableObject.
But, in Swift, usual protocols used as type cannot conform to any protocols.
(In your declaration of AccessTokenProvider, : Authenticator works as a requirement of the protocol, but it does not mean AccessTokenProvider conforming to ObservableObject.)

Under the current restriction of the type system of Swift, you cannot use protocol type for @EnvirontmentObject.

You can start some discussion about this in forums.swift.org .
  • Has anyone indeed started this discussion? I can't find one, and I would very much like to understand the justification behind this limitation.

Add a Comment

You can just use some generics:

public protocol Authenticator: ObservableObject {}
public protocol AccessTokenProvider: Authenticator {}

struct YourView<Provider>: View where Provider: AccessTokenProvider {
    @EnvironmentObject var authenticator: Provider

    var body: some View {
        EmptyView()
    }

}
  • Good approach. But it would require the parent view body to specify the type parameter for Provider, something like YourView<ConcreteAccessTokenProvider>().

    Two issues: (1) Having to explicitly specify the type parameter defeats one of the purposes of using environmentObject in the first place, which is reducing explicit specification of property fields and types in each view. (2) Explicitly specifying the type parameter is acceptable in the topmost view, but how is an inner view know what concrete implementation was used when the object was instantiated in the .environmentObject() call?

    I think SwiftUI needs a better solution to this.

Add a Comment