Proper way of determining if there is connection to a firebase database - firebase

func checkForConnection() {
let connectedRef = FIRDatabase.database().reference(withPath: ".info/connected")
connectedRef.observe(.value, with: { snapshot in
if let connected = snapshot.value as? Bool , connected {
print("Camera View Connected")
self.connectionStatus = 1
self.UpdateConnection()
} else {
print("Camera View Not connected")
self.connectionStatus = 0
Timer.scheduledTimer(timeInterval: TimeInterval(2.0), target: self, selector: #selector(CameraViewController.UpdateConnection), userInfo: nil, repeats: false)
}
})
}
func UpdateConnection() {
let connectionBanner = Banner(title: "Connection Error", subtitle: "Tap to dismiss", image: UIImage(named: "Wi-Fi"), backgroundColor: UIColor(hexString: "#DA4167"))
if connectionStatus == 0 {
print("connection banner shown from camera view")
// What happens when there's no connection
connectionBanner.dismissesOnTap = true
connectionBanner.show(duration: 10)
}
else if connectionStatus == 1 {
connectionBanner.dismiss()
}
}
Here I have a code which detects if the app has connection to the database. If it doesn't, it will display a banner notification telling the user that he has lost connection. But on first install, it always displays no connection despite having connection.
I understand the app takes awhile to establish connection but is there a way to implement a retry before calling the banner? I've already set it such that even when there's no connection, it will delay before executing the banner if connectionState is still = 0.
Thanks a lot!
Edit:
Here's my viewDidLoad:
override func viewDidLoad() {
// for connection
checkForConnection()
}
There's only one function here to check for connection.

Related

Unable to send message to select! loop via an mpsc::unbounded_channel

I have an issue with a Redis client which I'm trying to integrate into a larger message broker.
The problem is that I am using the PUBSUB functionality of Redis in order to subscribe to a topic and the async implementation shown in the docs example does not properly react to disconnects from the server.
Basically doing a loop { tokio::select!{ Some(msg) = pubsub_stream.next() => { handle_message(msg); } } } would properly handle new messages, but when the server went down or got unreachable, I would not get notified and pubsub_stream.next() would wait forever on a dead connection. I assume that the client would then drop this connection as soon as a command would get sent to Redis, but this is a listen-only service with no intention to issue other commands.
So I tried to use an approach which I learned while adding WebSocket support via axum to this broker, where an unbound mpsc channel is used to deliver messages to a specific WebSocket client, and there it works.
The following is the approach which I'm trying to get to work, but for some reason the code in the select! loop is never executed. I'm intending to add more code from other channels to the select! loop, but I've removed them to keep this example clean.
Basically I am seeing the - 1 REDIS subscription event printouts but not the - 2 REDIS subscription event printouts.
pub async fn redis_async_task(storage: Arc<Storage>) {
//-----------------------------------------------------------------
let mut eb_broadcast_rx = storage.eb_broadcast_tx.subscribe();
let (mpsc_tx, mut mpsc_rx) = mpsc::unbounded_channel::<Msg>();
let mut interval_5s = IntervalStream::new(tokio::time::interval(Duration::from_secs(5)));
//-----------------------------------------------------------------
let _task = tokio::spawn({
async move {
loop {
tokio::select! {
Some(msg) = mpsc_rx.recv() => {
// Why is this never called?
let channel = msg.get_channel_name().to_string();
let payload = msg.get_payload::<String>().unwrap();
println!(" - 2 REDIS: subscription event: {} channel: {} payload: {}", channel, payload);
},
Some(_ts) = interval_5s.next() => {
// compute messages per second
println!("timer");
},
Ok(evt) = eb_broadcast_rx.recv() => {
// Some other events unrelated to Redis
if let Event::WsClientConnected{id: _id, name: _name} = evt {}
else if let Event::WsClientDisconnected{id: _id, name: _name} = evt {}
},
}
}
}
});
//-----------------------------------------------------------------
loop {
// loop which runs forever, reconnecting to Redis server upon disconnect
// and resubscribing to the topic.
println!("REDIS connecting");
let client = redis::Client::open("redis://127.0.0.1:6379/").unwrap();
if let Ok(mut connection) = client.get_connection() {
// We have a connection
println!("REDIS connected");
if let Err(error) = connection.subscribe(&["tokio"], |msg| {
// We are subscribed to the topic and receiving messages
if let Ok(payload) = msg.get_payload::<String>() {
let channel = msg.get_channel_name().to_string();
println!(" - 1 REDIS subscription event: channel: {} payload: {}", channel, payload);
// Send the message through the channel into the select! loop
if let Err(error) = mpsc_tx.send(msg) {
eprintln!("REDIS: error sending: {}", error);
}
};
// ControlFlow::Break(())
ControlFlow::<i32>::Continue
}) {
// Connection to Redis is lost, subscription aborts
eprintln!("REDIS subscription error: {:?} ", error);
};
} else {
// Connection to Redis failed, is probably not reachable.
println!("REDIS connection failed");
}
// Sleep for 1 second before reconnecting.
sleep(Duration::from_millis(1000)).await;
}
}
The code above is called from main like so, in parallel to other clients like WebSocket and MQTT, which do work.
#[tokio::main]
async fn main() {
// ...
tokio::spawn(task_redis::redis_async_task(storage.clone()))
// ...
}

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

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

Trigger an alert when using Firebase observer and events

Working on an app that has two parts - Rider & Driver. When the driver accepts the request, an alert is then sent to the rider that the request was accepted and driver is on the way.
Unable to trigger the alert to the rider.
RiderVC:
func driverAcceptedRequest(requestAccepted: Bool, driverName: String) {
if !riderCancelledRequest {
if requestAccepted {
self.alertTheUser(title: "Ryde Accepted", message: "\(driverName) Has Accepted Your Ryde Request and will message you with details")
} else {
RydeHandler.Instance.cancelRyde()
alertTheUser(title: "Ryde Cancelled", message: "\(driverName) Has Cancelled the Ryde Request")
}
}
riderCancelledRequest = false
}
RydeHandler.swift:
// DRIVER ACCEPTED RYDE
DataService.Instance.requestAcceptedRef.observe(FIRDataEventType.childAdded) { (snapshot: FIRDataSnapshot) in
if let data = snapshot.value as? NSDictionary {
if let name = data[Constants.NAME] as? String {
if self.driver == "" {
self.driver = name
self.delegate?.driverAcceptedRequest(requestAccepted: true, driverName: self.driver)
}
}
}
}
Firebase database structure:
Edit
ViewDidLoad in tableviewcontroller - list of requests:
ref.child("drivers").child("RideRequests").observe(FIRDataEventType.value, with: { snapshot in
self.rideRequests.removeAll()
for item in snapshot.children{
self.rideRequests.append(item as! FIRDataSnapshot)
}
self.rideRequests.reverse()
self.tableView.reloadData()
})

didFinishUserInfoTransfer finished successfully - but still with outstandingUserInfoTransfers Objects

First I use the "transferUserInfo"-method in order to send a dictionary from the iPhone to the Apple Watch:
let dicty = //...my dictionary of property-list values...
if WCSession.isSupported() {
let session = WCSession.defaultSession()
if session.paired == true { // Check if your Watch is paired with your iPhone
if session.watchAppInstalled == true { // Check if your Watch-App is installed on your Watch
session.transferUserInfo(dicty)
}
}
}
Then I am using the following delegate callback method "didFinishUserInfoTransfer" to check upon the state of the transfer:
func session(session: WCSession, didFinishUserInfoTransfer userInfoTransfer: WCSessionUserInfoTransfer, error: NSError?) {
if error == nil {
let session = WCSession.defaultSession()
let transfers = session.outstandingUserInfoTransfers
if transfers.count > 0 { //--> is always > 0, why ?????????
for trans in transfers {
trans.cancel() // cancel transfer that will be sent by updateApplicationContext
let dict = trans.userInfo
session.transferUserInfo(dict) //--> creates enless-transfer cycle !!!!!
}
}
}
else {
print(error)
}
}
In the Apple documentation, it sais about the didFinishUserInfoTransfer method:
The session object calls this method when a data transfer initiated by the
current app finished, either successfully or unsuccessfully. Use this method
to note that the transfer completed or to respond to errors, perhaps by
trying to send the data again at a later time.
So far so good - I understood. But now - there is something I do not understand:
If didFinishUserInfoTransfer is entered and the error == nil, why on earth can the session.outstandingUserInfoTransfers COUNT be bigger than zero ??????
According to the Apple-documentation, the only non-error-state of didFinishUserInfoTransfer should be when the transfer is over !! Bit it does not seem to be over... Why ???
Thanks for any clarification on this.
And also, I am glad of any example-code on how to use these 3 methods correctly !
(i.e.
session.transferUserInfo(dicty)
didFinishUserInfoTransfer
session.outstandingUserInfoTransfers)
It seems that the userInfoTransfer that triggers the didFinishUserInfoTransfer callback is not removed from the outstandingUserInfoTransfers until the delegate callback has returned. To get the behavior you want (where count can go down to 0) you'd want to dispatch_async away from the delegate callback thread. So this should work:
func session(session: WCSession, didFinishUserInfoTransfer userInfoTransfer: WCSessionUserInfoTransfer, error: NSError?) {
if error == nil {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
let transfers = session.outstandingUserInfoTransfers
if transfers.count > 0 { //--> will in most cases now be 0
for trans in transfers {
trans.cancel() // cancel transfer that will be sent by updateApplicationContext
let dict = trans.userInfo
session.transferUserInfo(dict) // ***
}
}
});
}
else {
print(error)
}
}
That said, I don't quite understand why you'd want to cancel all the remaining outstanding userInfoTransfers whenever any of them completes, just to re-queue them (spot in question is indicated by ***)
There is a little misunderstanding, as far as I read the docs: only send again if an error occurs. To have outstanding userInfoTransfers if no error has been raised is the expected behavior; they have not yet successfully been send and are still queued.
Btw. code uses the actual dispatchQueue.
func session(_ session: WCSession, didFinish userInfoTransfer: WCSessionUserInfoTransfer, error: Error?) {
if error != nil { // resend if an error occured
DispatchQueue.main.async {
let transfers = session.outstandingUserInfoTransfers
if transfers.count > 0 {
// print("open transfers: \(transfers.count)")
for trans in transfers {
// print("resend transfer")
trans.cancel() // cancel transfer
let dict = trans.userInfo
session.transferUserInfo(dict) // send again
}
}
}
}
}

Resources