Looking to use @AppStorage, but avoid depending on UserDefaults.standard

I am using @AppStorage in a model object (see code below that works as expected).

class ModelObject {
    static let shared = ModelObject()
    
    @AppStorage("enhanced") var scriptPickers: Bool = true
    
    var defaultDependentValue: String {
        scriptPickers ? "Enhanced" : "NOT enhanced"
    }
}

struct ContentView: View {
    @AppStorage("enhanced") var scriptPickers: Bool = true
    var body: some View {
        VStack {
            Toggle(isOn: $scriptPickers, label: {
                Text("userDefault val")
            })
            Text("value: \(ModelObject.shared.defaultDependentValue)")
        }
    }
}

Now I want to test my model object in a way that will allow me to use a mock instance of UserDefaults, but am having trouble with the syntax. I tried adding a userDefaults var, and referring to the var in the @AppStorage

class ModelObject {
    static let shared = ModelObject()

    let userDefaults: UserDefaults
    
    init(userDefaults: UserDefaults = .standard) {
        self.userDefaults = userDefaults
    }

    @AppStorage("enhanced", store: userDefaults) var scriptPickers: Bool = true
    
    var defaultDependentValue: String {
        scriptPickers ? "Enhanced" : "NOT enhanced"
    }
}

However I can't find a way to avoid the syntax error this generates: Cannot use instance member 'userDefaults' within property initializer; property initializers run before 'self' is available

Any guidance on how I might be able to:

  1. continue using @AppStorage
  2. be able to test my class in a way that doesn't force me to use UserDefaults.standard

thanks, in advance, Mike

Replies

AFAIK, no way if userDefaults is a property of the model.

Did you try to declare userDefaults as environment var instead ?

Good suggestion @Claude31. I just tried adding this:

struct UserDefaultsKey: EnvironmentKey {
    static var defaultValue: UserDefaults = .standard
}

extension EnvironmentValues {
    var userDefaults: UserDefaults {
        get { self[UserDefaultsKey.self] }
        set { self[UserDefaultsKey.self] = newValue }
    }
}

and updating the original to include this:

    @Environment(\.userDefaults) var userDefaults
    @AppStorage("enhanced", store: userDefaults) var scriptPickers: Bool = true

I now get the following error one the @AppStorage line: Cannot use instance member 'userDefaults' within property initializer; property initializers run before 'self' is available

bummer...