swiftswiftuiwatchoswidgetkitapple-watch-complication

First add of my complication on apple watch is blank


I am developing an app with apple watch complications. When I first add one of the complication to the watch face it stays blank. When I tap on the complication the app opens and the complication on the watch face is displayed after closing the app again. Same behavior when I add a second complication on the same watch face or even another. Both the new and the old one is showing up. Is there a problem with my timeline provider or are the API calls taking too long?

import WidgetKit
import CoreLocation

struct Provider: TimelineProvider {
    let locationManager = LocationManager()
    
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: .now, station: .placeholder, recentMeasurements: [])
    }
    
    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        if context.isPreview {
            let station = Station.placeholder
            let recentMeasurements = Station.measurmentsPlaceholder
            let entry = SimpleEntry(date: .now, station: station, recentMeasurements: recentMeasurements)
            completion(entry)
        } else {
            Task {
                let station = await APIService.getStations().first!
                let recentMeasurements = await APIService.getStationMeasurements(station: station, days: 7)
                let entry = SimpleEntry(date: .now, station: station, recentMeasurements: recentMeasurements)
                completion(entry)
            }
        }
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> ()) {
        Task {
            let stations = await APIService.getStations()
            let station = locationManager.getNearestStation(in: stations) ?? stations.first!
            let recentMeasurements = await APIService.getStationMeasurements(station: station, days: 7)
            let entry = SimpleEntry(date: .now, station: station, recentMeasurements: recentMeasurements)
            let secondEntry = SimpleEntry(date: .now.advanced(by: 60 * 30), station: station, recentMeasurements: recentMeasurements)

            let timeline = Timeline(entries: [entry, secondEntry], policy: .atEnd)
            completion(timeline)
        }
    }
}

Thanks in advance.

Edit (18.02.2023): Provider with Result

import WidgetKit
import CoreLocation

struct Provider: TimelineProvider {
    let locationManager = LocationManager()
    
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: .now, station: .success(Station.placeholder), recentMeasurements: .success([]))
    }
    
    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        if context.isPreview {
            let entry = SimpleEntry(date: .now, station: .success(Station.placeholder), recentMeasurements: .success(Station.measurmentsPlaceholder))
            completion(entry)
        } else {
            Task {
                guard let stations = try? await APIService.getStations(),
                      let station = stations.first
                else {
                    completion(SimpleEntry(date: .now, station: .failure(ProviderError.stationFetch), recentMeasurements: .failure(ProviderError.stationFetch)))
                    return
                }
                guard let recentMeasurements = try? await APIService.getStationMeasurements(station: station, days: 7)
                else {
                    completion(SimpleEntry(date: .now, station: .failure(ProviderError.measurementsFetch), recentMeasurements: .failure(ProviderError.measurementsFetch)))
                    return
                }
                let entry = SimpleEntry(date: .now, station: .success(station), recentMeasurements: .success(recentMeasurements))
                completion(entry)
            }
        }
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> ()) {
        Task {
            let stationResult: Result<Station, Error>
            let station: Station?
            if let stations = try? await APIService.getStations() {
                station = locationManager.getNearestStation(in: stations) ?? stations.first!
                stationResult = .success(station!)
            }
            else {
                station = nil
                stationResult = .failure(ProviderError.stationFetch)
            }
            let recentMeasurements: Result<[Measurement], Error>
            if let result = try? await APIService.getStationMeasurements(station: station!, days: 7) {
                recentMeasurements = .success(result)
            }
            else {
                recentMeasurements = .failure(ProviderError.measurementsFetch)
            }
            let entry = SimpleEntry(date: .now, station: stationResult, recentMeasurements: recentMeasurements)
            let secondEntry = SimpleEntry(date: .now.advanced(by: 60 * 30), station: stationResult, recentMeasurements: recentMeasurements)

            let timeline = Timeline(entries: [entry, secondEntry], policy: .atEnd)
            completion(timeline)
        }
    }
}

enum ProviderError: Error {
    case stationFetch
    case measurementsFetch
}

Data fetching function:

static func getJSON<T: Decodable>(from path: String) async throws -> [T] {
    guard let url = URL(string: path) else { return [] }
        
    let (data, _) = try await URLSession.shared.data(from: url)
    let collection = try JSONDecoder().decode([T].self, from: data)
    return collection
}

Solution

  • There was an issue with filtering the data in the view. Thanks for the help.