diff --git a/androidApp/src/main/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml
index 28da1f2081..e863e54cd8 100644
--- a/androidApp/src/main/AndroidManifest.xml
+++ b/androidApp/src/main/AndroidManifest.xml
@@ -37,6 +37,9 @@
+
diff --git a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/ContentView.kt b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/ContentView.kt
index c2ad1e8472..c097094892 100644
--- a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/ContentView.kt
+++ b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/ContentView.kt
@@ -41,7 +41,7 @@ import com.mbta.tid.mbta_app.android.state.getGlobalData
import com.mbta.tid.mbta_app.android.state.subscribeToAlerts
import com.mbta.tid.mbta_app.android.util.ManageErrorBannerBackgrounding
import com.mbta.tid.mbta_app.android.util.SettingsCache
-import com.mbta.tid.mbta_app.android.util.fcmToken
+import com.mbta.tid.mbta_app.android.util.fcmInstallationId
import com.mbta.tid.mbta_app.cache.ScheduleCache
import com.mbta.tid.mbta_app.model.FeaturePromo
import com.mbta.tid.mbta_app.model.OnboardingScreen
@@ -105,8 +105,8 @@ fun ContentView(
val pendingFeaturePromos = viewModel.pendingFeaturePromos.collectAsState().value
val currentLocale = stringResource(R.string.current_locale)
- LaunchedEffect(fcmToken, notificationsEnabled) {
- fcmToken?.let {
+ LaunchedEffect(fcmInstallationId, notificationsEnabled) {
+ fcmInstallationId?.let {
val favorites = favoritesUsecases.getRouteStopDirectionFavorites()
val subscriptions = SubscriptionRequest.fromFavorites(favorites, includeAccessibility)
subscriptionsRepository.updateSubscriptions(
@@ -118,7 +118,9 @@ fun ContentView(
}
}
- LaunchedEffect(fcmToken, globalResponse) { favoritesViewModel.clearStaleFavorites(fcmToken) }
+ LaunchedEffect(fcmInstallationId, globalResponse) {
+ favoritesViewModel.clearStaleFavorites(fcmInstallationId)
+ }
val locationDataManager = rememberLocationDataManager()
val mapViewportState = rememberMapViewportState {
diff --git a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/MainActivity.kt b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/MainActivity.kt
index 18027cae81..843267a8cc 100644
--- a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/MainActivity.kt
+++ b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/MainActivity.kt
@@ -15,7 +15,6 @@ import com.google.firebase.analytics.analytics
import com.google.firebase.messaging.FirebaseMessaging
import com.mbta.tid.mbta_app.android.analytics.AnalyticsProvider
import com.mbta.tid.mbta_app.android.util.LocalLocationClient
-import com.mbta.tid.mbta_app.android.util.fcmToken
import com.mbta.tid.mbta_app.initializeSentry
import com.mbta.tid.mbta_app.routes.DeepLinkState
import kotlinx.coroutines.flow.MutableStateFlow
@@ -54,7 +53,7 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initSentry()
- getFCMToken()
+ getFCMInstallationId()
handleIntent(intent)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
enableEdgeToEdge()
@@ -83,7 +82,7 @@ class MainActivity : ComponentActivity() {
}
}
- private fun getFCMToken() {
- FirebaseMessaging.getInstance().token.addOnSuccessListener { fcmToken = it }
+ private fun getFCMInstallationId() {
+ FirebaseMessaging.getInstance().register()
}
}
diff --git a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/notification/MBTAGoMessagingService.kt b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/notification/MBTAGoMessagingService.kt
index 8680e79631..9fd9d213c5 100644
--- a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/notification/MBTAGoMessagingService.kt
+++ b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/notification/MBTAGoMessagingService.kt
@@ -14,20 +14,13 @@ import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.mbta.tid.mbta_app.android.MainActivity
import com.mbta.tid.mbta_app.android.R
-import com.mbta.tid.mbta_app.android.util.fcmToken
+import com.mbta.tid.mbta_app.android.util.fcmInstallationId
import kotlin.random.Random
class MBTAGoMessagingService : FirebaseMessagingService() {
-
- /**
- * If you want to target single devices or create device groups, you'll need this token. Because
- * the token could be rotated after initial startup, it is strongly recommended to retrieve the
- * latest updated registration token. If you need it any other time:
- * `FirebaseMessaging.getInstance().token.addOnCompleteListener {...}`
- */
- override fun onNewToken(token: String) {
- super.onNewToken(token)
- fcmToken = token
+ override fun onRegistered(installationId: String) {
+ super.onRegistered(installationId)
+ fcmInstallationId = installationId
}
// if a notification is sent when the app is in the foreground, we have to create the
diff --git a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/pages/EditFavoritesPage.kt b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/pages/EditFavoritesPage.kt
index a6c21f2b4d..6bc24df6a8 100644
--- a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/pages/EditFavoritesPage.kt
+++ b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/pages/EditFavoritesPage.kt
@@ -67,7 +67,7 @@ import com.mbta.tid.mbta_app.android.favorites.NoFavoritesView
import com.mbta.tid.mbta_app.android.util.IsLoadingSheetContents
import com.mbta.tid.mbta_app.android.util.SettingsCache
import com.mbta.tid.mbta_app.android.util.Typography
-import com.mbta.tid.mbta_app.android.util.fcmToken
+import com.mbta.tid.mbta_app.android.util.fcmInstallationId
import com.mbta.tid.mbta_app.android.util.getLabels
import com.mbta.tid.mbta_app.android.util.key
import com.mbta.tid.mbta_app.android.util.modifiers.haloContainer
@@ -157,7 +157,7 @@ fun EditFavoritesPage(
mapOf(deletedFavorite to null),
EditFavoritesContext.Favorites,
deletedFavorite.direction,
- fcmToken,
+ fcmInstallationId,
currentLocale,
)
@@ -173,7 +173,7 @@ fun EditFavoritesPage(
mapOf(deletedFavorite to deletedSettings),
EditFavoritesContext.Favorites,
deletedFavorite.direction,
- fcmToken,
+ fcmInstallationId,
currentLocale,
)
toastViewModel.hideToast()
diff --git a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/pages/MorePage.kt b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/pages/MorePage.kt
index ba79ce4685..c34193d329 100644
--- a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/pages/MorePage.kt
+++ b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/pages/MorePage.kt
@@ -43,7 +43,7 @@ import com.mbta.tid.mbta_app.android.more.MoreButton
import com.mbta.tid.mbta_app.android.more.MoreSectionView
import com.mbta.tid.mbta_app.android.util.SettingsCache
import com.mbta.tid.mbta_app.android.util.Typography
-import com.mbta.tid.mbta_app.android.util.fcmToken
+import com.mbta.tid.mbta_app.android.util.fcmInstallationId
import com.mbta.tid.mbta_app.android.util.key
import com.mbta.tid.mbta_app.android.util.modifiers.haloContainer
import com.mbta.tid.mbta_app.model.Dependency
@@ -133,7 +133,7 @@ fun MorePage(
when (setting) {
Settings.StationAccessibility -> {
if (notificationsEnabled) {
- fcmToken?.let {
+ fcmInstallationId?.let {
viewModel.updateAccessibility(
it,
newValue,
diff --git a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/pages/SaveFavoritePage.kt b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/pages/SaveFavoritePage.kt
index 34bf36b46e..7721942238 100644
--- a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/pages/SaveFavoritePage.kt
+++ b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/pages/SaveFavoritePage.kt
@@ -48,7 +48,7 @@ import com.mbta.tid.mbta_app.android.favorites.NotificationSettingsWidget
import com.mbta.tid.mbta_app.android.state.getGlobalData
import com.mbta.tid.mbta_app.android.util.SettingsCache
import com.mbta.tid.mbta_app.android.util.Typography
-import com.mbta.tid.mbta_app.android.util.fcmToken
+import com.mbta.tid.mbta_app.android.util.fcmInstallationId
import com.mbta.tid.mbta_app.android.util.getLabels
import com.mbta.tid.mbta_app.android.util.key
import com.mbta.tid.mbta_app.android.util.notificationPermissionState
@@ -153,7 +153,7 @@ fun SaveFavoritePage(
update,
context,
selectedDirection,
- fcmToken,
+ fcmInstallationId,
currentLocale,
)
}
diff --git a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/util/FCMInstallationId.kt b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/util/FCMInstallationId.kt
new file mode 100644
index 0000000000..b47c2d32cc
--- /dev/null
+++ b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/util/FCMInstallationId.kt
@@ -0,0 +1,8 @@
+package com.mbta.tid.mbta_app.android.util
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+
+/** Set in MBTAGoMessagingService or retrieved on app launch in MainActivity */
+var fcmInstallationId: String? by mutableStateOf(null)
diff --git a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/util/FCMToken.kt b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/util/FCMToken.kt
deleted file mode 100644
index f3391c69f3..0000000000
--- a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/util/FCMToken.kt
+++ /dev/null
@@ -1,4 +0,0 @@
-package com.mbta.tid.mbta_app.android.util
-
-/** Set in MBTAGoMessagingService or retrieved on app launch in MainActivity */
-var fcmToken: String? = null
diff --git a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/util/manageFavorites.kt b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/util/manageFavorites.kt
index 19a6169b71..54aa3fa8fd 100644
--- a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/util/manageFavorites.kt
+++ b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/util/manageFavorites.kt
@@ -35,7 +35,7 @@ fun manageFavorites(favoritesUseCases: FavoritesUsecases = koinInject()): Manage
newValues,
context,
defaultDirection,
- if (notificationsEnabled) fcmToken else null,
+ if (notificationsEnabled) fcmInstallationId else null,
currentLocale,
)
}
diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift
index 127fe541e4..cbffe5bd68 100644
--- a/iosApp/iosApp/ContentView.swift
+++ b/iosApp/iosApp/ContentView.swift
@@ -18,7 +18,7 @@ struct ContentView: View {
@EnvironmentObject var viewportProvider: ViewportProvider
@ObservedObject var contentVM: ContentViewModel
- @ObservedObject var fcmTokenContainer = FcmTokenContainer.shared
+ @ObservedObject var fcmInstallationIdContainer = FcmInstallationIdContainer.shared
@ObservedObject var notificationDeepLinkOwner = AppDelegate.notificationDeepLinkOwner
@State private var contentHeight: CGFloat = UIScreen.current?.bounds.height ?? 0
@@ -109,16 +109,16 @@ struct ContentView: View {
}
.alerts($alerts)
.global($globalData, errorKey: ErrorKey(sheets: [], id: "ContentView"))
- .handleFcmTokenSubscriptions(
- fcmToken: fcmTokenContainer.token,
+ .handleFcmInstallationIdSubscriptions(
+ fcmInstallationId: fcmInstallationIdContainer.installationId,
includeAccessibility: includeAccessibility,
notificationsEnabled: notificationsFlag
)
- .onChange(of: fcmTokenContainer.token) { token in
- favoritesVM.clearStaleFavorites(fcmToken: token)
+ .onChange(of: fcmInstallationIdContainer.installationId) { installationId in
+ favoritesVM.clearStaleFavorites(fcmInstallationId: installationId)
}
.onChange(of: globalData) { _ in
- favoritesVM.clearStaleFavorites(fcmToken: fcmTokenContainer.token)
+ favoritesVM.clearStaleFavorites(fcmInstallationId: fcmInstallationIdContainer.installationId)
}
.onChange(of: contentVM.defaultTab) { newTab in
// if we aren't on an entrypoint, then the default tab may have loaded after
@@ -663,7 +663,7 @@ struct ContentView: View {
updatedFavorites: favorites,
context: context,
defaultDirection: selectedDirection,
- fcmToken: fcmTokenContainer.token,
+ fcmInstallationId: fcmInstallationIdContainer.installationId,
)
},
navCallbacks: navCallbacks,
diff --git a/iosApp/iosApp/GoogleService-Info.plist b/iosApp/iosApp/GoogleService-Info.plist
index d6886410dc..edc7c62182 100644
--- a/iosApp/iosApp/GoogleService-Info.plist
+++ b/iosApp/iosApp/GoogleService-Info.plist
@@ -2,6 +2,8 @@
+ FirebaseMessagingInstallationIdEnabled
+
GCM_SENDER_ID
304904690247
PLIST_VERSION
diff --git a/iosApp/iosApp/IOSApp.swift b/iosApp/iosApp/IOSApp.swift
index 120de6be02..e8f2b6b49d 100644
--- a/iosApp/iosApp/IOSApp.swift
+++ b/iosApp/iosApp/IOSApp.swift
@@ -36,13 +36,7 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
func application(_: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
- Messaging.messaging().token { token, error in
- if let error {
- print("Error fetching FCM registration token: \(error)")
- } else if let token {
- FcmTokenContainer.shared.token = token
- }
- }
+ Messaging.messaging().register(completion: { _ in })
}
func application(
@@ -61,8 +55,8 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
completionHandler(.noData)
}
- func messaging(_: Messaging, didReceiveRegistrationToken token: String?) {
- FcmTokenContainer.shared.token = token
+ func messaging(_: Messaging, didReceiveRegistration installationId: String?) {
+ FcmInstallationIdContainer.shared.installationId = installationId
}
func userNotificationCenter(
diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist
index d3c3a2d91c..98a9cddb74 100644
--- a/iosApp/iosApp/Info.plist
+++ b/iosApp/iosApp/Info.plist
@@ -2,6 +2,8 @@
+ FirebaseMessagingInstallationIdEnabled
+
FirebaseAutomaticScreenReportingEnabled
FirebaseAppDelegateProxyEnabled
diff --git a/iosApp/iosApp/Pages/Favorites/EditFavoritesPage.swift b/iosApp/iosApp/Pages/Favorites/EditFavoritesPage.swift
index d120ce1bc3..6025a068eb 100644
--- a/iosApp/iosApp/Pages/Favorites/EditFavoritesPage.swift
+++ b/iosApp/iosApp/Pages/Favorites/EditFavoritesPage.swift
@@ -23,7 +23,7 @@ struct EditFavoritesPage: View {
let toastVM: IToastViewModel
let globalRepository: IGlobalRepository = RepositoryDI().global
- @ObservedObject var fcmTokenContainer = FcmTokenContainer.shared
+ @ObservedObject var fcmInstallationIdContainer = FcmInstallationIdContainer.shared
@EnvironmentObject var settingsCache: SettingsCache
var groupByStop: Bool { settingsCache.get(.favoritesByStop) }
@@ -36,7 +36,7 @@ struct EditFavoritesPage: View {
updatedFavorites: [rsd: nil],
context: .favorites,
defaultDirection: rsd.direction,
- fcmToken: fcmTokenContainer.token,
+ fcmInstallationId: fcmInstallationIdContainer.installationId,
)
let labels = rsd.getLabels(globalResponse)
@@ -70,7 +70,7 @@ struct EditFavoritesPage: View {
updatedFavorites: [rsd: settings],
context: .favorites,
defaultDirection: rsd.direction,
- fcmToken: fcmTokenContainer.token,
+ fcmInstallationId: fcmInstallationIdContainer.installationId,
)
}
),
diff --git a/iosApp/iosApp/Pages/More/MorePage.swift b/iosApp/iosApp/Pages/More/MorePage.swift
index db44d7f404..0dc5016402 100644
--- a/iosApp/iosApp/Pages/More/MorePage.swift
+++ b/iosApp/iosApp/Pages/More/MorePage.swift
@@ -20,7 +20,7 @@ struct MorePage: View {
@State var showingBuildNumber = false
@State private var path = NavigationPath()
- @ObservedObject var fcmTokenContainer = FcmTokenContainer.shared
+ @ObservedObject var fcmInstallationIdContainer = FcmInstallationIdContainer.shared
@EnvironmentObject var settingsCache: SettingsCache
private let translation = NSLocalizedString("key/current_locale", comment: "")
@@ -87,9 +87,10 @@ struct MorePage: View {
onChangeSetting: { setting, newValue in
switch setting {
case .stationAccessibility:
- if settingsCache.get(.notifications), let fcmToken = fcmTokenContainer.token {
+ if settingsCache.get(.notifications),
+ let fcmInstallationId = fcmInstallationIdContainer.installationId {
viewModel.updateAccessibility(
- fcmToken: fcmToken,
+ fcmInstallationId: fcmInstallationId,
includeAccessibility: newValue,
locale: NSLocalizedString("key/current_locale", comment: "")
)
diff --git a/iosApp/iosApp/Pages/RouteDetails/RouteStopListView.swift b/iosApp/iosApp/Pages/RouteDetails/RouteStopListView.swift
index 1e1bbece1d..69aa8caa72 100644
--- a/iosApp/iosApp/Pages/RouteDetails/RouteStopListView.swift
+++ b/iosApp/iosApp/Pages/RouteDetails/RouteStopListView.swift
@@ -210,7 +210,7 @@ struct RouteStopListContentView: View {
@State var displayedToast: ToastViewModel.Toast?
@State var firstTimeToast: ToastViewModel.Toast?
- @ObservedObject var fcmTokenContainer = FcmTokenContainer.shared
+ @ObservedObject var fcmInstallationIdContainer = FcmInstallationIdContainer.shared
@EnvironmentObject var settingsCache: SettingsCache
let inspection = Inspection()
@@ -484,7 +484,7 @@ struct RouteStopListContentView: View {
updatedFavorites: updatedValues,
context: editContext,
defaultDirection: selectedDirection,
- fcmToken: fcmTokenContainer.token,
+ fcmInstallationId: fcmInstallationIdContainer.installationId,
)
}
}
diff --git a/iosApp/iosApp/Pages/StopDetails/StopDetailsFilteredView.swift b/iosApp/iosApp/Pages/StopDetails/StopDetailsFilteredView.swift
index 6a0bc069c1..40910875ba 100644
--- a/iosApp/iosApp/Pages/StopDetails/StopDetailsFilteredView.swift
+++ b/iosApp/iosApp/Pages/StopDetails/StopDetailsFilteredView.swift
@@ -37,7 +37,7 @@ struct StopDetailsFilteredView: View {
@State var inSaveFavoritesFlow = false
@State var alertSummaries: [String: AlertSummary?] = [:]
- @ObservedObject var fcmTokenContainer = FcmTokenContainer.shared
+ @ObservedObject var fcmInstallationIdContainer = FcmInstallationIdContainer.shared
@EnvironmentObject var settingsCache: SettingsCache
@@ -294,7 +294,7 @@ struct StopDetailsFilteredView: View {
updatedFavorites: updatedValues,
context: .stopDetails,
defaultDirection: routeStopDirection.direction,
- fcmToken: fcmTokenContainer.token,
+ fcmInstallationId: fcmInstallationIdContainer.installationId,
)
},
onClose: { inSaveFavoritesFlow = false },
diff --git a/iosApp/iosApp/Utils/Extensions/FavoritesUsecasesExtension.swift b/iosApp/iosApp/Utils/Extensions/FavoritesUsecasesExtension.swift
index 60e2a2bae4..2c8dfa4c26 100644
--- a/iosApp/iosApp/Utils/Extensions/FavoritesUsecasesExtension.swift
+++ b/iosApp/iosApp/Utils/Extensions/FavoritesUsecasesExtension.swift
@@ -14,13 +14,13 @@ extension FavoritesUsecases {
newValues: [RouteStopDirection: FavoriteSettings?],
context: EditFavoritesContext,
defaultDirection: Int32,
- fcmToken: String?,
+ fcmInstallationId: String?,
) async throws {
try await __updateRouteStopDirections(
newValues: newValues as [RouteStopDirection: Any],
context: context,
defaultDirection: .init(int: defaultDirection),
- fcmToken: fcmToken,
+ fcmInstallationId: fcmInstallationId,
locale: NSLocalizedString("key/current_locale", comment: ""),
)
}
diff --git a/iosApp/iosApp/Utils/FcmInstallationIdContainer.swift b/iosApp/iosApp/Utils/FcmInstallationIdContainer.swift
new file mode 100644
index 0000000000..cb0b3aaf26
--- /dev/null
+++ b/iosApp/iosApp/Utils/FcmInstallationIdContainer.swift
@@ -0,0 +1,14 @@
+//
+// FcmInstallationIdContainer.swift
+// iosApp
+//
+// Created by esimon on 11/28/25.
+// Copyright © 2025 MBTA. All rights reserved.
+//
+
+import SwiftUI
+
+class FcmInstallationIdContainer: ObservableObject {
+ static let shared = FcmInstallationIdContainer()
+ @Published var installationId: String?
+}
diff --git a/iosApp/iosApp/Utils/FcmTokenContainer.swift b/iosApp/iosApp/Utils/FcmTokenContainer.swift
deleted file mode 100644
index 274844e534..0000000000
--- a/iosApp/iosApp/Utils/FcmTokenContainer.swift
+++ /dev/null
@@ -1,14 +0,0 @@
-//
-// FcmTokenContainer.swift
-// iosApp
-//
-// Created by esimon on 11/28/25.
-// Copyright © 2025 MBTA. All rights reserved.
-//
-
-import SwiftUI
-
-class FcmTokenContainer: ObservableObject {
- static let shared = FcmTokenContainer()
- @Published var token: String?
-}
diff --git a/iosApp/iosApp/Utils/Modifiers/FcmSubscriptionModifier.swift b/iosApp/iosApp/Utils/Modifiers/FcmSubscriptionModifier.swift
index 34d26df111..a586268c8d 100644
--- a/iosApp/iosApp/Utils/Modifiers/FcmSubscriptionModifier.swift
+++ b/iosApp/iosApp/Utils/Modifiers/FcmSubscriptionModifier.swift
@@ -11,7 +11,7 @@ import Shared
import SwiftUI
struct FcmSubscriptionModifier: ViewModifier {
- let fcmToken: String?
+ let fcmInstallationId: String?
let includeAccessibility: Bool
let notificationsEnabled: Bool
@@ -19,15 +19,15 @@ struct FcmSubscriptionModifier: ViewModifier {
@State var favorites: Favorites = LoadedFavorites.last
- func updateSubscriptions(_ fcmToken: String?, _ notificationsEnabled: Bool) {
- if let fcmToken {
+ func updateSubscriptions(_ fcmInstallationId: String?, _ notificationsEnabled: Bool) {
+ if let fcmInstallationId {
Task {
let subscriptions = SubscriptionRequest.companion.fromFavorites(
favorites: favorites.routeStopDirection,
includeAccessibility: includeAccessibility
)
try await subscriptionsRepository.updateSubscriptions(
- fcmToken: fcmToken,
+ fcmInstallationId: fcmInstallationId,
subscriptions: subscriptions,
locale: NSLocalizedString("key/current_locale", comment: ""),
notificationsEnabled: notificationsEnabled,
@@ -39,22 +39,28 @@ struct FcmSubscriptionModifier: ViewModifier {
func body(content: Content) -> some View {
content
.favorites($favorites)
- .onAppear { updateSubscriptions(fcmToken, notificationsEnabled) }
- .onChange(of: fcmToken) { newToken in updateSubscriptions(newToken, notificationsEnabled) }
- .onChange(of: notificationsEnabled) { newNotifications in updateSubscriptions(fcmToken, newNotifications) }
+ .onAppear { updateSubscriptions(fcmInstallationId, notificationsEnabled) }
+ .onChange(of: fcmInstallationId) { newInstallationId in updateSubscriptions(
+ newInstallationId,
+ notificationsEnabled
+ ) }
+ .onChange(of: notificationsEnabled) { newNotifications in updateSubscriptions(
+ fcmInstallationId,
+ newNotifications
+ ) }
.enableInjection()
}
}
public extension View {
- /** Update subscriptions on the backend when the FCM token is set or changed. */
- func handleFcmTokenSubscriptions(
- fcmToken: String?,
+ /** Update subscriptions on the backend when the FCM installation ID is set or changed. */
+ func handleFcmInstallationIdSubscriptions(
+ fcmInstallationId: String?,
includeAccessibility: Bool,
notificationsEnabled: Bool,
) -> some View {
modifier(FcmSubscriptionModifier(
- fcmToken: fcmToken,
+ fcmInstallationId: fcmInstallationId,
includeAccessibility: includeAccessibility,
notificationsEnabled: notificationsEnabled,
))
diff --git a/iosApp/iosApp/ViewModels/IFavoritesViewModelExtension.swift b/iosApp/iosApp/ViewModels/IFavoritesViewModelExtension.swift
index 6dfa8a3bfe..d7b99a8d9c 100644
--- a/iosApp/iosApp/ViewModels/IFavoritesViewModelExtension.swift
+++ b/iosApp/iosApp/ViewModels/IFavoritesViewModelExtension.swift
@@ -14,13 +14,13 @@ extension IFavoritesViewModel {
updatedFavorites: [RouteStopDirection: FavoriteSettings?],
context: EditFavoritesContext,
defaultDirection: Int32,
- fcmToken: String?,
+ fcmInstallationId: String?,
) {
__updateFavorites(
updatedFavorites: updatedFavorites,
context: context,
defaultDirection: .init(int: defaultDirection),
- fcmToken: fcmToken,
+ fcmInstallationId: fcmInstallationId,
locale: NSLocalizedString("key/current_locale", comment: ""),
)
}
diff --git a/iosApp/iosAppTests/Pages/Settings/MorePageTests.swift b/iosApp/iosAppTests/Pages/Settings/MorePageTests.swift
index f1f7d7fc5e..2088e578c3 100644
--- a/iosApp/iosAppTests/Pages/Settings/MorePageTests.swift
+++ b/iosApp/iosAppTests/Pages/Settings/MorePageTests.swift
@@ -60,13 +60,13 @@ final class MorePageTests: XCTestCase {
}
)
- let expectedToken = "fcm_token"
- FcmTokenContainer.shared.token = expectedToken
+ let expectedInstallationId = "fcm_installation_id"
+ FcmInstallationIdContainer.shared.installationId = expectedInstallationId
let mockRepos = MockRepositories()
mockRepos.subscriptions = MockSubscriptionsRepository(
onUpdateSubscriptions: { _, _, _ in },
- onUpdateAccessibility: { token, accessibility, locale in
- XCTAssertEqual(expectedToken, token)
+ onUpdateAccessibility: { installationId, accessibility, locale in
+ XCTAssertEqual(expectedInstallationId, installationId)
XCTAssertTrue(accessibility.boolValue)
XCTAssertEqual("en", locale)
updateExp.fulfill()
diff --git a/iosApp/iosAppTests/Utils/Modifiers/FcmSubscriptionModifierTests.swift b/iosApp/iosAppTests/Utils/Modifiers/FcmSubscriptionModifierTests.swift
index 963eb3c444..aef0c0c56c 100644
--- a/iosApp/iosAppTests/Utils/Modifiers/FcmSubscriptionModifierTests.swift
+++ b/iosApp/iosAppTests/Utils/Modifiers/FcmSubscriptionModifierTests.swift
@@ -40,12 +40,12 @@ final class FcmSubscriptionModifierTests: XCTestCase {
favorites: favorites.routeStopDirection,
includeAccessibility: true
)
- let expectedToken = "token_string"
+ let expectedInstallationId = "installation_id_string"
let mockRepos = MockRepositories()
mockRepos.subscriptions = MockSubscriptionsRepository(
- onUpdateSubscriptions: { token, subs, locale in
- XCTAssertEqual(expectedToken, token)
+ onUpdateSubscriptions: { installationId, subs, locale in
+ XCTAssertEqual(expectedInstallationId, installationId)
XCTAssertEqual(expectedSubscriptions, subs)
XCTAssertEqual("en", locale)
updateExp.fulfill()
@@ -54,8 +54,8 @@ final class FcmSubscriptionModifierTests: XCTestCase {
)
loadKoinMocks(repositories: mockRepos)
- let sut = Text("test").handleFcmTokenSubscriptions(
- fcmToken: expectedToken,
+ let sut = Text("test").handleFcmInstallationIdSubscriptions(
+ fcmInstallationId: expectedInstallationId,
includeAccessibility: true,
notificationsEnabled: true,
)
@@ -65,7 +65,7 @@ final class FcmSubscriptionModifierTests: XCTestCase {
wait(for: [updateExp], timeout: 1)
}
- func testDoesNothingWithNoToken() {
+ func testDoesNothingWithNoInstallationId() {
let updateExp = expectation(description: "subscriptions updated")
updateExp.isInverted = true
@@ -88,15 +88,15 @@ final class FcmSubscriptionModifierTests: XCTestCase {
let mockRepos = MockRepositories()
mockRepos.subscriptions = MockSubscriptionsRepository(
onUpdateSubscriptions: { _, _, _ in
- XCTFail("Should not update when token is missing")
+ XCTFail("Should not update when installation ID is missing")
updateExp.fulfill()
},
onUpdateAccessibility: { _, _, _ in }
)
loadKoinMocks(repositories: mockRepos)
- let sut = Text("test").handleFcmTokenSubscriptions(
- fcmToken: nil,
+ let sut = Text("test").handleFcmInstallationIdSubscriptions(
+ fcmInstallationId: nil,
includeAccessibility: true,
notificationsEnabled: true,
)
diff --git a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/UpdateSubscriptionsRequests.kt b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/UpdateSubscriptionsRequests.kt
index d324f1e963..66e735b0a7 100644
--- a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/UpdateSubscriptionsRequests.kt
+++ b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/UpdateSubscriptionsRequests.kt
@@ -8,7 +8,7 @@ import kotlinx.serialization.Serializable
@Serializable
public data class WriteSubscriptionsRequest
internal constructor(
- @SerialName("fcm_token") val fcmToken: String,
+ @SerialName("fcm_installation_id") val fcmInstallationId: String,
val subscriptions: List,
val locale: String?,
)
@@ -16,7 +16,7 @@ internal constructor(
@Serializable
public data class UpdateAccessibilityRequest
internal constructor(
- @SerialName("fcm_token") val fcmToken: String,
+ @SerialName("fcm_installation_id") val fcmInstallationId: String,
@SerialName("include_accessibility") val includeAccessibility: Boolean,
val locale: String?,
)
@@ -43,10 +43,10 @@ internal constructor(
favorites: Map,
includeAccessibility: Boolean = false,
): List {
- val enabled = favorites.filter { it.value?.notifications?.enabled == true }
+ val enabled = favorites.filter { it.value.notifications.enabled }
return enabled.map { (rsd, settings) ->
val windows =
- settings?.notifications?.windows?.map {
+ settings.notifications.windows.map {
WindowRequest(
startTime = it.startTime,
endTime = it.endTime,
@@ -58,7 +58,7 @@ internal constructor(
stopId = rsd.stop,
directionId = rsd.direction,
includeAccessibility = includeAccessibility,
- windows = windows ?: emptyList(),
+ windows = windows,
)
}
}
diff --git a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/repositories/SubscriptionsRepository.kt b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/repositories/SubscriptionsRepository.kt
index 8c1864d378..a4dcbe6341 100644
--- a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/repositories/SubscriptionsRepository.kt
+++ b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/repositories/SubscriptionsRepository.kt
@@ -16,14 +16,14 @@ import org.koin.core.component.inject
public interface ISubscriptionsRepository {
public suspend fun updateSubscriptions(
- fcmToken: String,
+ fcmInstallationId: String,
subscriptions: List,
locale: String?,
notificationsEnabled: Boolean,
)
public suspend fun updateAccessibility(
- fcmToken: String,
+ fcmInstallationId: String,
includeAccessibility: Boolean,
locale: String?,
)
@@ -34,13 +34,13 @@ internal class SubscriptionsRepository : ISubscriptionsRepository, KoinComponent
private val mobileBackendClient: MobileBackendClient by inject()
override suspend fun updateSubscriptions(
- fcmToken: String,
+ fcmInstallationId: String,
subscriptions: List,
locale: String?,
notificationsEnabled: Boolean,
) {
val subscriptions = if (notificationsEnabled) subscriptions else emptyList()
- val requestBody = WriteSubscriptionsRequest(fcmToken, subscriptions, locale)
+ val requestBody = WriteSubscriptionsRequest(fcmInstallationId, subscriptions, locale)
ApiResult.runCatching {
mobileBackendClient
.post {
@@ -53,11 +53,12 @@ internal class SubscriptionsRepository : ISubscriptionsRepository, KoinComponent
}
override suspend fun updateAccessibility(
- fcmToken: String,
+ fcmInstallationId: String,
includeAccessibility: Boolean,
locale: String?,
) {
- val requestBody = UpdateAccessibilityRequest(fcmToken, includeAccessibility, locale)
+ val requestBody =
+ UpdateAccessibilityRequest(fcmInstallationId, includeAccessibility, locale)
ApiResult.runCatching {
mobileBackendClient
.post {
@@ -77,19 +78,19 @@ public class MockSubscriptionsRepository(
public val onUpdateAccessibility: (String, Boolean, String?) -> Unit = { _, _, _ -> },
) : ISubscriptionsRepository {
override suspend fun updateSubscriptions(
- fcmToken: String,
+ fcmInstallationId: String,
subscriptions: List,
locale: String?,
notificationsEnabled: Boolean,
) {
- onUpdateSubscriptions(fcmToken, subscriptions, locale)
+ onUpdateSubscriptions(fcmInstallationId, subscriptions, locale)
}
override suspend fun updateAccessibility(
- fcmToken: String,
+ fcmInstallationId: String,
includeAccessibility: Boolean,
locale: String?,
) {
- onUpdateAccessibility(fcmToken, includeAccessibility, locale)
+ onUpdateAccessibility(fcmInstallationId, includeAccessibility, locale)
}
}
diff --git a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/usecases/FavoritesUsecases.kt b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/usecases/FavoritesUsecases.kt
index af6e753f90..9b7f574459 100644
--- a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/usecases/FavoritesUsecases.kt
+++ b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/usecases/FavoritesUsecases.kt
@@ -41,7 +41,7 @@ public class FavoritesUsecases(
newValues: Map,
context: EditFavoritesContext,
defaultDirection: Int?,
- fcmToken: String?,
+ fcmInstallationId: String?,
locale: String?,
) {
val storedFavorites = repository.getFavorites()
@@ -65,7 +65,7 @@ public class FavoritesUsecases(
}
}
repository.setFavorites(storedFavorites.copy(routeStopDirection = currentFavorites))
- fcmToken?.let {
+ fcmInstallationId?.let {
val settings = settingsRepository.getSettings()
val subs =
SubscriptionRequest.fromFavorites(
@@ -74,7 +74,7 @@ public class FavoritesUsecases(
)
CoroutineScope(Dispatchers.IO).launch {
subscriptionsRepository.updateSubscriptions(
- fcmToken,
+ fcmInstallationId,
subs,
locale,
notificationsEnabled = settings[Settings.Notifications] ?: false,
diff --git a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/viewModel/FavoritesViewModel.kt b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/viewModel/FavoritesViewModel.kt
index 55cf7a6739..97d86922e8 100644
--- a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/viewModel/FavoritesViewModel.kt
+++ b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/viewModel/FavoritesViewModel.kt
@@ -54,7 +54,7 @@ internal enum class RemovalReason {
public interface IFavoritesViewModel {
public val models: StateFlow
- public fun clearStaleFavorites(fcmToken: String?)
+ public fun clearStaleFavorites(fcmInstallationId: String?)
public fun dismissNotificationsHint()
@@ -77,7 +77,7 @@ public interface IFavoritesViewModel {
updatedFavorites: Map,
context: EditFavoritesContext,
defaultDirection: Int?,
- fcmToken: String?,
+ fcmInstallationId: String?,
locale: String?,
)
}
@@ -98,7 +98,7 @@ public class FavoritesViewModel(
}
public sealed interface Event {
- public data class ClearStaleFavorites(val fcmToken: String?) : Event
+ public data class ClearStaleFavorites(val fcmInstallationId: String?) : Event
public data object DismissNotificationsHint : Event
@@ -110,7 +110,7 @@ public class FavoritesViewModel(
val updatedFavorites: Map,
val context: EditFavoritesContext,
val defaultDirection: Int?,
- val fcmToken: String?,
+ val fcmInstallationId: String?,
val locale: String?,
) : Event
}
@@ -145,7 +145,7 @@ public class FavoritesViewModel(
mutableStateOf(null)
}
- var fcmTokenForClearingStaleFavorites: String? by remember { mutableStateOf(null) }
+ var fcmInstallationIdForClearingStaleFavorites: String? by remember { mutableStateOf(null) }
var hadOldPinnedRoutes: Boolean by remember { mutableStateOf(false) }
var shouldShowFirstTimeToast: Boolean by remember { mutableStateOf(false) }
var shouldShowNotificationsHint: Boolean by remember { mutableStateOf(false) }
@@ -218,7 +218,7 @@ public class FavoritesViewModel(
EventSink(eventHandlingTimeout = 2.seconds, sentryRepository = sentryRepository) { event ->
when (event) {
is Event.ClearStaleFavorites -> {
- fcmTokenForClearingStaleFavorites = event.fcmToken
+ fcmInstallationIdForClearingStaleFavorites = event.fcmInstallationId
}
Event.DismissNotificationsHint -> {
shouldShowNotificationsHint = false
@@ -245,7 +245,7 @@ public class FavoritesViewModel(
event.updatedFavorites,
event.context,
event.defaultDirection,
- event.fcmToken,
+ event.fcmInstallationId,
event.locale,
)
reloadFavorites()
@@ -253,14 +253,14 @@ public class FavoritesViewModel(
}
}
- LaunchedEffect(globalData, favorites, fcmTokenForClearingStaleFavorites) {
- val fcmToken = fcmTokenForClearingStaleFavorites
+ LaunchedEffect(globalData, favorites, fcmInstallationIdForClearingStaleFavorites) {
+ val fcmInstallationId = fcmInstallationIdForClearingStaleFavorites
val resolvedFavorites = favorites
- if (globalData == null || resolvedFavorites == null || fcmToken == null) {
+ if (globalData == null || resolvedFavorites == null || fcmInstallationId == null) {
return@LaunchedEffect
}
- fcmTokenForClearingStaleFavorites = null
+ fcmInstallationIdForClearingStaleFavorites = null
val staleFavorites = getStaleFavorites(resolvedFavorites.keys, globalData)
if (staleFavorites.isNotEmpty()) {
@@ -268,7 +268,7 @@ public class FavoritesViewModel(
staleFavorites.mapValues { null },
EditFavoritesContext.StaleCheck,
defaultDirection = null,
- fcmToken,
+ fcmInstallationId,
locale = null,
)
sentryRepository.captureMessage("Clearing stale favorites") {
@@ -402,8 +402,8 @@ public class FavoritesViewModel(
override val models: StateFlow
get() = internalModels
- override fun clearStaleFavorites(fcmToken: String?): Unit =
- fireEvent(Event.ClearStaleFavorites(fcmToken))
+ override fun clearStaleFavorites(fcmInstallationId: String?): Unit =
+ fireEvent(Event.ClearStaleFavorites(fcmInstallationId))
override fun dismissNotificationsHint(): Unit = fireEvent(Event.DismissNotificationsHint)
@@ -436,11 +436,17 @@ public class FavoritesViewModel(
updatedFavorites: Map,
context: EditFavoritesContext,
defaultDirection: Int?,
- fcmToken: String?,
+ fcmInstallationId: String?,
locale: String?,
) {
fireEvent(
- Event.UpdateFavorites(updatedFavorites, context, defaultDirection, fcmToken, locale)
+ Event.UpdateFavorites(
+ updatedFavorites,
+ context,
+ defaultDirection,
+ fcmInstallationId,
+ locale,
+ )
)
}
}
@@ -464,8 +470,8 @@ constructor(initialState: FavoritesViewModel.State = FavoritesViewModel.State())
override val models: MutableStateFlow = MutableStateFlow(initialState)
- override fun clearStaleFavorites(fcmToken: String?) {
- onClearStaleFavorites(fcmToken)
+ override fun clearStaleFavorites(fcmInstallationId: String?) {
+ onClearStaleFavorites(fcmInstallationId)
}
override fun dismissNotificationsHint() {
@@ -496,15 +502,15 @@ constructor(initialState: FavoritesViewModel.State = FavoritesViewModel.State())
onSetNow(now)
}
- override fun setIsFirstExposureToNewFavorites(isFirstExposure: Boolean) {
- onSetIsFirstExposureToNewFavorites(isFirstExposure)
+ override fun setIsFirstExposureToNewFavorites(isFirst: Boolean) {
+ onSetIsFirstExposureToNewFavorites(isFirst)
}
override fun updateFavorites(
updatedFavorites: Map,
context: EditFavoritesContext,
defaultDirection: Int?,
- fcmToken: String?,
+ fcmInstallationId: String?,
locale: String?,
) {
onUpdateFavorites(updatedFavorites)
diff --git a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/viewModel/MoreViewModel.kt b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/viewModel/MoreViewModel.kt
index 62722fe70f..46616a43a9 100644
--- a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/viewModel/MoreViewModel.kt
+++ b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/viewModel/MoreViewModel.kt
@@ -157,12 +157,16 @@ public class MoreViewModel(
}
public fun updateAccessibility(
- fcmToken: String,
+ fcmInstallationId: String,
includeAccessibility: Boolean,
locale: String,
) {
CoroutineScope(coroutineDispatcher).launch {
- subscriptionsRepository.updateAccessibility(fcmToken, includeAccessibility, locale)
+ subscriptionsRepository.updateAccessibility(
+ fcmInstallationId,
+ includeAccessibility,
+ locale,
+ )
}
}
}
diff --git a/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/usecases/FavoritesUsecasesTests.kt b/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/usecases/FavoritesUsecasesTests.kt
index 51bf5869d9..f121cd9932 100644
--- a/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/usecases/FavoritesUsecasesTests.kt
+++ b/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/usecases/FavoritesUsecasesTests.kt
@@ -88,13 +88,13 @@ class FavoritesUsecasesTests : KoinTest {
MockFavoritesRepository(
buildFavorites { routeStopDirection(Route.Id("route_1"), "stop_1", 0) }
)
- var token: String? = null
+ var installationId: String? = null
var subs: List? = null
var locale: String? = null
val subscriptionsRepository =
MockSubscriptionsRepository(
- onUpdateSubscriptions = { fcmToken, subscriptions, l ->
- token = fcmToken
+ onUpdateSubscriptions = { fcmInstallationId, subscriptions, l ->
+ installationId = fcmInstallationId
subs = subscriptions
locale = l
}
@@ -173,13 +173,13 @@ class FavoritesUsecasesTests : KoinTest {
),
EditFavoritesContext.Favorites,
0,
- "fake_token",
+ "fake_installation_id",
"en",
)
delay(250.milliseconds)
- assertEquals("fake_token", token)
+ assertEquals("fake_installation_id", installationId)
assertEquals(expectedSubs, subs)
assertEquals("en", locale)
}
diff --git a/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/viewModel/FavoritesViewModelTest.kt b/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/viewModel/FavoritesViewModelTest.kt
index 6fb97f98aa..e5510c70a1 100644
--- a/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/viewModel/FavoritesViewModelTest.kt
+++ b/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/viewModel/FavoritesViewModelTest.kt
@@ -1214,7 +1214,7 @@ internal class FavoritesViewModelTest : KoinTest {
mapOf(RouteStopDirection(route1.id, stop1.id, 0) to null),
EditFavoritesContext.Favorites,
0,
- "fcmToken",
+ "fcmInstallationId",
"en",
)
awaitItemSatisfying {