Using opaque types as @Environment variables

I am struggling with something that seems like it should work but doesn't, and I'm not sure whether to submit a bug.

The use case is having an app where its state can be passed in from the environment, and therefore I can do basic testing of the navigation and function of the app before introducing the actual data layer, which will be from an NSPersistentCloudKitContainer.

The basic implementation of the environment value is:

extension EnvironmentValues {
    @Entry var appState: some AppState = MockAppState()
}

protocol AppState {}

@Observable
class MockAppState: AppState {}

Providing a default value for appState does not cause an issue, but if I try to inject the MockAppState into the environment of the ContentView, I get an error:

import SwiftUI

@main
struct CascadingPoetryCircles240926App: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.appState, MockAppState())
/*
Error: Cannot convert value of type 'MockAppState' to expected argument type 'some AppState'
*/
        }
    }
}

I am able to pass it to the contentView directly without an error:

@main
struct CascadingPoetryCircles240926App: App {
    var body: some Scene {
        WindowGroup {
            ContentView(appState: MockAppState())
        }
    }
}

...

struct ContentView<ViewAppState: AppState>: View {
    var appState: ViewAppState
    var body: some View {
        VStack {}
    }
}

I will proceed with that strategy for now, but this feels like a common use case for @Environment, but in this case we lose the handiness of keeping the appState in the environment without having to pass it view to view. I suspect I am missing some basic issue about Opaque types, but would be grateful if anyone has the patience to help me understand the reason for the different function of the environment variable.

Declaring var appState: some AppState means that the actual type of the property is opaque outside of the property's implementation. So when you try to call .environment(), the value parameter must match the type of appState, which at that point is not known to match MockAppState. The only way to get a value that the compiler believes is the correct type is to read appState itself.

Thanks for the fast response. That makes sense. I guess what might work is if we could call the environment value with a generic, e.g.

.environment(\.appState<MockAppState>, MockAppState())

However, my impression from your response is that that is not possible. Am I understanding correctly?

The purpose of a generic would be if you wanted different variations for different types, which doesn't seem like what you were going for in your original code.

I think maybe what you need is to just use any instead of some.

1 Like