Skip to content

Commit

Permalink
Merge pull request #1666 from appwrite/fix-android-push-notif-example
Browse files Browse the repository at this point in the history
Update push notification sample on Android
  • Loading branch information
abnegate authored Feb 20, 2025
2 parents 88d42b1 + 1135da3 commit 2460509
Showing 1 changed file with 40 additions and 175 deletions.
215 changes: 40 additions & 175 deletions src/routes/docs/products/messaging/send-push-notifications/+page.markdoc
Original file line number Diff line number Diff line change
Expand Up @@ -39,74 +39,45 @@ First, register for remote notifications in your app delegate's `application(_:d
```swift
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
// Register for remote notifications
UNUserNotificationCenter.current().delegate = self

let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: { granted, error in
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in
if granted {
DispatchQueue.main.async {
if granted {
application.registerForRemoteNotifications()
}
application.registerForRemoteNotifications()
}
}
)

}
return true
}
```


Next, create a handler for when the app receives the push notification device token.
```swift
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
// register device
var token = deviceToken.map { String(format: "%.2hhx", $0) }.joined()

// Save the token to be used later
UserDefaults.standard.set(token, forKey: "apnsToken")
/* store this `token` */
let token = deviceToken.map { String(format: "%.2hhx", $0) }.joined()
}

```

Since the token is saved in `UserDefaults`, you can access it from anywhere in your app.
With this saved `apnsToken`, you can create a push target with Appwrite when the user logs in.
Each push target is associated with an account, heres an example with an email password login.
The same logic applies to all types of login methods.
```swift
func login() async {
do {
let session = try await account.createEmailPasswordSession(
email: username,
password: password
)
let session = try await account.createEmailPasswordSession(email: username, password: password)

guard let token = UserDefaults.standard.string(forKey: "apnsToken") else {
return
}

guard let target = try? await account.createPushTarget(
targetId: ID.unique(),
identifier: token
) else {
return
}
let token = /* Retrieve the stored push token */

UserDefaults.standard.set(target.id, forKey: "targetId")

DispatchQueue.main.async {
self.response = String(describing: session.toMap())
}
try await account.createPushTarget(targetId: ID.unique(), identifier: token)
} catch {
DispatchQueue.main.async {
self.response = error.localizedDescription
}
print("Login failed: \(error.localizedDescription)")
}
}
```
Expand All @@ -122,23 +93,15 @@ initialize Firebase in your main activity and fetch the FCM registration token.
```kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate() {
// ... other logic

// Initialize Firebase
FirebaseApp.initializeApp(this)

// Set the FCM token
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
if (!task.isSuccessful) {
Log.w(TAG, "Fetching FCM registration token failed", task.exception)
return@OnCompleteListener
if (task.isSuccessful) {
/* store this `token` */
val token = task.result
}

// Get new FCM registration token and save it in prefs to be used later.
val token = task.result
val prefs = getSharedPreferences("example", MODE_PRIVATE)

prefs.edit().putString("fcmToken", token).apply()
})
}
}
Expand All @@ -151,48 +114,22 @@ For example, when the user logs in with email and password, your app
can register itself as a target after handling the login.

```kotlin
fun onLogin(
email: String,
password: String,
token: String?,
) {
fun login(email: String, password: String) {
viewModelScope.launch {
try {
// Log in the user
val session = account.createEmailPasswordSession(
email,
password
)
val session = account.createEmailPasswordSession(email, password)

// If a token exists, register a push target with Appwrite.
if (token != null) {
val target = account.createPushTarget(ID.unique(), token)
let token = /* Retrieve the stored push token */

_target.postValue(Event(target))
}

_response.postValue(Event(session.toJson()))
/* store the `target.id` */
val target = account.createPushTarget(ID.unique(), token)
} catch (e: AppwriteException) {
_error.postValue(Event(e))
Log.e("Login", "Failed: ${e.message}")
}
}
}
```

The FCM token that we defined in `sharedPreferenes` will be passed into the `onLogin` handler
to create the push target.
```kotlin
binding.login.setOnClickListener{
viewModel.onLogin(
binding.email.text.toString(),
binding.password.text.toString(),
context
?.getSharedPreferences("example", Context.MODE_PRIVATE)
?.getString("fcmToken", null) ?: null
)
}
```

Lastly, because FCM push tokens can change, we need to add a service to handle FCM token
refreshes and update the target with Appwrite Messaging.

Expand All @@ -201,33 +138,13 @@ the FCM token is updated.

```kotlin
class MessagingService : FirebaseMessagingService() {

companion object {
var account: Account? = null
}

// This is called when FCM token is updated.
override fun onNewToken(token: String) {
super.onNewToken(token)

val prefs = getSharedPreferences("example", MODE_PRIVATE)

prefs.edit().putString("fcmToken", token).apply()

if (account == null) {
return
}

val targetId = prefs.getString("targetId", null)

/* store the `token` */
/* If the user is logged in, update the push target */
runBlocking {
if (targetId == null) {
val target = account!!.createPushTarget(ID.unique(), token)

prefs.edit().putString("targetId", target.id).apply()
} else {
account!!.updatePushTarget(targetId, token)
}
account?.updatePushTarget(/* retrieve saved `target.id` */, token)
}
}
}
Expand Down Expand Up @@ -263,108 +180,56 @@ implement the messaging delegate protocol, and register for remote notifications
```swift
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
// initialize Firebase
FirebaseApp.configure()

// Set the messaging delegate
Messaging.messaging().delegate = self

// Register for remote notifications
UNUserNotificationCenter.current().delegate = self

let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: { granted, error in
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in
if granted {
DispatchQueue.main.async {
if granted {
application.registerForRemoteNotifications()
}
application.registerForRemoteNotifications()
}
}
)

}
return true
}
```
Your APNS token can change, so you need to handle the token refresh event and update the target with Appwrite Messaging.
Implement `didReceiveRegistrationToken`, which is called when the FCM token is updated.
```swift
func messaging(
_ messaging: FirebaseMessaging.Messaging,
didReceiveRegistrationToken fcmToken: String?
) {
guard let fcmToken = fcmToken else {
return
}

// Save the token to be used later
UserDefaults.standard.set(fcmToken , forKey: "fcmToken")

// Get the current Appwrite targetId from UserDefaults
let targetId = UserDefaults.standard.string(forKey: "targetId")
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
/* store the fcmToken */
guard let fcmToken = fcmToken else { return }

Task {
do {
_ = try await account.get()
try await account.createPushTarget(targetId: ID.unique(), identifier: fcmToken)
} catch {
return // if not logged in, don't update the target
}

// if targetId is nil, create a new target
if targetId == nil {
let target = try? await account.createPushTarget(
targetId: ID.unique(),
identifier: fcmToken
)

UserDefaults.standard.set(target?.id , forKey: "targetId")
} else {
// if targetId exists for the Appwrite Account, update the target
_ = try? await account.updatePushTarget(
targetId: targetId!,
identifier: fcmToken
)
print("Failed to create push target: \(error.localizedDescription)")
}
}
}
```
Since the token is saved in `UserDefaults`, you can access it from anywhere in your app.
With this saved `fcmToken`, you can create a push target with Appwrite when the user logs in.
Each push target is associated with an account, heres an example with an email password login.
Each push target is associated with an account, here's an example with an email password login.
The same logic applies to all types of login methods.
```swift
func login() async {
do {
let session = try await account.createEmailPasswordSession(
email: username,
password: password
)

guard let token = UserDefaults.standard.string(forKey: "fcmToken") else {
return
}

guard let target = try? await account.createPushTarget(
targetId: ID.unique(),
identifier: token
) else {
return
}
let session = try await account.createEmailPasswordSession(email: username, password: password)

UserDefaults.standard.set(target.id, forKey: "targetId")
let token = /* Retrieve stored push token */

DispatchQueue.main.async {
self.response = String(describing: session.toMap())
}
let target = try await account.createPushTarget(targetId: ID.unique(), identifier: token)
} catch {
DispatchQueue.main.async {
self.response = error.localizedDescription
}
print("Login failed: \(error.localizedDescription)")
}
}

```

If you have disabled method swizzling, or you are building a SwiftUI app,
Expand Down

0 comments on commit 2460509

Please sign in to comment.