How can i connect Swift file and dart file? - flutter

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.

Related

firebase observe not running in background with 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
}
}
}

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/

swift show loader while reading data from firebase

i have a list of music at my firebase real time database and i am retriving them but i have 1000 musics data and i want to show loader when i reading data and stop loader when if there is a error(internet connection, or something else) or reading completed.
when i turn off the internet i couldn't get the data and can't stop loader to show error alert like there is no internet connection.
please help me how to handle that problem.
here is my code
didload function called from viewdidload()
private var musicArray = [ItemModal]() {
didSet {
view?.updateTableView()
}
}
func didLoad() {
view?.showLoader()
getAllMusics { ItemModal in
self.musicArray = ItemModal
self.view?.hideLoader()
}
}
func getAllMusics(completion: #escaping ([ItemModal]) -> Void) {
var musicArray = [ItemModal]()
ref.child("music").observeSingleEvent(of: .value) { snapshot in
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot {
guard let data = try? JSONSerialization.data(withJSONObject: rest.value as Any, options: []) else { return }
if let itemModal = try? JSONDecoder().decode(ItemModal.self, from: data) {
musicArray.append(itemModal)
}
}
completion(musicArray)
}
}
You can use reachability function by using https://github.com/ashleymills/Reachability.swift. To get to notify when the internet is turned off, you can implement reachabilityChanged Notification. In the selector method of reachabilityChanged, you can hide the loader.
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(reachabilityChanged), name: .reachabilityChanged)
}
#objc func changed() {
if reachability?.isReachable {
//Continue success implementation
} else {
view?.hideLoder
//Implement Error handling
}
}

Singleton not accessible in Sirikit IntentHandler

I have a project that I want to add sirikit to. I added the intent and wanted to store values in my datastorage which is realm, when I tried to access the function that is used to create this task , I get an eeror. this is my code below
extension IntentHandler : INCreateTaskListIntentHandling {
public func handle(intent: INCreateTaskListIntent,
completion: #escaping (INCreateTaskListIntentResponse) -> Swift.Void) {
guard let title = intent.title else {
completion(INCreateTaskListIntentResponse(code: .failure, userActivity: nil))
return
}
CategoryFunctions.instance.createList(name: title.spokenPhrase,.....)
var tasks: [INTask] = []
if let taskTitles = intent.taskTitles {
let taskTitlesStrings = taskTitles.map {
taskTitle -> String in
return taskTitle.spokenPhrase
}
tasks = createTasks(fromTitles: taskTitlesStrings)
CategoryFunctions.instance.add(tasks: taskTitlesStrings, toList: title.spokenPhrase)
}
let response = INCreateTaskListIntentResponse(code: .success, userActivity: nil)
response.createdTaskList = INTaskList(title: title,
tasks: tasks,
groupName: nil,
createdDateComponents: nil,
modifiedDateComponents: nil,
identifier: nil)
completion(response)
}
}
this singlton instantiation works well in my app but I do not know why I get an error saying Use of unresolved identifier 'CategoryFunctions'
my CategoryFunctions singleton
class CategoryFunctions {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
var database:Realm!
static let instance = CategoryFunctions()
.....
...
Select your file in xcode, on the right, choose the File Inspector, then under Target Membership, pick your Intent.

Alamofire background working on simulator but not on device

As the title mentions I've set up a backgroundURL with Alamofire. It works like a charm in simulator but on my device doesn't. I'm sure I'm missing something here since I'm not that experienced with URL.
Here's the code I have so far:
class NetworkManager {
static let shared = NetworkManager()
private lazy var backgroundManager: Alamofire.SessionManager = {
let bundleIdentifier = MyStruct.identifier
return Alamofire.SessionManager(configuration: URLSessionConfiguration.background(withIdentifier: bundleIdentifier))
}()
var backgroundCompletionHandler: (() -> Void)? {
get{
return backgroundManager.backgroundCompletionHandler
}
set{
backgroundManager.backgroundCompletionHandler = newValue
}
}
}
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
NetworkManager.shared.backgroundCompletionHandler = completionHandler
}
In my ViewController:
func populateArrays(){
Alamofire.request("http://www.aps.anl.gov/Accelerator_Systems_Division/Accelerator_Operations_Physics/sddsStatus/mainStatus.sdds.gz").responseData { response in
switch response.result{
case .success:
print("Validation Successful")
case .failure(let error):
print(error.localizedDescription)
}
if let data = response.result.value{
Solved it. For anyone else that has this problem you need to add the following code to your appDelegate.
func applicationDidEnterBackground(_ application: UIApplication) {
var bgTask = 0
var app = UIApplication.shared
bgTask = app.beginBackgroundTask(expirationHandler: {() -> Void in
app.endBackgroundTask(bgTask)
})
It seems to me that you are not using the background manager you've created. Instead of
Alamofire.request("http://www.aps.anl.gov...")
which calls the default (not background) session manager, you should use:
backgroundManager.request("http://www.aps.anl.gov...")
Which Jon Shier mentioned in the comments by the way.