@@ -32,26 +32,26 @@ class DefaultPushNotificationService implements IPushNotificationService {
3232 /// {@macro default_push_notification_service}
3333 DefaultPushNotificationService ({
3434 required DataRepository <PushNotificationDevice >
35- pushNotificationDeviceRepository,
35+ pushNotificationDeviceRepository,
3636 required DataRepository <UserContentPreferences >
37- userContentPreferencesRepository,
37+ userContentPreferencesRepository,
3838 required DataRepository <RemoteConfig > remoteConfigRepository,
3939 required DataRepository <InAppNotification > inAppNotificationRepository,
4040 required IPushNotificationClient ? firebaseClient,
4141 required IPushNotificationClient ? oneSignalClient,
4242 required Logger log,
43- }) : _pushNotificationDeviceRepository = pushNotificationDeviceRepository,
44- _userContentPreferencesRepository = userContentPreferencesRepository,
45- _remoteConfigRepository = remoteConfigRepository,
46- _inAppNotificationRepository = inAppNotificationRepository,
47- _firebaseClient = firebaseClient,
48- _oneSignalClient = oneSignalClient,
49- _log = log;
43+ }) : _pushNotificationDeviceRepository = pushNotificationDeviceRepository,
44+ _userContentPreferencesRepository = userContentPreferencesRepository,
45+ _remoteConfigRepository = remoteConfigRepository,
46+ _inAppNotificationRepository = inAppNotificationRepository,
47+ _firebaseClient = firebaseClient,
48+ _oneSignalClient = oneSignalClient,
49+ _log = log;
5050
5151 final DataRepository <PushNotificationDevice >
52- _pushNotificationDeviceRepository;
52+ _pushNotificationDeviceRepository;
5353 final DataRepository <UserContentPreferences >
54- _userContentPreferencesRepository;
54+ _userContentPreferencesRepository;
5555 final DataRepository <RemoteConfig > _remoteConfigRepository;
5656 final DataRepository <InAppNotification > _inAppNotificationRepository;
5757 final IPushNotificationClient ? _firebaseClient;
@@ -113,8 +113,8 @@ class DefaultPushNotificationService implements IPushNotificationService {
113113 // Check if breaking news notifications are enabled.
114114 final isBreakingNewsEnabled =
115115 pushConfig.deliveryConfigs[PushNotificationSubscriptionDeliveryType
116- .breakingOnly] ??
117- false ;
116+ .breakingOnly] ??
117+ false ;
118118
119119 if (! isBreakingNewsEnabled) {
120120 _log.info ('Breaking news notifications are disabled. Aborting.' );
@@ -123,16 +123,16 @@ class DefaultPushNotificationService implements IPushNotificationService {
123123
124124 // 2. Find all user preferences that contain a saved headline filter
125125 // subscribed to breaking news. This query targets the embedded 'savedHeadlineFilters' array.
126- final subscribedUserPreferences = await _userContentPreferencesRepository
127- .readAll (
128- filter: {
129- 'savedHeadlineFilters.deliveryTypes' : {
130- r'$in' : [
131- PushNotificationSubscriptionDeliveryType .breakingOnly.name,
132- ],
133- },
134- },
135- );
126+ final subscribedUserPreferences =
127+ await _userContentPreferencesRepository .readAll (
128+ filter: {
129+ 'savedHeadlineFilters.deliveryTypes' : {
130+ r'$in' : [
131+ PushNotificationSubscriptionDeliveryType .breakingOnly.name,
132+ ],
133+ },
134+ },
135+ );
136136
137137 if (subscribedUserPreferences.items.isEmpty) {
138138 _log.info ('No users subscribed to breaking news. Aborting.' );
@@ -142,22 +142,21 @@ class DefaultPushNotificationService implements IPushNotificationService {
142142 // 3. Collect all unique user IDs from the preference documents.
143143 // Using a Set automatically handles deduplication.
144144 // The ID of the UserContentPreferences document is the user's ID.
145- final userIds = subscribedUserPreferences.items
146- .map ((preference) => preference.id)
147- .toSet ();
145+ final userIds =
146+ subscribedUserPreferences.items.map ((preference) => preference.id).toSet ();
148147
149148 _log.info (
150149 'Found ${subscribedUserPreferences .items .length } users with '
151150 'subscriptions to breaking news.' ,
152151 );
153152
154153 // 4. Fetch all devices for all subscribed users in a single bulk query.
155- final allDevicesResponse = await _pushNotificationDeviceRepository
156- .readAll (
157- filter: {
158- 'userId' : {r'$in' : userIds.toList ()},
159- },
160- );
154+ final allDevicesResponse =
155+ await _pushNotificationDeviceRepository .readAll (
156+ filter: {
157+ 'userId' : {r'$in' : userIds.toList ()},
158+ },
159+ );
161160
162161 final allDevices = allDevicesResponse.items;
163162 if (allDevices.isEmpty) {
@@ -183,61 +182,59 @@ class DefaultPushNotificationService implements IPushNotificationService {
183182 return ;
184183 }
185184
186- // 6. Extract the specific tokens for the primary provider.
187- final tokens = targetedDevices
188- .map ((d) => d.providerTokens[primaryProvider]! )
189- .toList ();
190-
191185 _log.info (
192- 'Found ${tokens .length } devices to target via $primaryProvider .' ,
186+ 'Found ${targetedDevices .length } devices to target via $primaryProvider .' ,
193187 );
194188
195- // Before sending the push, persist the InAppNotification record for
196- // each targeted user. This ensures the notification is available in
197- // their inbox immediately.
198- try {
199- final notificationCreationFutures = userIds.map ((userId) {
200- final notification = InAppNotification (
201- id: ObjectId ().oid,
202- userId: userId,
203- payload: PushNotificationPayload (
204- title: headline.title,
205- body: headline.excerpt,
206- imageUrl: headline.imageUrl,
207- data: {
208- 'headlineId' : headline.id,
209- 'contentType' : 'headline' ,
210- 'notificationType' :
211- PushNotificationSubscriptionDeliveryType .breakingOnly.name,
212- },
213- ),
214- createdAt: DateTime .now (),
215- );
216- return _inAppNotificationRepository.create (item: notification);
217- });
218- await Future .wait (notificationCreationFutures);
219- _log.info ('Persisted ${userIds .length } in-app notifications.' );
220- } catch (e, s) {
221- _log.severe ('Failed to persist in-app notifications.' , e, s);
222- }
189+ // 7. Iterate through each subscribed user to create and send a
190+ // personalized notification.
191+ final sendFutures = < Future <void >> [];
192+ for (final userId in userIds) {
193+ // Create the InAppNotification record first to get its unique ID.
194+ final notificationId = ObjectId ();
195+ final notification = InAppNotification (
196+ id: notificationId.oid,
197+ userId: userId,
198+ payload: PushNotificationPayload (
199+ title: headline.title,
200+ body: headline.excerpt,
201+ imageUrl: headline.imageUrl,
202+ data: {
203+ 'notificationType' :
204+ PushNotificationSubscriptionDeliveryType .breakingOnly.name,
205+ 'contentType' : 'headline' ,
206+ 'headlineId' : headline.id,
207+ 'notificationId' : notificationId.oid,
208+ },
209+ ),
210+ createdAt: DateTime .now (),
211+ );
223212
224- // 7. Construct the notification payload.
225- final payload = PushNotificationPayload (
226- title: headline.title,
227- body: headline.excerpt,
228- imageUrl: headline.imageUrl,
229- data: {
230- 'headlineId' : headline.id,
231- 'contentType' : 'headline' ,
232- 'notificationType' :
233- PushNotificationSubscriptionDeliveryType .breakingOnly.name,
234- },
235- );
213+ try {
214+ await _inAppNotificationRepository.create (item: notification);
236215
237- await client.sendBulkNotifications (
238- deviceTokens: tokens,
239- payload: payload,
240- );
216+ // Find all device tokens for the current user.
217+ final userDeviceTokens = targetedDevices
218+ .where ((d) => d.userId == userId)
219+ .map ((d) => d.providerTokens[primaryProvider]! )
220+ .toList ();
221+
222+ if (userDeviceTokens.isNotEmpty) {
223+ // Add the send operation to the list of futures.
224+ sendFutures.add (
225+ client.sendBulkNotifications (
226+ deviceTokens: userDeviceTokens,
227+ payload: notification.payload,
228+ ),
229+ );
230+ }
231+ } catch (e, s) {
232+ _log.severe ('Failed to process notification for user $userId .' , e, s);
233+ }
234+ }
235+
236+ // Await all the send operations to complete in parallel.
237+ await Future .wait (sendFutures);
241238
242239 _log.info (
243240 'Successfully dispatched breaking news notification for headline: '
0 commit comments