Closed ekohlwey closed 1 year ago
A couple of things. First ContentView's content property should probably be a StateObject, not State, since Content() is an observable object.
Second you're doing....
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group{
let _ = Container.shared.fooService.register { LocalFooServiceProxy(fooName: "bar", blockReturn: false) }
ContentView(content: Content())
let _ = Container.shared.fooService.register { LocalFooServiceProxy(fooName: "bar", blockReturn: true) }
ContentView(content: Content())
}
}
}
While the doc shows...
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
let _ = Container.shared.myService.register { MockServiceN(4) }
let vm1 = ContentViewModel()
ContentView(viewModel: vm1)
let _ = Container.shared.myService.register { MockServiceN(8) }
let vm2 = ContentViewModel()
ContentView(viewModel: vm2)
}
}
}
Creating vm1 on a separate line is important since the passed value is thunked. Do it as shown in the first example, and ContentView() won't be evaluated until later when the view is rendered.
I'm still experiencing the issue with the following setup:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group{
let _ = Container.shared.fooService.onPreview { LocalFooServiceProxy(fooName: "bar", blockReturn: false) }
let content1 = Content()
ContentView(content: content1)
let _ = Container.shared.fooService.onPreview { LocalFooServiceProxy(fooName: "bar", blockReturn: true) }
let content2 = Content()
ContentView(content: content2)
}
}
}
I also tried setting up the app with a similar injection scenario, with the same outcome.
@main
struct TestApp: App {
var body: some Scene {
let _ = Container.shared.fooService.register{ LocalFooServiceProxy(fooName: "baz", blockReturn: false)}
let content = Content()
WindowGroup {
ContentView(content:content)
}
}
}
When I tried to step through this, I found that the compiler had optimized a lot of the calls out and that the debugger was somewhat uninformative about what was happening.
I do see the Factory library itself has tests that I think are equivalent code as well, so I'm quite confused about whats happening and really do appreciate your patience looking into this.
One idea I'm wondering about is that the first access to the Container
variable happens inside a Task
. If the compiler is lazily evaluating these (what I think you mean by Thunking above?), perhaps this is a shared memory access issue (like it needs a volatile or a map that uses atomic access)? Do you think that might be the source of the issue? In Containter.swift
from my patch, eg.:
class Content: ObservableObject {
@Published var text:String?
let fooServiceProxy = Container.shared.fooService()
var valueUpdater: Any?
init() {
Task {
print("Getting foo text")
text = await self.fooServiceProxy.getFoo()
print("Got foo text")
}
valueUpdater = $text.sink { value in
guard let value else { return }
Task {
await self.fooServiceProxy.setFoo(foo:value)
}
}
}
}
Thanks so much for your input on this!
I verified the approach mentioned above in the FactoryDemo app's content view. (from develop)
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
let _ = Container.shared.myServiceType.onPreview { MockServiceN(44) }
let model1 = ContentViewModel()
ContentView(model: model1)
let _ = Container.shared.myServiceType.onPreview { MockServiceN(88) }
let model2 = ContentViewModel()
ContentView(model: model2)
}
}
}
If you wanted to build a minimal standard Xcode project that demonstrates the problem I could download and look at it, but I'm not sure the would help with your build system.
Other than that I'm not quite sure there's a lot more I can do with this, since Factory seems to be working as designed.
One thing I might mention, however, is that when you run tests or previews Xcode has to run part of your app. As such you need to make sure that other processes aren't interfering.
Hi there, I'm working on adding an example using Factory to the bazel rules_swift_package_manager project. I'm having an issue where factory doesn't seem to process the registrations I've added in the preview. I believe my usage is consistent with the docs. I've created a branch and will link relevant sections below. The dependency version is 2.2.0.
The preview itself see patch is using the same
Container.shared.fooService.register { LocalFooServiceProxy(..) }
type syntax as is described in the docs.The
Container
extension see patch also seems to be according to docs.Finally the resolution seems appropriate as well see patch.
I've tried adding some debug print messages to the code for my example as well as Factory and was able to help me debug whats happening. The print statements were added to
Registrations.swift#register()
andRegistrations.swift#resolve()
, to show the contents of the registration map while callingregister()
andresolve()
. The logs are below:The logs show that actually during the call to
register()
, the registration map is indeed populated. But by the timeresolve()
is called, the registration map has somehow been cleared. The remaining lines in the log simply show that indeed the default implementation is being invoked instead of the intended (mocked) implementation during preview.I'm unsure how the registrations map is getting cleared and have been unable to make progress on debugging the issue.
Any guidance you have is very appreciated!