observing an AppStorage object across multiple views

Hi folks

I'm playing with the new @AppStorage wrapper because I'm considering using it to store some simple application state data.

I've created a custom class, which conforms to ObservableObject, and RawRepresentable, and it all works fine if I do all my work in a single view.

If I invoke a subview, and pass in the @AppStorage object as an environmentObject, I can read data from it, but updates to the data never propagate back to the underlying storage.

Here is a simplified reproducer, using the new @main/App lifecycle. I would be thrilled if anyone can help me figure out what's wrong:

Code Block Swift
//
//  ContentView.swift
//  AppStorageTest
//
//  Created by Chris Jones on 25/06/2020.
//
import SwiftUI
import Combine
@main
struct AppStorageTestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
class Person: RawRepresentable, ObservableObject {
    @Published var name: String
    @Published var age: Int
    // RawRepresentable
    var rawValue: String {
        get {
            let value = "\(name):\(age)"
            print("Returning raw value: "+value)
            return value
        }
    }
    required init?(rawValue: String) {
        if rawValue == "" {
            print("Initialised with empty rawValue, setting defaults")
            self.name = "Jonny Appleseed"
            self.age = 42
            return
        }
        print("Initialising with rawValue: \(rawValue)")
        let parts = rawValue.split(separator: ":")
        self.name = String(parts[0])
        self.age = Int(parts[1])!
    }
}
struct ContentView: View {
    @AppStorage("person") var person: Person = Person(rawValue: "")!
    var body: some View {
        // Swap this subview out for the VStack{} from ContentSubview and this all works
        ContentSubview().environmentObject(person)
    }
}
struct ContentSubview: View {
    @EnvironmentObject var person: Person
    var body: some View {
        VStack {
            TextField("Name: ", text: $person.name).padding()
            TextField("Age: ", value: $person.age, formatter: NumberFormatter()).padding()
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(Person(rawValue: "")!)
    }
}


Replies

I'm experiencing the same issue with using @AppStorage inside of a class that conforms to ObservableObject. I, too, would love to know what is going on here and if this is a bug or we're doing something wrong.

In the meantime, my hacky solution was to create a second variable inside my ObservableObject that is initialized with UserDefaults and that is published manually and once set, updates the @AppStorage variable. Roughly, like this...

Code Block
@AppStorage("value") var userValue: String = "Tahu!"
var value = UserDefaults.standard.string(forKey: "value") ?? "" {
    didSet {
        userValue = value
    } willSet {
        objectWillChange.send()
   }
}


Am I missing something about how @AppStorage works? Or is it just not fully working yet in the beta?
Looks like the issue is still present in Beta 2.

This seems awkward but here's how I made it work

Code Block
class Settings {
    @AppStorage("key") var property = ""
    static var shared: Settings { .init() }
}

then in Views

Code Block
struct MyView: View {
   @AppStorage("key") var property = Settings.shared.property
var body: some View {
// the view
}
}