Home How to Inject Dependencies into SwiftUI @StateObject
Post
Cancel

How to Inject Dependencies into SwiftUI @StateObject

Intro

When it comes to SwiftUI, we frequently rely on the @StateObject property wrapper for effectively handling the lifecycle of objects that persist across view updates. However, the process of injecting dependencies into a @StateObject can feel a bit counterintuitive, particularly if you’re new to the concept. If you’re like me and often find yourself resorting to google just to recall the correct syntax, you’re in the right spot.

This form of dependency injection, where external dependencies are provided to a ViewModel rather than being internally created or managed by the ViewModel itself, is a commonly used practice. It contributes to modularity, testability, and maintainability of a codebase, because it enables us to easily switch between different dependency implementations without having to alter the fundamental functionality of the ViewModel.

Code Example

Let’s say you have a view model that relies on a network service:

1
2
3
4
5
6
7
8
9
10
11
class NetworkService {
    // ... network-related logic
}

class ViewModel: ObservableObject {
    @Published var data: [String] = []

    init(networkService: NetworkService) {
        // Initialize and use the network service
    }
}

Now, we want to use this view model in a SwiftUI view and inject the NetworkService dependency. Here’s how we can do it:

1
2
3
4
5
6
7
8
9
10
11
struct ContentView: View {
    @StateObject private var viewModel: ViewModel

    init(networkService: NetworkService) {
        _viewModel = StateObject(wrappedValue: ViewModel(networkService: networkService))
    }

    var body: some View {
        // Use the viewModel in your UI
    }
}

By using the StateObject property wrapper’s initializer in the init block, we can inject the necessary dependencies into our view model. This ensures that the view model retains its state across view updates while correctly initializing its dependencies.

Another Example

Now, consider a scenario where the dependency we’re injecting into our ViewModel happens to be a published property. The approach remains consistent with what we discussed above.

1
2
3
4
5
struct Task {
    let id: UUID
    var title: String
    var isCompleted: Bool
}
1
2
3
4
5
6
7
8
class TaskViewModel: ObservableObject {
    @Published var task: Task

    init(task: Task) {
        self.task = task
    }
}

1
2
3
4
5
6
7
8
9
10
struct TaskView: View {
    @StateObject private var viewModel: TaskViewModel

    init(task: Task) {
        _viewModel = StateObject(wrappedValue: TaskViewModel(task: task))
    }

    var body: some View {
    }
}

Conclusion

So, to wrap things up, injecting a model into a SwiftUI ViewModel is a pretty nifty move that facilitates better modularity, and testing.

This post is licensed under CC BY 4.0 by the author.