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()
}
}