firebase observe not running in background with swift - swift

I am pulling data from firebase realtime database with swift. Everything works fine when the app is open. I am running the loadGame function. After running the observeGame function everything is fine. But when I press the iPhone screen off button and dim the screen, observe is not triggered. (sometimes it works but mostly it doesn't).
After waiting for a while, the application gives an error when I open the screen. because firebase cloud function has changed its database but refStateDate.observe is not triggered so it can't fetch new data.
Note: I'm not killing the app. Shouldn't it be running in the background? I just turn off the screen with the iphone side button.
class startGame{
func loadGame(){
/// some code
fullScreenLoading.shared.isLoading = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { /// removing it doesn't fix it either
GameHomeManager.shared.CreateGame(game: game){
fullScreenLoading.shared.isLoading = false
}
}
}
}
class GameHomeManager: ObservableObject {
static let shared = GameHomeManager()
#Published var games: [ActiveGameModel] = [ActiveGameModel]()
func CreateGame(function: String = #function, game: ActiveGameModel, completion: #escaping () -> Void) {
/// some code
game.initalize(){
return completion()
}
}
}
class ActiveGameModel: ObservableObject {
func initalize(completion: (()->Void)? = nil , function: String = #function) {
/// some code
observeGame() { veriVarmi in
completion?()
}
}
func observeGame(completion: #escaping (Bool) -> Void, function: String = #function){
refStateDate.observe(.value, with: { (snap) in --->>> THE PROBLEM IS HERE. THIS IS NOT TRIGGERING
/// some code
completion(false)
}){ (error) in
}
}
}

Related

SNAudioStreamAnalyzer not stopping sound classification request

I'm a student studying iOS development currently working on a simple AI project that utilizes SNAudioStreamAnalyzer to classify an incoming audio stream from the device's microphone. I can start the stream and analyze audio no problem, but I've noticed I can't seem to get my app to stop analyzing and close the audio input stream when I'm done. At the beginning, I initialize the audio engine and create the classification request like so:
private func startAudioEngine() {
do {
// start the stream of audio data
try audioEngine.start()
let snoreClassifier = try? SnoringClassifier2_0().model
let classifySoundRequest = try audioAnalyzer.makeRequest(snoreClassifier)
try streamAnalyzer.add(classifySoundRequest,
withObserver: self.audioAnalyzer)
} catch {
print("Unable to start AVAudioEngine: \(error.localizedDescription)")
}
}
After I'm done classifying my audio stream, I attempt to stop the audio engine and close the stream like so:
private func terminateNight() {
streamAnalyzer.removeAllRequests()
audioEngine.stop()
stopAndSaveNight()
do {
let session = AVAudioSession.sharedInstance()
try session.setActive(false)
} catch {
print("unable to terminate audio session")
}
nightSummary = true
}
However, after I call the terminateNight() function my app will continue using the microphone and classifying the incoming audio. Here's my SNResultsObserving implementation:
class AudioAnalyzer: NSObject, SNResultsObserving {
var prediction: String?
var confidence: Double?
let snoringEventManager: SnoringEventManager
internal init(prediction: String? = nil, confidence: Double? = nil, snoringEventManager: SnoringEventManager) {
self.prediction = prediction
self.confidence = confidence
self.snoringEventManager = snoringEventManager
}
func makeRequest(_ customModel: MLModel? = nil) throws -> SNClassifySoundRequest {
if let model = customModel {
let customRequest = try SNClassifySoundRequest(mlModel: model)
return customRequest
} else {
throw AudioAnalysisErrors.ModelInterpretationError
}
}
func request(_ request: SNRequest, didProduce: SNResult) {
guard let classificationResult = didProduce as? SNClassificationResult else { return }
let topClassification = classificationResult.classifications.first
let timeRange = classificationResult.timeRange
self.prediction = topClassification?.identifier
self.confidence = topClassification?.confidence
if self.prediction! == "snoring" {
self.snoringEventManager.snoringDetected()
} else {
self.snoringEventManager.nonSnoringDetected()
}
}
func request(_ request: SNRequest, didFailWithError: Error) {
print("ended with error \(didFailWithError)")
}
func requestDidComplete(_ request: SNRequest) {
print("request finished")
}
}
It was my understanding that upon calling streamAnalyzer.removeAllRequests() and audioEngine.stop() the app would stop streaming from the microphone and call the requestDidComplete function, but this isn't the behavior I'm getting. Any help is appreciated!
From OP's edition:
So I've realized it was a SwiftUI problem. I was calling the startAudioEngine() function in the initializer of the view it was declared on. I thought this would be fine, but since this view was embedded in a parent view when SwiftUI updated the parent it was re-initializing my view and as such calling startAudioEngine() again. The solution was to call this function in on onAppear block so that it activates the audio engine only when the view appears, and not when SwiftUI initializes it.
I don't believe you should expect to receive requestDidComplete due to removing a request. You'd expect to receive that when you call completeAnalysis.

How to do Apple Watch Background App Refresh?

I've consulted many variations of background app refresh for Apple Watch so that I can update the complications for my app. However, the process seems very much hit or miss and most of the time it doesn't run at all after some time.
Here is the code I currently have:
BackgroundService.swift
Responsibility: Schedule background refresh and handle download processing and update complications.
import Foundation
import WatchKit
final class BackgroundService: NSObject, URLSessionDownloadDelegate {
var isStarted = false
private let requestFactory: RequestFactory
private let logManager: LogManager
private let complicationService: ComplicationService
private let notificationService: NotificationService
private var pendingBackgroundTask: WKURLSessionRefreshBackgroundTask?
private var backgroundSession: URLSession?
init(requestFactory: RequestFactory,
logManager: LogManager,
complicationService: ComplicationService,
notificationService: NotificationService
) {
self.requestFactory = requestFactory
self.logManager = logManager
self.complicationService = complicationService
self.notificationService = notificationService
super.init()
NotificationCenter.default.addObserver(self,
selector: #selector(handleInitialSchedule(_:)),
name: Notification.Name("ScheduleBackgroundTasks"),
object: nil
)
}
func updateContent() {
self.logManager.debugMessage("In BackgroundService updateContent")
let complicationsUpdateRequest = self.requestFactory.makeComplicationsUpdateRequest()
let config = URLSessionConfiguration.background(withIdentifier: "app.wakawatch.background-refresh")
config.isDiscretionary = false
config.sessionSendsLaunchEvents = true
self.backgroundSession = URLSession(configuration: config,
delegate: self,
delegateQueue: nil)
let backgroundTask = self.backgroundSession?.downloadTask(with: complicationsUpdateRequest)
backgroundTask?.resume()
self.isStarted = true
self.logManager.debugMessage("backgroundTask started")
}
func handleDownload(_ backgroundTask: WKURLSessionRefreshBackgroundTask) {
self.logManager.debugMessage("Handling finished download")
self.pendingBackgroundTask = backgroundTask
}
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL) {
processFile(file: location)
self.logManager.debugMessage("Marking pending background tasks as completed.")
if self.pendingBackgroundTask != nil {
self.pendingBackgroundTask?.setTaskCompletedWithSnapshot(false)
self.backgroundSession?.invalidateAndCancel()
self.pendingBackgroundTask = nil
self.backgroundSession = nil
self.logManager.debugMessage("Pending background task cleared")
}
self.schedule()
}
func processFile(file: URL) {
guard let data = try? Data(contentsOf: file) else {
self.logManager.errorMessage("file could not be read as data")
return
}
guard let backgroundUpdateResponse = try? JSONDecoder().decode(BackgroundUpdateResponse.self, from: data) else {
self.logManager.errorMessage("Unable to decode response to Swift object")
return
}
let defaults = UserDefaults.standard
defaults.set(backgroundUpdateResponse.totalTimeCodedInSeconds,
forKey: DefaultsKeys.complicationCurrentTimeCoded)
self.complicationService.updateTimelines()
self.notificationService.isPermissionGranted(onGrantedHandler: {
self.notificationService.notifyGoalsAchieved(newGoals: backgroundUpdateResponse.goals)
})
self.logManager.debugMessage("Complication updated")
}
func schedule() {
let time = self.isStarted ? 15 * 60 : 60
let nextInterval = TimeInterval(time)
let preferredDate = Date.now.addingTimeInterval(nextInterval)
WKExtension.shared().scheduleBackgroundRefresh(withPreferredDate: preferredDate,
userInfo: nil) { error in
if error != nil {
self.logManager.reportError(error!)
return
}
self.logManager.debugMessage("Scheduled for \(preferredDate)")
}
}
#objc func handleInitialSchedule(_ notification: NSNotification) {
if !self.isStarted {
self.schedule()
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
The flow for the above file's usage is that it will be used by the ExtensionDelegate to schedule background refresh. The first time, it'll schedule a refresh for 1 minute out and then every 15 minutes after that.
Here is the ExtensionDelegate:
import Foundation
import WatchKit
class ExtensionDelegate: NSObject, WKExtensionDelegate {
private var backgroundService: BackgroundService?
private var logManager: LogManager?
override init() {
super.init()
self.backgroundService = DependencyInjection.shared.container.resolve(BackgroundService.self)!
self.logManager = DependencyInjection.shared.container.resolve(LogManager.self)!
}
func isAuthorized() -> Bool {
let defaults = UserDefaults.standard
return defaults.bool(forKey: DefaultsKeys.authorized)
}
func applicationDidFinishLaunching() {
self.logManager?.debugMessage("In applicationDidFinishLaunching")
if isAuthorized() && !(self.backgroundService?.isStarted ?? false) {
self.backgroundService?.schedule()
}
}
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
self.logManager?.debugMessage("In handle backgroundTasks")
if !isAuthorized() {
return
}
for task in backgroundTasks {
self.logManager?.debugMessage("Processing task: \(task.debugDescription)")
switch task {
case let backgroundTask as WKApplicationRefreshBackgroundTask:
self.backgroundService?.updateContent()
backgroundTask.setTaskCompletedWithSnapshot(false)
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
self.backgroundService?.handleDownload(urlSessionTask)
default:
task.setTaskCompletedWithSnapshot(false)
}
}
}
}
When the app is launched from background, it'll try to schedule for the first time in applicationDidFinishLaunching.
From my understanding, WKExtension.shared().scheduleBackgroundRefresh will get called in schedule, then after the preferred time WatchOS will call handle with WKApplicationRefreshBackgroundTask task. Then I will use that to schedule a background URL session task and immediately start it as seen in the updateContent method of BackgroundService. After some time, WatchOS will then call ExtensionDelegate's handle method with WKURLSessionRefreshBackgroundTask and I'll handle that using the handleDownload task. In there, I process the response, update the complications, clear the task, and finally schedule a new background refresh.
I've found it works great if I'm actively working on the app or interacting with it in general. But let's say I go to sleep then the next day the complication will not have updated at all.
Ideally, I'd like for it to function as well as the Weather app complication WatchOS has. I don't interact with the complication, but it reliably updates.
Is the above process correct or are there any samples of correct implementations?
Some of the posts I've consulted:
https://wjwickham.com/posts/refreshing-data-in-the-background-on-watchOS/
https://developer.apple.com/documentation/watchkit/background_execution/using_background_tasks
https://spin.atomicobject.com/2021/01/26/complications-basic-functionality/

DispatchQueue.main.asyncAfter hanging on repeat, but does not hang when using sleep

I am trying to create a Robotic Process Automation tool for Macos using Swift. Users create an Automation that is an array of Step objects and then play it. One of the subclasses of Step is Pause which is supposed to pause the execution for a given number of seconds.
For some reason, execution hangs when I use the DispatchQueue.main.asyncAfter() method in the Pause class. Usually the first run through the automation is fine, but when it goes to repeat, it eventually hangs for much longer. The error goes away when I use sleep() instead.
The other weird thing about this bug is when I open Xcode to try and see what is happening, the hang resolves and execution continues. I am wondering if the process enters background somehow and then the DispatchQueue.main.asyncAfter() doesn't work. I have tried to change the Info.plist "Application does not run in background" to YES, but this doesn't have any effect.
The problem with using sleep() is it blocks the UI thread so users can't stop the automation if they need to. I have tried lots of different variations of threading with DispatchQueue, but it always seems to hang somewhere on repeat execution. I have also tried using a Timer.scheduledTimer() instead of DispatchQueue but that hangs as well. I'm sure I'm missing something simple, but I can't figure it out.
Creating the Step Array and Starting Automation
class AutomationPlayer {
static let shared = AutomationPlayer()
var automation: Automation?
var stepArray: [Step] = []
func play() {
// Create array of steps
guard let steps = automation?.steps, let array = Array(steps) as? [Step] else {
return
}
// If the automation repeats, add more steps to array.
for _ in 0..<(automation?.numberOfRepeats ?? 1) {
for (index, step) in array.enumerated() {
stepArray.append(step)
}
}
// Add small delay to allow window to close before execution.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { [weak self] in
self?.execute(index: 0)
}
}
private func execute(index: Int) {
let step = stepArray[index]
executeStep(step: step) { [weak self] success, error in
guard error == nil else { return }
let newIndex = index + 1
if newIndex < self?.stepArray.count ?? 0 {
//Need a small delay between steps otherwise execution is getting messed up.
usleep(400000)
self?.execute(index: newIndex)
} else {
self?.stepArray = []
}
}
}
private func executeStep(step: Step?, completionHandler: #escaping (Bool, Error?) -> Void) -> Void {
step?.execute(completionHandler: { [weak self] success, error in
guard error == nil else {
completionHandler(false, error)
return
}
completionHandler(true, nil)
})
}
Pause Class
#objc(Pause)
public class Pause: Step {
override func execute(completionHandler: #escaping (Bool, Error?) -> Void) {
print("Pause for: \(self.time) seconds")
// This will eventually hang when the automation repeats itself
DispatchQueue.main.asyncAfter(deadline: .now() + Double(self.time)) {
completionHandler(true, nil)
})
// This will also hang
Timer.scheduledTimer(withTimeInterval: self.time, repeats: false) { timer in
completionHandler(true, nil)
}
// If I use this instead, the automation repeats just fine
sleep(UInt32(self.time))
completionHandler(true, nil)
}
}
So I think I figured it out. MacOS was putting my app into AppNap after a certain period of time which would cause the DispatchQueue.main.async() to stop working. For some reason, AppNap does not affect delays when you use sleep()
I found an answer here
This answer was a little older. I am using SwiftUI to build my mac app so I added this my #main struct
#main
struct Main_App: App {
#State var activity: NSObjectProtocol?
var body: some Scene {
WindowGroup("") {
MainWindow()
.onAppear {
activity = ProcessInfo().beginActivity(options: .userInitiated, reason: "Good Reason")
}
}
}
This seems to prevent the app from going into AppNap and the automation continues. It's pretty ugly, but it works.

How can i connect Swift file and dart file?

I`m connecting Flutter and Swift so that i can use it in Flutter by connecting the public certificate api that works only in Swift. but, I have an issue and i want to ask a question.
What i want to do is import a Swift function through a channel to main.dart so that Swift code can work when a button is pressed in main.dart
How can i get func in Tilko.swift to main.dart
Here is that Structures
enter image description here
main.dart is in ios/lib folder and Tilko.swift is in ios/Runner
There should be a FlutterMethodChannel.swift
and it look like
class MethodChannelFlutter {
var controller: FlutterViewController
var printerChannel: FlutterMethodChannel
init(controller: FlutterViewController) {
self.controller = controller
printerChannel = FlutterMethodChannel(name: "hem_cert_copy_plugin", binaryMessenger: controller.binaryMessenger)
printerChannel.setMethodCallHandler {
(call: FlutterMethodCall, _: #escaping FlutterResult) -> Void in
//Your handler
//...
and your AppDelegate.swift should be like
import UIKit
import Flutter
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
MethodChannelFlutter(controller: controller)
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
I have create a working sample here
https://github.com/theamorn/flutter-template
In this file, I have 3 ways of connecting to Native SDK
calling Native and return value immediately
calling Native and pass to function and return from that function
calling Native and wait for the delegate to callback so we can callback to Flutter
First you add Flutter Channel in iOS
https://github.com/theamorn/flutter-template/blob/main/ios/Runner/AppDelegate.swift
private func initFlutterChannel() {
// com.theamorn.flutter can change to whatever you want, just have to be the same between Flutter and iOS and unique ‘domain prefix. just com.theamorn is not enough
if let controller = window?.rootViewController as? FlutterViewController {
let channel = FlutterMethodChannel(
name: methodChannel,
binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler({ [weak self] (
call: FlutterMethodCall,
result: #escaping FlutterResult) -> Void in
switch call.method {
case "methodNameOne":
if let data = call.arguments as? String {
self?.callNativeSDK(data)
} else {
result(FlutterMethodNotImplemented)
}
case "deviceHasPasscode":
result(self?.isDeviceHasPasscode)
case "returnValue":
if let data = call.arguments as? String {
self?.returnValueSDK(result: result, id: data)
} else {
result(FlutterMethodNotImplemented)
}
default:
result(FlutterMethodNotImplemented)
}
})
}
}
methodNameOne use for calling Native function with a delegate which name should be consistent within Flutter
private func callNativeSDK(_ id: String) {
// You can put any Native SDK in here, if the result return from Delegate
let sdk = ThirdPartySDK(delegate: self, id: id)
sdk.start()
}
private func returnValueSDK(result: FlutterResult, id: String) {
// You can call any SDK in here if the result return immediately
let sdk = ThirdPartySDK(delegate: self, id: id)
return result(sdk.process())
}
To call from Flutter using this
https://github.com/theamorn/flutter-template/blob/main/lib/main.dart
final channel = MethodChannel('com.theamorn.flutter');
try {
await channel.invokeMethod('methodNameOne', "12345");
} on PlatformException catch (e) {
debugPrint("==== Failed to get data '${e.message}' ====");
}
Calling back from Native to Flutter, I call this function in Delegate of mocking SDK
extension AppDelegate: ThirdPartySDKDelegate {
func onFinish(value: String) {
// You can send anything into arguments, String, Boolean, or even Dictionary
// Send data back to Flutter
if let controller = self.window?.rootViewController as? FlutterViewController {
let channel = FlutterMethodChannel(
name: methodChannel,
binaryMessenger: controller.binaryMessenger)
channel.invokeMethod("methodNameTwoFromSDK", arguments: value)
}
}
}
Waiting Value from Native
_waitingDataFromNative() async {
channel.setMethodCallHandler((call) async {
// Receive data from Native
switch (call.method) {
case "methodNameTwoFromSDK":
_nativeValue = call.arguments;
setState(() {});
break;
default:
break;
}
});
}
You see that method name in iOS, and Flutter needs to be exactly the same otherwise they can't see each others.

WatchOS | How can I update my complication?

I am currently working on an watchOS app, for which I am trying to program a dynamic complication.
What I want to achieve is to display a changing value stored inside core data. So the complication value should update in respect to the value.
I have tried watching WWDC videos and so on, but everything seemed too advanced for this.
Here is a simplified form of my implementation:
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: #escaping (CLKComplicationTimelineEntry?) -> Void) {
let dataString = DataManager.shared.fetchData()
let textProvider = CLKTextProvider(format: dataString)
let template = CLKComplicationTemplateUtilitarianLargeFlat(textProvider: textProvider)
handler(CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template))
}
Core Data Part:
func fetch() -> String {
let request = Item.fetchRequest()
do {
let items = try persistenceController.moc.fetch(request)
return ...
} catch { ... }
}
func update() { //<--- Here I want to call getCurrentTimelineEntry and so update the complication
...
persistenceController.saveContext()
#if os(watchOS) //<--- What I have tried, not working...
let server = CLKComplicationServer.sharedInstance()
server.activeComplications?.forEach(server.reloadTimeline)
#endif
}
Thank you!