I am attempting to call a SQLite insert method from the AppDelegate.swft method didReceiveRemoteNotiication. I have the following code :
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
let db = try! Connection()
//try db.run(users.insert(email <- "xxx#gmail.com", name <- userInfo))
print(userInfo)
}
I want to insert the string "userInfo" when the remote notification is received, however when I attempt to run the commented code I get "Command failed due to signal: Abort trap: 6" error.
I was able to achieve this with my android version of the app, my question is how can I make this work for Swift 3.0?
Related
Short version of my question: I have a Flutter mobile app that uses AWS Amplify to handle user
sign-up and log-in. Now I need to add push notification capability (with AWS SNS, APN etc.)
to the app, and in particular I need to let the app itself to create the platform endpoint.
So now I have to call the CreatePlatformEndpoint function in an Amplify Flutter
mobile app, and I don't know how to do it correctly. Any idea?
Here's what I've done so far: Firstly, I configured AWS Cognito and added Amplify
to my app so that the user can sign up and log in. The relevant code looks something
like the following:
// Sign Up
var userAttributes = {
CognitoUserAttributeKey.email: email,
};
await Amplify.Auth.signUp(
username: email,
password: password,
options: CognitoSignUpOptions(
userAttributes: userAttributes,
),
);
// Log In
var res = await Amplify.Auth.signIn(
username: email,
password: password,
);
if (res.isSignedIn) {
....
} else {
....
}
This part is successful and the code works all right.
Secondly, I configured APN, AWS SNS, and the iOS module of the app, then modified
AppDelegate to something like the following:
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
registerForNotifications()
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func registerForNotifications() {
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, _ in
guard granted else { return }
self?.getNotificationSettings()
}
}
private func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
override func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken tokenData: Data
) {
let token = tokenData.map { String(format: "%02.2hhx", $0) }.joined()
print("token: \(token)")
}
override func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void
) {
completionHandler([.badge, .sound, .alert])
}
This part is also successful. The iPhone can obtain and print the device token,
and when I manually create the endpoint in AWS Management Console and send a notification
from there, the iPhone can receive and show the notification.
Finally, I tried to let the app automatically create the endpoint in SNS when it got
the device token. I referenced this article, and managed to modify the
didRegisterForRemoteNotificationsWithDeviceToken method to the following:
override func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken tokenData: Data
) {
let token = tokenData.map { String(format: "%02.2hhx", $0) }.joined()
print("token: \(token)")
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: ......, identityPoolId: ".........")
let configuration = AWSServiceConfiguration(region: ......, credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
let appArn = "arn:aws:sns:........"
let platformEndpointRequest = AWSSNSCreatePlatformEndpointInput()!
platformEndpointRequest.customUserData = "........."
platformEndpointRequest.token = token
platformEndpointRequest.platformApplicationArn = appArn
let sns = AWSSNS.default()
sns.createPlatformEndpoint(platformEndpointRequest) { response, error in
....
}
}
This part is only partially successful. The endpoint is indeed created in SNS,
and I can verify this in AWS Management Console. But there are serious problems:
the user is logged out after the above code is executed
when the user tries to log back in, the app crashes with an "unexpectedly found nil" exception
Here's the crash log:
AWSMobileClient/AWSMobileClient+SignIn.swift:66: Fatal error: Unexpectedly found nil while unwrapping an Optional value
AWSMobileClient/AWSMobileClient+SignIn.swift:66: Fatal error: Unexpectedly found nil while unwrapping an Optional value
* thread #4, queue = 'NSOperationQueue 0x107d22800 (QOS: UNSPECIFIED)', stop reason = Fatal error: Unexpectedly found nil while unwrapping an Optional value
frame #0: 0x000000019b861060 libswiftCore.dylib`_swift_runtime_on_report
libswiftCore.dylib`_swift_runtime_on_report:
-> 0x19b861060 : ret
libswiftCore.dylib`_swift_reportToDebugger:
0x19b861064 : b 0x19b861060 ; _swift_runtime_on_report
libswiftCore.dylib`_swift_shouldReportFatalErrorsToDebugger:
0x19b861068 : adrp x8, 324077
0x19b86106c : ldrb w0, [x8, #0x611]
Target 0: (Runner) stopped.
I think maybe the defaultServiceConfiguration = configuration thing conflicts
with Amplify, so I try removing that part and modify the code to:
override func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken tokenData: Data
) {
let token = tokenData.map { String(format: "%02.2hhx", $0) }.joined()
print("token: \(token)")
let appArn = "arn:aws:sns:........"
let platformEndpointRequest = AWSSNSCreatePlatformEndpointInput()!
platformEndpointRequest.customUserData = "........."
platformEndpointRequest.token = token
platformEndpointRequest.platformApplicationArn = appArn
let sns = AWSSNS.default()
sns.createPlatformEndpoint(platformEndpointRequest) { response, error in
....
}
}
but then the code crashes at the createPlatformEndpoint call, and here's the crash log:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'The service configuration is `nil`. You need to configure `awsconfiguration.json`, `Info.plist` or set `defaultServiceConfiguration` before using this method.'
*** First throw call stack:
(0x197c1f128 0x1ab932480 0x102d7fcf4 0x197857298 0x1977faf90 0x102d7fb5c 0x102c875a0 0x102c86e4c 0x102c872c0 0x19a4d8c34 0x1978562b0 0x197857298 0x197805ce4 0x197b9e170 0x197b985d0 0x197b976a8 0x1ae247570 0x19a4b5370 0x19a4ba8ec 0x102c88400 0x197876140)
libc++abi: terminating with uncaught exception of type NSException
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
frame #0: 0x00000001c38b47b0 libsystem_kernel.dylib`__pthread_kill + 8
libsystem_kernel.dylib`__pthread_kill:
-> 0x1c38b47b0 : b.lo 0x1c38b47cc ;
0x1c38b47b4 : stp x29, x30, [sp, #-0x10]!
0x1c38b47b8 : mov x29, sp
0x1c38b47bc : bl 0x1c38911fc ; cerror_nocancel
Target 0: (Runner) stopped.
So what should I do? What's the correct way to create an endpoint in an Amplify Flutter app?
OK, I'll answer my own question. I asked the Amplify-Flutter people and learned that there was currently no way to do what I want. I was advised to create a feature request ticket in their repository. I decided to turn to another approach, i.e. creating a Lambda that did the job on the backend, and it worked fine.
When I create a CKSubscription, didReceiveRemoteNotification gets called on iOS just fine but not on MacOS. I came across a 2015 SO thread talking about a bug and the suggested workaround was to set the notification info's soundName to an empty string - unfortunately that didn't resolve the issue for me.
Here is how I register my remote notifications:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let subscription = CKQuerySubscription(recordType: "Reminder", predicate: NSPredicate(format: "TRUEPREDICATE"), options: [.firesOnRecordCreation, .firesOnRecordUpdate])
// Here we customize the notification message
let info = CKSubscription.NotificationInfo()
info.shouldSendContentAvailable = true
info.desiredKeys = ["identifier", "title", "date"]
info.soundName = ""
subscription.notificationInfo = info
// Save the subscription to Private Database in Cloudkit
CKContainer.default().privateCloudDatabase.save(subscription, completionHandler: { subscription, error in
if error == nil {
// Subscription saved successfully
} else {
// Error occurred
}
})
}
This has to do with the bundle identifier being different on Mac Catalyst. Thanks to the soon to be introduced universal app purchase, catalyst apps can now bear the same bundle identifier as their iOS counterpart, and that fixes the issue.
Note that I was also experiencing issues with cloudkit key values not syncing on Mac (NSUbiquitousKeyValueStore). Having a single bundle id for Mac and iOS fixed the problem too.
Im working with sinch and using firebase as my database and using its cloud functions to send notifications. Sending notifications are working fine but the shouldSendPushNotifications function but it never gets called so i can't add the sinch payload to the notification data and I couldn't find anything on the internet of a solid working example so I gave up on that and decided to send the SINcall object to the database and the callee will retrieve it and answer that.
But the problem is that firebase only stores strings so when I retrieve the SINCall I stored i get an error Could not cast value of type '__NSCFString' (0x102096998) to '__ObjC.SINCall' (0x10209ab08). So is there a way to convert this to a SINCall so I can answer, decline, etc with it?
EDIT:
this is where i have my code in AppDelegate.swift
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("USERINFO: \(userInfo["sin"]!)") <-- prints call: <SINCallImpl: 0x1c00f2600>
let incomingCall = userInfo["sin"]!
SINClientManager.shared.call = incomingCall as! SINCall <-- Error occurs
...
}
You need to send the data to the sinch client to set up the call, you cant convert a string to a call.
NSString* payload = [remotePush objectForKey:#"SIN"];
// Get previously initiated Sinch client
id<SINClient> client = [self sinchClient];
id<SINNotificationResult> result = [client relayRemotePushNotificationPayload:payload];
if (result.isCall && result.callResult.isTimedOut) {
// Present alert notifying about missed call
} else if (!result.isValid) {
// Handle error
}
First of all, i have no problem for FCM, firebase token is never null every time tokenRefreshNotification is called. But after, i add Google analytics, i got weird problem for Firebase token. Every time i turn off and turn on notification in my app settings used
UIApplication.shared.registerForRemoteNotifications()
my tokenRefreshNotification is called continously and it doesn't stop looping until i force close my apps. At first, my app crash, and when i try to trace it with NsLog, i found that Firebase token is null. The problem occurs only when i`m using my apps installed from TestFlight / production. When i try it from my Xcode builder, firebase token is null only once, but the second call, firebase token is exist and it stop working.
For google Analytics, it works fine and in my GoogleService-info.plist,
i have set IS_ANALYTICS_ENABLED to YES and IS_GCM_ENABLED to YES also. For the other, IS_ADS_ENABLED = YES, IS_APPINVITE_ENABLED = NO, and IS_SIGNIN_ENABLED = YES
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
FIRApp.configure()
NotificationCenter.default.addObserver(self, selector: #selector(self.registerNotification), name: NSNotification.Name(rawValue: "registerNotification"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.tokenRefreshNotification(_:)), name: .firInstanceIDTokenRefresh, object: nil)
setupGoogleAnalytics()
return true
}
//when firebase token is null, this function is working continously until my firebase token is exist
func tokenRefreshNotification(_ notification: Notification) {
print("token Refresh")
if FIRInstanceID.instanceID().token() == nil{
NSLog("firebase token is null")
}
if (UserDefaults.standard.object(forKey: "id") != nil) && FIRInstanceID.instanceID().token() != nil{
FIRInstanceID.instanceID().getWithHandler({ (instanceID, error) in
NSLog("instanceID: \(instanceID!)")
//save firebase token to my database, sorry i can`t show it
})
}
// Connect to FCM since connection may have failed when attempted before having a token.
connectToFcm()
}
Note: At the first launch, i called my RegisterForRemoteNotifications, at TestFlight version, when tokenRefreshNotification is called, the firebase token is null, but the second call, firebase token is exist, so it could stop. But, when i run my app from Xcode, the first call is success because firebase token is not null.
i have figured it out! just change ANPS Type token from Sandbox to Unknown!
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// With swizzling disabled you must set the APNs token here.
FIRInstanceID.instanceID().setAPNSToken(deviceToken, type: FIRInstanceIDAPNSTokenType.unknown)
}
As Parse crash report is depreciating, we are moving to Google Analytics.
I follow the guide to and receive view tracking and exception reports successfully.
here is how I setup the GA
// Configure tracker from GoogleService-Info.plist.
var configureError:NSError?
GGLContext.sharedInstance().configureWithError(&configureError)
assert(configureError == nil, "Error configuring Google services: \(configureError)")
// Optional: configure GAI options.
let gai = GAI.sharedInstance()
gai.trackerWithTrackingId("UA-XXXXXX-1")
gai.trackUncaughtExceptions = true // report uncaught exceptions
gai.dispatchInterval = 1
gai.defaultTracker.allowIDFACollection = true
#if DEBUG
gai.logger.logLevel = GAILogLevel.Verbose // remove before app release
#endif
And I tried to make a crash by (in AppDelegate.swift didFinishLaunchingWithOptions ):
delay(20.0) { () -> () in
let _ = [String]()[10];
}
And I can't get any crash report from GA dashboard.
I've tried to move this line to an IBAction, but failed.
My testing steps:
debug on device ->(20s)-> crash
debug on device 2nd time ->(20s)-> crash
run the app without debugging ->(20s)-> crash
run the app without debugging ->(20s)-> crash
It turns out that Google Analytics only supports reporting of uncaught Objective-C exceptions. It does not report Swift throws. The following creates a Swift throw (and is not reported by GA):
let crashArray:Array = [0]
crashArray[1]
The following does create an Objective-C exception and is reported by GA:
let array = NSArray()
let _ = array.objectAtIndex(99)
Some useful information here...
How should I use NSSetUncaughtExceptionHandler in Swift
We can force an Objective-C exception from within Swift by using the following command (i.e. from within a catch).
let error = NSError(domain: "Some error.", code: 0, userInfo: nil)
NSException.raise("Exception", format:"Error: %#", arguments:getVaList([error ?? "nil"]))
There is not a way I can find to automatically capture all throws and relay them as an Objective-C exception.