Skip to content
This repository was archived by the owner on Aug 11, 2026. It is now read-only.
This repository was archived by the owner on Aug 11, 2026. It is now read-only.

Does not work when resources are wiped while loading? #1

Description

@laptou

I have the following code in my application:

struct HomeView: View {
  // other variables omitted for brevity
  
  // Profile is a struct which implements Codable and Identifiable
  @State private var friends: TypedResource<[Profile]> = ApiService.main.service.resource("/friends").typed()
  
  private func buildFriendsList() -> some View {
    VStack(alignment: .leading, spacing: 16.0) {
      Text("your nearby friends")
        .font(Font.DesignSystem.displaySmBold)
      ResourceView(friends, statusDisplay: .standard) { friends in
        List {
          
          ForEach(friends) { item in
            HStack {
              VStack(alignment: .leading) {
                Text(item.fullName)
                  .font(Font.DesignSystem.textMdSemibold)
                  .listRowBackground(Color.bgSecondary)
              }
            }
            .padding(.vertical, 8.0)
          }
          .listRowBackground(Color.clear)
          .listRowInsets(EdgeInsets())
        }
        .overlay(alignment: .top) {
          if friends.isEmpty {
            ContentUnavailableView {
              Label("no friends", systemImage: "person.fill.questionmark")
            } description: {
              Text("add your friends on rdvz to invite them on adventures")
            }
          }
        }
        .listStyle(.plain)
        .scrollContentBackground(.hidden)
      }
    }
    .padding(.horizontal, 24.0)
    .padding(.vertical, 24.0)
  }
}

And my ApiService.swift looks like this:

import Foundation
import Siesta
import Siesta_Alamofire
import SiestaExt
import Alamofire
import Combine
import AlamofireNetworkActivityLogger

@MainActor
class ApiService {
  var service: Service
  
  var authToken: String? {
    didSet {
      DispatchQueue.main.schedule { [weak self] in
        self?.service.invalidateConfiguration()
        self?.service.wipeResources()
      }
    }
  }
  
  init() {
    
#if DEBUG
    SiestaLog.Category.enabled = .all
    NetworkActivityLogger.shared.level = .debug
    NetworkActivityLogger.shared.startLogging()
#endif
    
    let reqManager = Session()
    
    service = Service(
      baseURL: "http://localhost:4321/api",
      standardTransformers: [.json, .text],
      networking: reqManager
    )
    
    service.configure("**") { [weak self] in
      $0.headers["user-agent"] = "rdvz/ios"
      $0.headers["accept"] = "application/json"
      
      if let authToken = self?.authToken {
        $0.headers["authorization"] = "Bearer \(authToken)"
      }
    }
    
    Task { [weak self] in
      for await (event, session) in await supabaseClient.auth.authStateChanges {
        self?.authToken = session?.accessToken
      }
    }
  }
  
  static let main = ApiService()
}

When Supabase emits an event to point out that the user has logged in, this happens at the same time as the initial request to the backend for the friends list. The initial request is cancelled, but then the application doesn't send another request to get the data, it just does nothing and shows no data.

In the Xcode preview, where I am not logged in with Supabase, I can see the request hitting my localhost server, but in the Simulator, where I am logged in, no request ever reaches my server. I also tried making HomeView.friends a computed property, as well as inlining the call to .resource() into my view builder; neither of these approaches worked.

The logs from my app look like this:

Siesta:configuration  │ Added config 0 [Siesta standard JSON parsing]
Siesta:configuration  │ Added config 1 [Siesta standard text parsing]
Siesta:configuration  │ URL pattern ** compiles to regex ^http:\/\/localhost:4321\/api\/[^?]*($|\?)
Siesta:configuration  │ Added config 2 [**]
Siesta:configuration  │ Computing configuration for GET Resource(…/friends)[]
Siesta:configuration  │   ├╴Applying config 0 [Siesta standard JSON parsing]
Siesta:configuration  │   ├╴Applying config 1 [Siesta standard text parsing]
Siesta:configuration  │   ├╴Applying config 2 [**]
Siesta:configuration  │   └╴Resulting configuration 
Siesta:configuration  │       expirationTime:            30.0 sec
Siesta:configuration  │       retryTime:                 1.0 sec
Siesta:configuration  │       progressReportingInterval: 0.05 sec
Siesta:configuration  │       headers (2)
Siesta:configuration  │         user-agent: rdvz/ios
Siesta:configuration  │         accept: application/json
Siesta:configuration  │       requestDecorators: 0
Siesta:configuration  │       pipeline
Siesta:configuration  │         ║ rawData stage (no transformers)
Siesta:configuration  │         ║ decoding stage (no transformers)
Siesta:configuration  │         ║ parsing stage
Siesta:configuration  │         ╟   ⟨*/json */*+json⟩ Data → JSONConvertible  [transformErrors: true]
Siesta:configuration  │         ╟   ⟨text/*⟩ Data → String  [transformErrors: true]
Siesta:configuration  │         ║ model stage (no transformers)
Siesta:configuration  │         ║ cleanup stage (no transformers)
Siesta:network        │ Cache request for Resource(…/friends)[]
Siesta:staleness      │ Resource(…/friends)[] is not up to date: no error | no data
Siesta:network        │ Chain[Request:6000009f6080(Cache request for Resource(…/friends)[])]
Siesta:networkDetails │ Cache request for Resource(…/friends)[] already started
Siesta:staleness      │ Resource(…/friends)[L] is not up to date: no error | no data
Siesta:staleness      │ Resource(…/friends)[] is not up to date: no error | no data
Siesta:networkDetails │ Request: 
Siesta:networkDetails │     headers (2)
Siesta:networkDetails │       User-Agent: rdvz/ios
Siesta:networkDetails │       Accept: application/json
Siesta:network        │ GET http://localhost:4321/api/friends
Siesta:observers      │ Resource(…/friends)[L] sending requested event to 1 observer
Siesta:observers      │   ↳ requested → ClosureObserver(ObservableResource.swift:15)
Siesta:staleness      │ Resource(…/friends)[L] is not up to date: no error | no data
Siesta:networkDetails │ GET http://localhost:4321/api/friends already started
Siesta:configuration  │ Configurations need to be recomputed
Siesta:stateChanges   │ Resource(…/friends)[L] wiped
Siesta:network        │ Cancelled GET http://localhost:4321/api/friends
Siesta:network        │ cancel() called but request already completed: GET http://localhost:4321/api/friends
Siesta:observers      │ Resource(…/friends)[L] sending newData(wipe) event to 1 observer
Siesta:observers      │   ↳ newData(wipe) → ClosureObserver(ObservableResource.swift:15)
Siesta:configuration  │ Computing configuration for GET Resource(…/friends)[L]
Siesta:configuration  │   ├╴Applying config 0 [Siesta standard JSON parsing]
Siesta:configuration  │   ├╴Applying config 1 [Siesta standard text parsing]
Siesta:configuration  │   ├╴Applying config 2 [**]
Siesta:configuration  │   └╴Resulting configuration 
Siesta:configuration  │       expirationTime:            30.0 sec
Siesta:configuration  │       retryTime:                 1.0 sec
Siesta:configuration  │       progressReportingInterval: 0.05 sec
Siesta:configuration  │       headers (3)
Siesta:configuration  │         authorization: Bearer <JWT goes here, I removed it>
Siesta:configuration  │         user-agent: rdvz/ios
Siesta:configuration  │         accept: application/json
Siesta:configuration  │       requestDecorators: 0
Siesta:configuration  │       pipeline
Siesta:configuration  │         ║ rawData stage (no transformers)
Siesta:configuration  │         ║ decoding stage (no transformers)
Siesta:configuration  │         ║ parsing stage
Siesta:configuration  │         ╟   ⟨*/json */*+json⟩ Data → JSONConvertible  [transformErrors: true]
Siesta:configuration  │         ╟   ⟨text/*⟩ Data → String  [transformErrors: true]
Siesta:configuration  │         ║ model stage (no transformers)
Siesta:configuration  │         ║ cleanup stage (no transformers)
Siesta:staleness      │ Resource(…/friends)[L] is not up to date: no error | no data
Siesta:network        │ Cache request for Resource(…/friends)[L]
---------------------
GET 'http://localhost:4321/api/friends':
cURL:
$ curl -v \
	-X GET \
	-H "Accept-Language: en-US;q=1.0" \
	-H "Accept-Encoding: br;q=1.0, gzip;q=0.9, deflate;q=0.8" \
	-H "User-Agent: rdvz/ios" \
	-H "Accept: application/json" \
	"http://localhost:4321/api/friends"
Siesta:observers      │ Resource(…/friends)[] sending requestCancelled event to 1 observer
Siesta:observers      │   ↳ requestCancelled → ClosureObserver(ObservableResource.swift:15)
Siesta:staleness      │ Resource(…/friends)[] is not up to date: no error | no data
---------------------
[Error] GET 'http://localhost:4321/api/friends' [0.0242 s]:
Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLStringKey=http://localhost:4321/api/friends, NSLocalizedDescription=cancelled, NSErrorFailingURLKey=http://localhost:4321/api/friends}
Siesta:network        │ Response:  explicitlyCancelled ← GET http://localhost:4321/api/friends
Siesta:networkDetails │ Raw response headers: nil
Siesta:networkDetails │ Raw response body: 0 bytes
Siesta:networkDetails │ Received response, but request was already cancelled: GET http://localhost:4321/api/friends 
Siesta:networkDetails │     New response: RequestError(userMessage: "Request explicitly cancelled.", httpStatusCode: nil, entity: nil, cause: Optional(Alamofire.AFError.explicitlyCancelled), timestamp: 732752061.707411)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions