Open ngunv13 opened 2 months ago
Hello!!! I haven't added any View level or Preview support at this time, but it is a great idea and thanks for the feedback. I think that instead of what you are proposing with conditionally adding dependencies to your app container, it might be better if try to override the active DependencyContainer entirely only in the preview context.
Until a proper upcoming release that addresses this issue and allows for interleaving of preview / non-preview dependency container, you may use the following helper functions:
// MARK: Example Service and View
protocol SomeService {
func sayHi()
}
class SomeServiceImpl: SomeService {
func sayHi() {
print("Hi!")
}
}
class SomeServiceMockImpl: SomeService {
func sayHi() {
print("Hi from Mock!")
}
}
struct FirstView: View {
class ViewModel: ObservableObject {
@Injected
var service: SomeService?
init() {}
}
@StateObject
var viewModel = ViewModel()
var body: some View {
Button("Say Hi") {
viewModel.service?.sayHi()
}
}
}
// MARK: Preview
#Preview {
withPreviewDependencies {
Dependency(conformingTo: SomeService.self) {
SomeServiceMockImpl()
}
} content: {
FirstView()
}
}
// MARK: Preview Dependencies Helpers
struct _WithPreviewDependenciesStaged<Content: View>: View {
class DependencyContainerController: ObservableObject {
let container: DependencyContainer
init(container: DependencyContainer) {
self.container = container
DependencyGraph.deactivateAll()
DependencyGraph.activate(container)
}
deinit {
DependencyGraph.deactivate(container)
}
}
@StateObject
private var dependencyContainerController: DependencyContainerController
private let content: Content
init(
dependencyContainer: DependencyContainer,
content: Content
) {
self.content = content
self._dependencyContainerController = StateObject(
wrappedValue: DependencyContainerController(
container: dependencyContainer
)
)
}
init(
content: Content,
@DependencyRegistrarBuilder dependencies: @escaping () -> DependenciesRegistrar
) {
self.content = content
self._dependencyContainerController = StateObject(
wrappedValue: DependencyContainerController(
container: DependencyContainer(
dependencies: dependencies
)
)
)
}
var body: some View {
content
}
}
func withPreviewDependencies<Content: View>(
dependencies dependencyContainer: DependencyContainer,
@ViewBuilder content: () -> Content
) -> some View {
_WithPreviewDependenciesStaged(
dependencyContainer: dependencyContainer,
content: content()
)
}
func withPreviewDependencies<Content: View>(
@DependencyRegistrarBuilder dependencies: @escaping () -> DependenciesRegistrar,
@ViewBuilder content: () -> Content
) -> some View {
_WithPreviewDependenciesStaged(
content: content(),
dependencies: dependencies
)
}
Note that currently these helper functions will override all the active dependency containers defined in your App structure. Furthermore, you may define a DependencyContainer extension that returns the container for previews / tests.
Hello! After trying out a lot of solutions and libraries, I decided to use this great library for dependency injection in my project. I wonder how I can replace a dependency with another (mock) one in testing or in preview. I think I'll need to create some variables, apply conditional injection and invalidate the container in each test. This may affect performance and introduce some "redundant" code, but right now I cannot think of a better solution.