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.