diff --git a/SwiftDataTCA/Service/Database.swift b/SwiftDataTCA/Service/Database.swift index 60c44e0..fee1e92 100644 --- a/SwiftDataTCA/Service/Database.swift +++ b/SwiftDataTCA/Service/Database.swift @@ -16,37 +16,53 @@ extension DependencyValues { } } -fileprivate let appContext: ModelContext = { +fileprivate let liveContext: ModelContext = { + ModelContext(liveContainer) +}() + +fileprivate let previewContext: ModelContext = { + ModelContext(previewContainer) +}() + +fileprivate let liveContainer: ModelContainer = { do { - let url = URL.applicationSupportDirectory.appending(path: "Model.sqlite") let config = ModelConfiguration(url: url) - let container = try ModelContainer(for: Movie.self, migrationPlan: MovieMigrationPlan.self, configurations: config) - return ModelContext(container) + return try ModelContainer(for: Movie.self, migrationPlan: MovieMigrationPlan.self, configurations: config) } catch { - fatalError("Failed to create container.") + fatalError("Failed to create live container.") + } +}() + +fileprivate let previewContainer: ModelContainer = { + do { + let config = ModelConfiguration(isStoredInMemoryOnly: true) + return try ModelContainer(for: Movie.self, migrationPlan: MovieMigrationPlan.self, configurations: config) + } catch { + fatalError("Failed to create preview container.") } }() struct Database { - var context: () throws -> ModelContext + var context: @Sendable () -> ModelContext + var container: @Sendable () -> ModelContainer } extension Database: DependencyKey { public static let liveValue = Self( - context: { appContext } + context: { liveContext }, + container: { liveContainer } ) } extension Database: TestDependencyKey { - public static var previewValue = Self.noop + public static var previewValue = Self.inMemory - public static let testValue = Self( - context: unimplemented("\(Self.self).context") - ) + public static let testValue = Self.inMemory - static let noop = Self( - context: unimplemented("\(Self.self).context") + private static let inMemory = Self( + context: { previewContext }, + container: { previewContainer } ) } diff --git a/SwiftDataTCA/Service/MovieDatabase.swift b/SwiftDataTCA/Service/MovieDatabase.swift index a89747a..1c934b2 100644 --- a/SwiftDataTCA/Service/MovieDatabase.swift +++ b/SwiftDataTCA/Service/MovieDatabase.swift @@ -19,8 +19,8 @@ extension DependencyValues { struct MovieDatabase { var fetchAll: @Sendable () throws -> [Movie] var fetch: @Sendable (FetchDescriptor) throws -> [Movie] - var add: @Sendable (Movie) throws -> Void - var delete: @Sendable (Movie) throws -> Void + var add: @Sendable (Movie) -> Void + var delete: @Sendable (Movie) -> Void enum MovieError: Error { case add @@ -33,7 +33,7 @@ extension MovieDatabase: DependencyKey { fetchAll: { do { @Dependency(\.databaseService.context) var context - let movieContext = try context() + let movieContext = context() let descriptor = FetchDescriptor(sortBy: [SortDescriptor(\.title)]) return try movieContext.fetch(descriptor) @@ -44,38 +44,29 @@ extension MovieDatabase: DependencyKey { fetch: { descriptor in do { @Dependency(\.databaseService.context) var context - let movieContext = try context() + let movieContext = context() return try movieContext.fetch(descriptor) } catch { return [] } }, add: { model in - do { - @Dependency(\.databaseService.context) var context - let movieContext = try context() + @Dependency(\.databaseService.context) var context + let movieContext = context() - movieContext.insert(model) - } catch { - throw MovieError.add - } + movieContext.insert(model) }, delete: { model in - do { - @Dependency(\.databaseService.context) var context - let movieContext = try context() + @Dependency(\.databaseService.context) var context + let movieContext = context() - let modelToBeDelete = model - movieContext.delete(modelToBeDelete) - } catch { - throw MovieError.delete - } + let modelToBeDelete = model + movieContext.delete(modelToBeDelete) } ) } extension MovieDatabase: TestDependencyKey { - public static var previewValue = Self.noop public static let testValue = Self( fetchAll: unimplemented("\(Self.self).fetch"), @@ -84,10 +75,4 @@ extension MovieDatabase: TestDependencyKey { delete: unimplemented("\(Self.self).delete") ) - static let noop = Self( - fetchAll: { [] }, - fetch: { _ in [] }, - add: { _ in }, - delete: { _ in } - ) } diff --git a/SwiftDataTCA/SwiftDataTCAApp.swift b/SwiftDataTCA/SwiftDataTCAApp.swift index 159d37a..87e377a 100644 --- a/SwiftDataTCA/SwiftDataTCAApp.swift +++ b/SwiftDataTCA/SwiftDataTCAApp.swift @@ -12,12 +12,6 @@ import Dependencies @main struct SwiftDataTCAApp: App { @Dependency(\.databaseService) var databaseService - var modelContext: ModelContext { - guard let modelContext = try? self.databaseService.context() else { - fatalError("Could not find modelcontext") - } - return modelContext - } var body: some Scene { WindowGroup { @@ -33,7 +27,7 @@ struct SwiftDataTCAApp: App { QueryView(store: .init(initialState: .init(), reducer: { QueryReducer()._printChanges() })) - .modelContext(self.modelContext) + .modelContainer(databaseService.container()) .tabItem { Label("QueryView", systemImage: "2.circle") } diff --git a/SwiftDataTCA/View/ContentView.swift b/SwiftDataTCA/View/ContentView.swift index 45d4c3d..238d99f 100644 --- a/SwiftDataTCA/View/ContentView.swift +++ b/SwiftDataTCA/View/ContentView.swift @@ -190,19 +190,13 @@ extension TCAContentView { state.refetchMovies() return .none case .add: - do { - let randomMovieName = ["Star Wars", "Harry Potter", "Hunger Games", "Lord of the Rings"].randomElement()! - try context.add(.init(title: randomMovieName, cast: ["Sam Worthington", "Zoe Saldaña", "Stephen Lang", "Michelle Rodriguez"])) - } catch { } + let randomMovieName = ["Star Wars", "Harry Potter", "Hunger Games", "Lord of the Rings"].randomElement()! + context.add(.init(title: randomMovieName, cast: ["Sam Worthington", "Zoe Saldaña", "Stephen Lang", "Michelle Rodriguez"])) return .run { @MainActor send in send(.onAppear, animation: .default) } case .delete(let movie): - do { - try context.delete(movie) - } catch { - - } + context.delete(movie) return .run { @MainActor send in send(.onAppear, animation: .default) @@ -241,8 +235,11 @@ extension TCAContentView { } #Preview { - TCAContentView( - store: .init(initialState: .init(), - reducer: TCAContentView.Feature.init) - ) + @Dependency(\.databaseService) var databaseService + let container = databaseService.container() + + return TCAContentView(store: Store(initialState: TCAContentView.Feature.State(), reducer: { + TCAContentView.Feature() + })) + .modelContainer(container) } diff --git a/SwiftDataTCA/View/QueryView.swift b/SwiftDataTCA/View/QueryView.swift index 9adc82e..d3d3785 100644 --- a/SwiftDataTCA/View/QueryView.swift +++ b/SwiftDataTCA/View/QueryView.swift @@ -64,8 +64,7 @@ struct QueryReducer: Reducer { case favorite(Movie) } - @Dependency(\.swiftData) var context - @Dependency(\.databaseService) var databaseService + @Dependency(\.swiftData) var database var body: some ReducerOf { Reduce { state, action in @@ -74,28 +73,15 @@ struct QueryReducer: Reducer { state.movies = newMovies return .none case .addMovie: - do { - let randomMovieName = ["Star Wars", "Harry Potter", "Hunger Games", "Lord of the Rings"].randomElement()! - try context.add(.init(title: randomMovieName, cast: ["Sam Worthington", "Zoe Saldaña", "Stephen Lang", "Michelle Rodriguez"])) - } catch { } - return .none + let randomMovieName = ["Star Wars", "Harry Potter", "Hunger Games", "Lord of the Rings"].randomElement()! + database.add(.init(title: randomMovieName, cast: ["Sam Worthington", "Zoe Saldaña", "Stephen Lang", "Michelle Rodriguez"])) + return .none case .delete(let movieToDelete): - do { - try context.delete(movieToDelete) - } catch { } - return .none + database.delete(movieToDelete) + return .none case .favorite(let movieToFavorite): - movieToFavorite.favorite.toggle() - guard let context = try? self.databaseService.context() else { - print("Failed to find context") - return .none - } - do { - try context.save() - } catch { - print("Failed to save") - } - return .none + movieToFavorite.favorite.toggle() + return .none } } }