Background data task in watchOS - watchkit

I'm trying to build a basic proof-of-concept watchOS app and complication that pulls JSON data from an API and displays a gauge based on that.
I've watched Apple's 'Keping your watch app up to date' and found several other questions on the subject but the sample code has been taken down.
The API provides forecasts for the next ~48 hours and can be used to populate the timeline entries for the complication. When the Complication Controller requests the timeline entries I pull the data from the Extension Delegate and therefore it must be kept up to date. However the process of scheduling background data tasks has got me stumped. When I call backgroundSession.dataTask(with: URL(string: "https://...... I expect my URLSessionDataDelegate functions to be called but they never are and I never get a the handle(_ backgroundTasks) called with WKURLSessionRefreshBackgroundTask
Question:
When a WKApplicationRefreshBackgroundTask is sent to my ExtensionDelegate how should I request/schedule data from the API and then receive it?
Code
//ExtensionDelegate
class ExtensionDelegate: NSObject, WKExtensionDelegate, URLSessionDelegate, URLSessionDataDelegate {
...
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for task in backgroundTasks {
switch task {
case let backgroundTask as WKApplicationRefreshBackgroundTask:
print("background task as WKApplicationRefreshBackgroundTask")
self.scheduleURLSession()
backgroundTask.setTaskCompletedWithSnapshot(false)
return
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
print("background task as WKURLSessionRefreshBackgroundTask")
let backgroundConfigObject =
URLSessionConfiguration.background(withIdentifier: urlSessionTask.sessionIdentifier)
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
print("Rejoining session ", backgroundSession)
self.savedTask = urlSessionTask
return
default:
// make sure to complete unhandled task types
task.setTaskCompletedWithSnapshot(false)
}
}
}
func scheduleURLSession() {
let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: "nz.co.craigstanton")
backgroundConfigObject.sessionSendsLaunchEvents = true
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
let dataTask = backgroundSession.dataTask(with: URL(string: "https://craigstanton.co.nz/uvi-test?latitude=-36&longitude=174")!)
print("scheduleURLSession about to 'resume' ")
dataTask.resume()
}
//Delegate callbacks
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print("Data task error", error)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse,
completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
print("urlSession Delegate did receive everything ")
}
func urlSession(_: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
print("urlSession Delegate did receive something ")
}

Related

watchOS 8 HealtKit background delivery stops working after a few hours in background

I'm trying to implement background delivery of the HealthKit data for an independent watchOS 8 app. I was following Gettings the most out of HealthKit WWDC talk and seems to have added everything that is needed for background delivery to work, including recent iOS 15 and watchOS 8
com.apple.developer.healthkit.background-delivery entitlement. But for some reason, background delivery stops working after approximately 3-5 hours after the app went to the background. For example, I'm receiving updates during the evening from the app, but then over the night updates stops delivering and I'm getting those only if I open the app again in the morning. See the ExtensionDelegate code below
class ExtensionDelegate: NSObject, WKExtensionDelegate {
private let healthStore = HKHealthStore()
private var anchor: HKQueryAnchor?
func applicationDidFinishLaunching() {
print("application did finish launching")
activateHeathKit()
}
func activateHeathKit() {
let types = Set([HKObjectType.categoryType(forIdentifier: .lowHeartRateEvent)!])
healthStore.requestAuthorization(toShare: nil, read: types) { [weak self] success, _ in
guard let `self` = self else {
return
}
guard let lowHeartRateType = HKObjectType.categoryType(forIdentifier: .lowHeartRateEvent) else {
return
}
`self`.healthStore.enableBackgroundDelivery(for: lowHeartRateType, frequency: .immediate) { success, _ in
print("enableBackgroundDelivery: \(success) for lowHeartRateEvent")
}
let query = HKObserverQuery(sampleType: stepsType, predicate: nil) { _, completionHandler, error in
`self`.updateLowHeartRate {
completionHandler()
}
}
`self`.healthStore.execute(query)
}
}
func updateLowHeartRate(completionHandler: #escaping () -> Void) {
guard let lowHeartRateType = HKObjectType.categoryType(forIdentifier: .lowHeartRateEvent) else {return}
let anchoredQuery = HKAnchoredObjectQuery(type: lowHeartRateType, predicate: nil, anchor:
self.anchor, limit: Int(HKObjectQueryNoLimit)) { [unowned self] query, newSamples,
_, newAnchor, error -> Void in
for item in newSamples ?? [] {
let date = item.startDate
let hour = Calendar.current.component(.hour, from: date)
let minute = Calendar.current.component(.minute, from: date)
let message = "Low heart rate from \(hour):\(String(format: "%02d", minute))"
print(message)
}
self.anchor = newAnchor
completionHandler()
}
healthStore.execute(anchoredQuery)
}
}
I don't see an implementation of the handle(_:) method for background tasks but perhaps it is just not shown. Link to the docs here.
Just in case here is how I have my workout app set up to update complications on the watch face.
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for task in backgroundTasks {
if WKExtension.shared().applicationState == .background {
if let watchComplication = task as? WKApplicationRefreshBackgroundTask {
// do background work here
}
}
task.setTaskCompletedWithSnapshot(false)
}
completePendingTasksIfNeeded()
}

UpdateApplicationContext Not Being Called

It's my first time trying WatchConnectivity kit. When I attempt to send ApplicationContext from phone, my debug print indicates that updateApplicationContext is working correctly (with isPaired/reachable/isActivated/isWatchAppInstalled checked before sending the update). However, I'm having trouble receiving it form the paired watch simulator as nothing is shown on that end. Here are the code for the Phone Extension:
func sendDataToWatch(data: String) {
state.append(data)
do {
NSLog("sent by phone")
try WCSession.default.updateApplicationContext(["currentstate": state])
}
catch {
print(error)
}
}
Here are the code for watch:
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) {
print("Hello")
if let getState = applicationContext["currentstate"] as? [String]{
print("\(getState)")
self.state = getState[0]
}
}
Any suggestion would be appreciated!

Rust - How to use a synchronous and an asynchronous crate in one application

I began writing a program using the Druid crate and Crabler crate to make a webscraping application whose data I can explore. I only realized that merging synchronous and asynchronous programming was a bad idea long after I had spent a while building this program. What I am trying to do right now is have the scraper run while the application is open (preferably every hour).
Right now the scraper doesn't run until after the application is closed. I tried to use Tokio's spawn to make a separate thread that starts before the application opens, but this doesn't work because the Crabler future doesn't have the "Send" trait.
I tried to make a minimal functional program as shown below. The title_handler doesn't function as expected but otherwise it demonstrates the issue I'm having well.
Is it possible to allow the WebScraper to run while the application is open? If so, how?
EDIT: I tried using task::spawn_blocking() to run the application and it threw out a ton of errors, including that druid doesn't implement the trait Send.
use crabler::*;
use druid::widget::prelude::*;
use druid::widget::{Align, Flex, Label, TextBox};
use druid::{AppLauncher, Data, Lens, WindowDesc, WidgetExt};
const ENTRY_PREFIX: [&str; 1] = ["https://duckduckgo.com/?t=ffab&q=rust&ia=web"];
// Use WebScraper trait to get each item with the ".result__title" class
#[derive(WebScraper)]
#[on_response(response_handler)]
#[on_html(".result__title", title_handler)]
struct Scraper {}
impl Scraper {
// Print webpage status
async fn response_handler(&self, response: Response) -> Result<()> {
println!("Status {}", response.status);
Ok(())
}
async fn title_handler(&self, _: Response, el: Element) -> Result<()> {
// Get text of element
let title_data = el.children();
let title_text = title_data.first().unwrap().text().unwrap();
println!("Result is {}", title_text);
Ok(())
}
}
// Run scraper to get info from https://duckduckgo.com/?t=ffab&q=rust&ia=web
async fn one_scrape() -> Result<()> {
let scraper = Scraper {};
scraper.run(Opts::new().with_urls(ENTRY_PREFIX.to_vec()).with_threads(1)).await
}
#[derive(Clone, Data, Lens)]
struct Init {
tag: String,
}
fn build_ui() -> impl Widget<Init> {
// Search box
let l_search = Label::new("Search: ");
let tb_search = TextBox::new()
.with_placeholder("Enter tag to search")
.lens(Init::tag);
let search = Flex::row()
.with_child(l_search)
.with_child(tb_search);
// Describe layout of UI
let layout = Flex::column()
.with_child(search);
Align::centered(layout)
}
#[async_std::main]
async fn main() -> Result<()> {
// Describe the main window
let main_window = WindowDesc::new(build_ui())
.title("Title Tracker")
.window_size((400.0, 400.0));
// Create starting app state
let init_state = Init {
tag: String::from("#"),
};
// Start application
AppLauncher::with_window(main_window)
.launch(init_state)
.expect("Failed to launch application");
one_scrape().await
}

Why does holding a non-Send type across an await point result in a non-Send Future?

In the documentation for the Send trait, there is a nice example of how something like Rc is not Send, since cloning/dropping in two different threads can cause the reference count to get out of sync.
What is less clear, however, is why holding a binding to a non-Send type across an await point in an async fn causes the generated future to also be non-Send. I was able to find a work around for when the compiler has been too conservative in the work-arounds chapter of the async handbook, but it does not go as far as answering the question that I am asking here.
Perhaps someone could shed some light on this with an example of why having a non-Send type in a Future is ok, but holding it across an await is not?
When you use .await in an async function, the compiler builds a state machine behind the scenes. Each .await introduces a new state (while it waits for something) and the code in between are state transitions (aka tasks), which will be triggered based on some external event (e.g. from IO or a timer etc).
Each task gets scheduled to be executed by the async runtime, which could choose to use a different thread from the previous task. If the state transition is not safe to be sent between threads then the resulting Future is also not Send so that you get a compilation error if you try to execute it in a multi-threaded runtime.
It is completely OK for a Future not to be Send, it just means you can only execute it in a single-threaded runtime.
Perhaps someone could shed some light on this with an example of why having a non-Send type in a Future is ok, but holding it across an await is not?
Consider the following simple example:
async fn add_votes(current: Rc<Cell<i32>>, post: Url) {
let new_votes = get_votes(&post).await;
*current += new_votes;
}
The compiler will construct a state machine like this (simplified):
enum AddVotes {
Initial {
current: Rc<Cell<i32>>,
post: Url,
},
WaitingForGetVotes {
current: Rc<Cell<i32>>,
fut: GetVotesFut,
},
}
impl AddVotes {
fn new(current: Rc<Cell<i32>>, post: Url) {
AddVotes::Initial { current, post }
}
fn poll(&mut self) -> Poll {
match self {
AddVotes::Initial(state) => {
let fut = get_votes(&state.post);
*self = AddVotes::WaitingForGetVotes {
current: state.current,
fut
}
Poll::Pending
}
AddVotes::WaitingForGetVotes(state) => {
if let Poll::Ready(votes) = state.fut.poll() {
*state.current += votes;
Poll::Ready(())
} else {
Poll::Pending
}
}
}
}
}
In a multithreaded runtime, each call to poll could be from a different thread, in which case the runtime would move the AddVotes to the other thread before calling poll on it. This won't work because Rc cannot be sent between threads.
However, if the future just used an Rc within the same state transition, it would be fine, e.g. if votes was just an i32:
async fn add_votes(current: i32, post: Url) -> i32 {
let new_votes = get_votes(&post).await;
// use an Rc for some reason:
let rc = Rc::new(1);
println!("rc value: {:?}", rc);
current + new_votes
}
In which case, the state machine would look like this:
enum AddVotes {
Initial {
current: i32,
post: Url,
},
WaitingForGetVotes {
current: i32,
fut: GetVotesFut,
},
}
The Rc isn't captured in the state machine because it is created and dropped within the state transition (task), so the whole state machine (aka Future) is still Send.

perform network call and proceed - asynchronous task

i just started learning Swift a year ago, so please be patient with me :)
i am downloading JSON data with a network call, and as soon as i successfully received those rows, i then continue to clear the rows inside my coreData entity, and rewrite those new rows into coredata..
i am having a hard time understanding this asynchronous procedure..
what i've learned is that i have to use completion handlers, but i still can't use it the way i need to.. especialy when i need to proceed after those 3 steps were executed..
First call from button action:
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
textField.text = success! // not possible bc not in the Mainthread
}
textField.text = "blabla" // gets executed before the result is available
methods:
func update(completion: #escaping (String?) -> Void) { //parent method which calls sub methods
var returnValue = ""
Step1getJson {_ in. // step 1
self.Step2Delete { // step 2
self.Step3Save { // step 3
returnValue = "return Value: \(self.step1Result)"
completion(returnValue)
}
}
}
}
func Step1getJson(completion: #escaping (Bool) -> ()) {
var success = false
if let url = URL(string: "https:foo") {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { return }
do {
let parsedJSON = try JSONDecoder().decode([RemoteWire].self, from: data)
print("-- Successfully received \(parsedJSON.count) datarows ")
self.JSON = parsedJSON
self.step1Result = "-- Successfully received \(parsedJSON.count) datarows "
success = true
} catch {
print(error)
}
completion(success)
}.resume()
}
}
func Step2Delete(completion: () -> Void) {
...delete entity rows
completion()
}
func Step3Save(completion: () -> Void) {
.. save new JSON rows to coreData
completion()
}
Everything is working fine that far, and step 2 and step 3 get successfully called when network download has finished..
but how can i proceed after those steps were executed inside my updateButtonPressed function?
if i try to write those results into any UI element inside my completion block, a textField or whatever, i get an error that this has to happen in the main thread, and if i execute it outside the completion block those lines get executed far too early, when no results are available yet.
i feel like i have understanding problem with this, i hope you guys can help me out and guide me in the right direction.
As swift allows any changes or updates in UI element only from main thread, you need to call the main thread to update the UI.
Replace the below code
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
textField.text = success! // not possible bc not in the Mainthread
}
}
with the new code
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
DispatchQueue.main.async {
textField.text = success! // Now possible because it is in main thread
}
}
}

Resources