Comments (13)
So here's one example using namespaces that I've used. The mock0, mock1, and mockN modes are used in many places in the app to select mocks that are empty, have only one element, and have many elements.
import SwiftUI
extension Resolver.Name {
static let api = Self("api")
static let mock0 = Self("mock0")
static let mock1 = Self("mock1")
static let mockN = Self("mockN")
static var mode: Resolver.Name = .api
}
extension Resolver: ResolverRegistering {
public static func registerAllServices() {
register { resolve(name: .mode) as DataProviding }
register(name: .api) { APIDataProvider() as DataProviding }
register(name: .mock0) { Mock0DataProvider() as DataProviding }
register(name: .mock1) { Mock1DataProvider() as DataProviding }
register(name: .mockN) { MockNDataProvider() as DataProviding }
}
}
protocol DataProviding {
var text: String { get }
}
struct APIDataProvider: DataProviding {
var text: String = "API"
}
struct Mock0DataProvider: DataProviding {
var text: String = "MOCK0"
}
struct Mock1DataProvider: DataProviding {
var text: String = "MOCK1"
}
struct MockNDataProvider: DataProviding {
var text: String = "MOCKN"
}
class ContentViewModel: ObservableObject {
@Injected var data: DataProviding
var text: String { "Hello, \(data.text) world!" }
}
struct ContentView: View {
@StateObject var viewModel = ContentViewModel()
var body: some View {
Text(viewModel.text)
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Resolver.Name.mode = .mock1
return ContentView()
}
}
from resolver.
In the documentation. There's a section called custom containers. Within there is another section called Party Tricks. It was there I used the truck to do what I needed to do for Swift previews that needed Resolver. I made a mock version and swapped before the moment previews would perform the resolve @sthwicked
from resolver.
Plus another example using mock containers.
extension Resolver {
static var mock: Resolver!
static func setupMockMode() {
Resolver.mock = Resolver(parent: .main)
Resolver.root = .mock
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Resolver.setupMockMode()
Resolver.mock.register { MockNDataProvider() as DataProviding }
return ContentView()
}
}
Using the mock container in unit tests is also straightforward.
import XCTest
@testable import ResolverSwiftUI
class ResolverSwiftUITests: XCTestCase {
override func setUp() {
Resolver.setupMockMode()
}
func testExample0() throws {
Resolver.mock.register { Mock0DataProvider() as DataProviding }
let viewModel = ContentViewModel()
XCTAssert(viewModel.text == "Hello, MOCK0 world!")
}
func testExample1() throws {
Resolver.mock.register { Mock1DataProvider() as DataProviding }
let viewModel = ContentViewModel()
XCTAssert(viewModel.text == "Hello, MOCK1 world!")
}
func testExampleN() throws {
Resolver.mock.register { MockNDataProvider() as DataProviding }
let viewModel = ContentViewModel()
XCTAssert(viewModel.text == "Hello, MOCKN world!")
}
}
from resolver.
Hi @hmlongco just a quick note from me to thank you! As an engineer relatively new to iOS dev, I've been trying to figure out how to do this for literally months. Your examples are extremely useful šš» and I wanted to make sure to express my gratitude š
I would concur with @Prince2k3, it would be worth adding this example to the documentation in my opinion; it is a quite useful, and practical example of how to integrate Resolver with SwiftUI.
Cheers š¦
from resolver.
Came up with something that kind of works.
from resolver.
@Prince2k3 Can you share your solution?
from resolver.
@Prince2k3 Appeciate it! Thanx
from resolver.
Hi @Prince2k3 sorry to revive this issue, but could you elaborate on how you swapped the container right before the previews resolved?
I tried
- setting Resolver.root = .mock in the PreviewPRovider's init()
- setting Resolver.root = .mock in the ViewModel's init
- setting Resolver.root = .mock in the App's init
Unfortunately none of them update the SwiftUI Preview.
Swapping the root of Resolver when running the app on my device works though.
import SwiftUI
import Resolver
import Combine
final class GoalPickerViewModel: ObservableObject {
@LazyInjected private var goalService: GoalService
@LazyInjected private var alertService: AlertService
@Published private(set) var untrackedGoals: [Goal] = []
private var subscribers = Set<AnyCancellable>()
init() {
Resolver.root = .mock
goalService.goalsPublisher
.combineLatest(goalService.goalTrackersPublisher)
.map { goals, goalTrackers in
goals.filter { goalTrackers[$0.id] == nil }
}
.receive(on: DispatchQueue.main)
.sink { [weak self] in self?.untrackedGoals = $0 }
.store(in: &subscribers)
}
func trackGoal(_ goal: Goal) {
goalService.trackGoal(goal)
.errorHandled(by: alertService)
}
}
struct GoalPicker: View {
@StateObject private var viewModel = GoalPickerViewModel()
var body: some View {
ScrollView {
LazyVStack(alignment: .leading) {
ForEach(viewModel.untrackedGoals) { goal in
GoalCard(goal: goal)
.onTap { viewModel.trackGoal(goal) }
.cardButtonStyle(.modalCard)
.disabled(!goal.isTrackable)
}
}
}
}
}
struct GoalPicker_Previews: PreviewProvider {
static var previews: some View {
GoalPicker()
.preferredColorScheme(.dark)
}
}
from resolver.
I did the change, setting it to a mock type, inside the previews getter. That worked for me
from resolver.
Also Iām no longer using Resolver all over my code like this. Iām went with a more composable setup. I use Resolver at the top (app) level while everything else in mostly init injection. This to me makes it easier to test previews versus what was done before with resolver.
Another note: i recommended not using the property wrapper for injections in your view models. As it makes it hard to test and you lose control of how you inject data.
from resolver.
@Prince2k3 I posted several examples above. Note that I disagree (rather strongly in fact) in regard to your comment regarding using @injected in your viewModels and elsewhere in the stack.
As the test example shows, it's not hard to mock services for unit tests, and in fact using dependency injection let's you reach down deeper into the stack and swap out services that would be hard to change if you were using constructor injection and object A depended on B that depended on C that depended on D.
If "D" in this case was a network layer, you might want to swap it out for one that pulled mock json data from a directory. Resolver makes it simple to reregister D, then construct the viewModel which would then inject everything needed to build it.
from resolver.
Hey @hmlongco. It is my bad on adding my opinion on the issue and also my bad on word chosen. (Probably should have just @ you instead). Anyway glad you are here to help.
Can I ask if you can add this to the Readme? These are great solution to getting SwiftUI previews working with Resolver.
from resolver.
I agree with @Prince2k3 regarding not using the @Injected
property wrappers but I disagree with @Prince2k3's reason why.
Due to the way property wrappers are implemented, @Injected
cannot be used if you rely on using injected functionality in an initializer whilst also initializing properties in that initializer.
For that reason, if you want your method of injecting to be consistent between all your classes and you will be needing to initialize properties in the initializer, you cannot use @Injected
.
An additional added benefit of not using @Injected
is you can make your dependency be immutable via let
(unless @hmlongco makes/has made an immutable version of @Injected
).
See the following example demonstrating this:
protocol PersonFormatterType {
func string(from person: Person) -> String
}
struct PersonFormatter: PersonFormatterType {
func string(from person: Person) -> String {
"\(person.name) is a great person"
}
}
struct Person {
let name: String
}
struct PersonView: View {
@ObservedObject var viewModel: PersonViewModel
init(person: Person) {
viewModel = .init(person: person)
}
var body: some View {
Text(viewModel.text)
.padding()
}
}
class PersonViewModel: ObservableObject {
// THIS WON'T WORK: 'self' used in property access 'formatter' before all stored properties are initialized
@Injected private var formatter: PersonFormatterType
// THIS WILL WORK
private let formatter: PersonFormatterType = Resolver.main.resolve()
let text: String
init(person: Person) {
text = formatter.string(from: person)
}
}
from resolver.
Related Issues (20)
- Using Resolver to Register a View Model with "@Main Actor" Annotation HOT 6
- using InjectedObject as a StateObject HOT 13
- Custom Resolver-container per SwiftUI Scene HOT 2
- Blurry photos and videos HOT 1
- I have yet to see my videos HOT 6
- @Injected vars not working after calling Resolver.reset() HOT 1
- My blurry photos HOT 1
- Could not cast value of type 'Swift.Optional<Swift.Optional<Any>>' to 'Swift.String' HOT 3
- Actor Based Register Method HOT 1
- I have yet to see any of the videos HOT 2
- Why use the same lock for `Resolver` and `LazyInjected` HOT 5
- Need to use Xcode in Rosetta mode in order to build a code for simulator HOT 5
- Can't use existential 'any' with InjectedObject property wrapper HOT 4
- Feature Request | Platform specific registering and resolving HOT 2
- ResolverOptions.implements does not allow custom scope other than default HOT 1
- Occasional deadlock in multithreading app HOT 2
- [FR]: Support for Apple Privacy Info Manifest-Files HOT 6
- Missing visionOS Support HOT 1
- Resolver is Deprecated
- Apple Privacy Info Manifest-Files cocoapods integration issue
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
š Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. ššš
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ā¤ļø Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from resolver.