My app has calls to a database in the .onAppear method of a view, but I want to use the preview feature of SwiftUI. Is there a way I can mock or disable this function in the preview window?
struct MyView: View {
@State var dataArray: [Data] = [Data]()
var body: some View {
VStack {
ForEach(dataArray) { data in
Text(data.text)
}
.onAppear {
getData()
}
}
private func getData() {
someCallToDatabase() { returnedData in
self.dataArray = returnedData
}
}
struct MyView_Previews: PreviewProvider {
static var previews: some View {
MyView()
}
}
Basically I'd like to either pass the data in manually without it being overwritten, or somehow add an extension to my database caller class where it automatically sets some dummy data when it knows it's just for the preview.
Thanks a bunches, crunches!
By using some sort of dependency injection, you can make sure that there's a class/object responsible for doing the database calls that can be mocked. One version of this might be using @Envrionment
to send in an object to handle the database calls:
struct ApiData : Identifiable {
var id = UUID()
var text : String
}
protocol DatabaseManager {
func someCallToDatabase(_ callback : ([ApiData]) -> Void)
}
class RealDatabaseManager : DatabaseManager {
func someCallToDatabase(_ callback: ([ApiData]) -> Void) {
callback([]) //do real database call here
}
}
class MockDatabaseManager : DatabaseManager {
func someCallToDatabase(_ callback: ([ApiData]) -> Void) {
callback([])
}
}
private struct DatabaseEnvironmentKey: EnvironmentKey {
static let defaultValue : DatabaseManager = RealDatabaseManager()
}
extension EnvironmentValues {
var databaseManager : DatabaseManager {
get { self[DatabaseEnvironmentKey.self] }
set { self[DatabaseEnvironmentKey.self] = newValue }
}
}
struct MyView: View {
@State var dataArray: [ApiData] = [ApiData]()
@Environment(\.databaseManager) private var databaseManager : DatabaseManager
var body: some View {
VStack {
ForEach(dataArray) { data in
Text(data.text)
}
}
.onAppear {
getData()
}
}
func getData() {
databaseManager.someCallToDatabase { returnedData in
self.dataArray = returnedData
}
}
}
struct MyView_Previews: PreviewProvider {
static var previews: some View {
MyView().environment(\.databaseManager, MockDatabaseManager())
}
}
Of course, you'll need to make sure that in your non-preview views, you're also passing the real database manager down the view hierarchy using .environment(...)
in a parent view, so it'll involve some slight revision of your existing code.