Version
2.43.1
Platform
iOS
Swift Version
6.3
What happened?
Consider the following pseudocode:
struct CustomView: View {
@State private var viewModel = CustomViewModel()
var body: some View {
Image(systemName: viewModel.posts.isEmpty ? "tray" : "tray.fill")
.task { await viewModel.load() }
}
}
and the view model
@Observable
final class CustomViewModel {
/// This is using pointfreeco's SQLiteData to simplify the example but the same behavior is observed with SwiftData
@FetchAll var posts: [Post]
func load() async {
let token = sup abase.auth.currentSession!.accessToken
let options = FunctionOptions(method: .get, query: [], headers: ["Authorization": "Bearer \(accessToken)"]
do {
let response: PostListResponse = try await supabase.functions.invoke("posts-v3", options: options, decode: { data, _ in try JSONDecoder().decode(PostListResponse.self, from: data) }
try await database.write { db in
Post.upsert { response.posts }.execute(db)
Post.delete().where { $0.notIn(response.posts) }
}
} catch {}
}
}
Very simple view and view model that changes the fill state of a system image based on a db query and onAppear, calls an initial load method. On first load, there's a delay because the database query with FetchAll has nothing on disk yet, so the image doesn't transition from empty to filled until after the network call, which is expected.
However on second load, when data is already on disk, the same behavior occurs. Rather than the view getting immediately populated with the @FetchAll data and immediately flipping to the filled icon, it waits until the network call has completed and finished the load method to do so. You can exacerbate this issue by adding a Task.sleep after the network call and the UI state won't update for that additional sleep delay.
What should happen is the UI immediately updates with the results of the db query powered by FetchAll or @Query in SwiftData and the supabase call runs separately from that and populates the db and then a subsequent update happens if needed. Instead, the supabase call seems to be blocking render updates to the view until it's finished, possibly by blocking the main thread.
To further show that it was the supabase and not our client side code that was responsible for this, I replaced the supabase invocation with a Task.sleep that returns stub data from the network, just the supabase invocation, nothing else in the load function and the UI updated immediately, as I would expect it to. Unfortunately I can't share our actual code as that is proprietary but even this simple example should illustrate the issue. I'm not an expert on concurrency but it does seem like something in supabase is blocking the thread its on, which in this case is the main thread, and is causing delayed view renders.
Steps to Reproduce
- Create a view dependent on some local database state i.e. state thats quick to fetch but not in memory
- call a supabase method on
.task in SwiftUI to fetch remote data
- Observe that view will not update until after the supabase call has returned, rather than two updates in a row, one from the db and one from the network.
Version
2.43.1
Platform
iOS
Swift Version
6.3
What happened?
Consider the following pseudocode:
and the view model
Very simple view and view model that changes the fill state of a system image based on a db query and onAppear, calls an initial load method. On first load, there's a delay because the database query with
FetchAllhas nothing on disk yet, so the image doesn't transition from empty to filled until after the network call, which is expected.However on second load, when data is already on disk, the same behavior occurs. Rather than the view getting immediately populated with the
@FetchAlldata and immediately flipping to the filled icon, it waits until the network call has completed and finished theloadmethod to do so. You can exacerbate this issue by adding a Task.sleep after the network call and the UI state won't update for that additional sleep delay.What should happen is the UI immediately updates with the results of the db query powered by
FetchAllor@Queryin SwiftData and the supabase call runs separately from that and populates the db and then a subsequent update happens if needed. Instead, the supabase call seems to be blocking render updates to the view until it's finished, possibly by blocking the main thread.To further show that it was the supabase and not our client side code that was responsible for this, I replaced the supabase invocation with a
Task.sleepthat returns stub data from the network, just the supabase invocation, nothing else in the load function and the UI updated immediately, as I would expect it to. Unfortunately I can't share our actual code as that is proprietary but even this simple example should illustrate the issue. I'm not an expert on concurrency but it does seem like something in supabase is blocking the thread its on, which in this case is the main thread, and is causing delayed view renders.Steps to Reproduce
.taskin SwiftUI to fetch remote data