watchOS SwiftUI background task not working

I'm attempting to create a standalone watchOS app that fetches the prayer timings of my local mosque in JSON format via an API call. I want the app to fetch the prayer timings in the background, but only once per day, at the start of the day (when prayer timings change, i.e., midnight).

I'm trying to implement this using SwiftUI's backgroundTask modifier as explained in the docs and in this WWDC22 video.

I made sure to enable the Background Modes capability, and this is what my app's Info.plist looks like:

However, I've been unable to get it to work. I would appreciate any assistance I can get and feedback for improvements I can make, even with the Info.plist if anything is incorrect about it. Thank you!

This is what I have so far:

//  PrayerTimesCompanionApp.swift
//  PrayerTimesCompanion Watch App

import SwiftUI
import WatchKit

@main
struct PrayerTimesCompanion_Watch_AppApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .backgroundTask(.appRefresh("TIMINGS_REFRESH")) {
            print("Found matching task")
            scheduleNextBackgroundRefresh()
        }
    }
}

// Schedule the next background refresh
func scheduleNextBackgroundRefresh() {
    let today = Calendar.current.startOfDay(for: .now)
    if let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: today) {
        WKApplication.shared().scheduleBackgroundRefresh(withPreferredDate: tomorrow, userInfo: "TIMINGS_REFRESH" as NSSecureCoding & NSObjectProtocol) { error in
            if error != nil {
                fatalError("*** An error occurred while scheduling the background refresh task. ***")
            }
            print("*** Scheduled! ***")
        }
    }
}
/  ContentView.swift
//  PrayerTimesCompanion Watch App

import SwiftUI

struct ContentView: View {
    @StateObject var prayerTimeModel = PrayerTimesModel()
    
    var body: some View {
        List {
            VStack {
                VStack(spacing: 15) {
                    // Table Header
                    HStack {
                        Text("Prayer")
                            .bold()
                            .frame(maxWidth: .infinity, alignment: .leading) // Align to the left
                        Text("Iqamah")
                            .bold()
                            .frame(maxWidth: .infinity, alignment: .trailing) // Align to the right
                    }
                    .padding()
                    
                    // Table Rows (5 prayers)
                    ForEach(prayerTimeModel.prayerTimes.data.iqamah, id: \.date) { iqamah in
                        rowView(prayer: "Fajr", time: iqamah.fajr)
                        rowView(prayer: "Zuhr", time: iqamah.zuhr)
                        rowView(prayer: "Asr", time: iqamah.asr)
                        rowView(prayer: "Maghrib", time: iqamah.maghrib)
                        rowView(prayer: "Isha", time: iqamah.isha)
                    }
                }
                .padding()
            }
            .padding()
            .onAppear {
                prayerTimeModel.fetch()
            }
        }
        .edgesIgnoringSafeArea(.top)
    }
    
    func rowView(prayer: String, time: String) -> some View {
        HStack {
            Text(prayer)
                .frame(maxWidth: .infinity, alignment: .leading)
            Text(time)
                .frame(maxWidth: .infinity, alignment: .trailing)
        }
    }
}

#Preview {
    ContentView()
}
/  PrayerTimesModel.swift
//  PrayerTimesCompanion Watch App

import Foundation
import SwiftUI

// Main struct for the response
struct PrayerTimesResponse: Codable {
    let status: String
    var data: SalahData
    let message: [String]
}

// Struct for the data section
struct SalahData: Codable {
    var salah: [Salah]
    var iqamah: [Iqamah]
}

// Struct for Salah times
struct Salah: Codable {
    let date: String
    let hijriDate: String
    let hijriMonth: String
    let day: String
    var fajr: String
    let sunrise: String
    var zuhr: String
    var asr: String
    var maghrib: String
    var isha: String
    
    enum CodingKeys: String, CodingKey {
        case date, hijriDate = "hijri_date", hijriMonth = "hijri_month", day, fajr, sunrise, zuhr, asr, maghrib, isha
    }
}

// Struct for Iqamah times
struct Iqamah: Codable {
    let date: String
    var fajr: String
    var zuhr: String
    var asr: String
    var maghrib: String
    var isha: String
    let jummah1: String
    let jummah2: String
    
    enum CodingKeys: String, CodingKey {
        case date, fajr, zuhr, asr, maghrib, isha, jummah1 = "jummah1", jummah2 = "jummah2"
    }
}


class PrayerTimesModel: ObservableObject {
    @Published var prayerTimes: PrayerTimesResponse = PrayerTimesResponse(
        status: "Unknown",
        data: SalahData(
            salah: [Salah(date: "", hijriDate: "", hijriMonth: "", day: "", fajr: "", sunrise: "", zuhr: "", asr: "", maghrib: "", isha: "")],
            iqamah: [Iqamah(date: "", fajr: "", zuhr: "", asr: "", maghrib: "", isha: "", jummah1: "", jummah2: "")]),
        message: ["No data available"]
    )
    
    // fetches the local mosque's prayer timings via an API call
    func fetch() {
        guard let url = URL(string: "https://masjidal.com/api/v1/time/range?masjid_id=3OA87VLp") else {
            return
        }
        
        let task = URLSession.shared.dataTask(with: url) { [self] data, _, error in
            guard let data = data, error == nil else {
                return
            }
            
            // Convert to JSON
            do {
                var prayerTimes = try JSONDecoder().decode(PrayerTimesResponse.self, from: data)
                DispatchQueue.main.async {
                    self.prayerTimes = prayerTimes
                }
            }
            catch {
                print(error)
            }
        }
        task.resume()
    }
}

Hi, and welcome! Apple SDKs related questions are better to ask on Apple Developer Forums.

I can note that calling background tasks in general aren’t guaranteed to happen like at all, plus they have restrictions on how much they can run in total and so on, so relying only on background fetch might cause that you don’t see updates. Particularly, docs say:

The system gives your app a few seconds of background execution time to execute the closure you provided. Complete the task as quickly as possible.

And then expands in the section a bit later how the system manages task execution.

So you may not see it called due to variety of reasons, including that you might have already requested too much updates and system pauses your app requests for a while.