Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update push notification sample on Android #1666

Merged
merged 4 commits into from
Feb 20, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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