Wire up backend requested sync

Change-Id: I1d4485b589c7e94527a2a02f371cd3f030231622
pull/649/head
Adetunji Dahunsi 2 years ago
parent f3faec8432
commit 05be2855d8

@ -27,6 +27,7 @@ import com.google.samples.apps.nowinandroid.core.database.model.PopulatedNewsRes
import com.google.samples.apps.nowinandroid.core.database.model.TopicEntity import com.google.samples.apps.nowinandroid.core.database.model.TopicEntity
import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel
import com.google.samples.apps.nowinandroid.core.datastore.ChangeListVersions import com.google.samples.apps.nowinandroid.core.datastore.ChangeListVersions
import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import com.google.samples.apps.nowinandroid.core.network.NiaNetworkDataSource import com.google.samples.apps.nowinandroid.core.network.NiaNetworkDataSource
import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource
@ -45,6 +46,7 @@ private const val SYNC_BATCH_SIZE = 40
* Reads are exclusively from local storage to support offline access. * Reads are exclusively from local storage to support offline access.
*/ */
class OfflineFirstNewsRepository @Inject constructor( class OfflineFirstNewsRepository @Inject constructor(
private val niaPreferencesDataSource: NiaPreferencesDataSource,
private val newsResourceDao: NewsResourceDao, private val newsResourceDao: NewsResourceDao,
private val topicDao: TopicDao, private val topicDao: TopicDao,
private val network: NiaNetworkDataSource, private val network: NiaNetworkDataSource,
@ -72,15 +74,25 @@ class OfflineFirstNewsRepository @Inject constructor(
}, },
modelDeleter = newsResourceDao::deleteNewsResources, modelDeleter = newsResourceDao::deleteNewsResources,
modelUpdater = { changedIds -> modelUpdater = { changedIds ->
val userData = niaPreferencesDataSource.userData.first()
val hasOnBoarded = userData.shouldHideOnboarding
val followedTopicIds = userData.followedTopics
// TODO: Make this more efficient, there is no need to retrieve populated // TODO: Make this more efficient, there is no need to retrieve populated
// news resources when all that's needed are the ids // news resources when all that's needed are the ids
val existingNewsResourceIds = newsResourceDao.getNewsResources( val existingFollowedChangedNewsResourceIds = when {
useFilterNewsIds = true, hasOnBoarded -> newsResourceDao.getNewsResources(
filterNewsIds = changedIds.toSet(), useFilterTopicIds = true,
) filterTopicIds = followedTopicIds,
.first() useFilterNewsIds = true,
.map { it.entity.id } filterNewsIds = changedIds.toSet(),
.toSet() )
.first()
.map { it.entity.id }
.toSet()
// No need to retrieve anything if notifications won't be sent
else -> emptySet()
}
changedIds.chunked(SYNC_BATCH_SIZE).forEach { chunkedIds -> changedIds.chunked(SYNC_BATCH_SIZE).forEach { chunkedIds ->
val networkNewsResources = network.getNewsResources(ids = chunkedIds) val networkNewsResources = network.getNewsResources(ids = chunkedIds)
@ -106,19 +118,18 @@ class OfflineFirstNewsRepository @Inject constructor(
) )
} }
val addedNewsResources = newsResourceDao.getNewsResources( if (hasOnBoarded) {
useFilterNewsIds = true, val addedNewsResources = newsResourceDao.getNewsResources(
filterNewsIds = changedIds.toSet(), useFilterTopicIds = true,
) filterTopicIds = followedTopicIds,
.first() useFilterNewsIds = true,
.filter { !existingNewsResourceIds.contains(it.entity.id) } filterNewsIds = changedIds.toSet() - existingFollowedChangedNewsResourceIds,
.map(PopulatedNewsResource::asExternalModel) )
.first()
.map(PopulatedNewsResource::asExternalModel)
// TODO: Define business logic for notifications on first time sync. if (addedNewsResources.isNotEmpty()) notifier.onNewsAdded(addedNewsResources)
// we probably do not want to send notifications on first install. }
// We can easily check if the change list version is 0 and not send notifications
// if it is.
if (addedNewsResources.isNotEmpty()) notifier.onNewsAdded(addedNewsResources)
}, },
) )
} }

@ -34,6 +34,7 @@ import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel
import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource
import com.google.samples.apps.nowinandroid.core.datastore.test.testUserPreferencesDataStore import com.google.samples.apps.nowinandroid.core.datastore.test.testUserPreferencesDataStore
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.network.model.NetworkChangeList import com.google.samples.apps.nowinandroid.core.network.model.NetworkChangeList
import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource
import com.google.samples.apps.nowinandroid.core.testing.notifications.TestNotifier import com.google.samples.apps.nowinandroid.core.testing.notifications.TestNotifier
@ -46,6 +47,7 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue
class OfflineFirstNewsRepositoryTest { class OfflineFirstNewsRepositoryTest {
@ -53,6 +55,8 @@ class OfflineFirstNewsRepositoryTest {
private lateinit var subject: OfflineFirstNewsRepository private lateinit var subject: OfflineFirstNewsRepository
private lateinit var niaPreferencesDataSource: NiaPreferencesDataSource
private lateinit var newsResourceDao: TestNewsResourceDao private lateinit var newsResourceDao: TestNewsResourceDao
private lateinit var topicDao: TestTopicDao private lateinit var topicDao: TestTopicDao
@ -68,17 +72,19 @@ class OfflineFirstNewsRepositoryTest {
@Before @Before
fun setup() { fun setup() {
niaPreferencesDataSource = NiaPreferencesDataSource(
tmpFolder.testUserPreferencesDataStore(testScope),
)
newsResourceDao = TestNewsResourceDao() newsResourceDao = TestNewsResourceDao()
topicDao = TestTopicDao() topicDao = TestTopicDao()
network = TestNiaNetworkDataSource() network = TestNiaNetworkDataSource()
notifier = TestNotifier() notifier = TestNotifier()
synchronizer = TestSynchronizer( synchronizer = TestSynchronizer(
NiaPreferencesDataSource( niaPreferencesDataSource,
tmpFolder.testUserPreferencesDataStore(testScope),
),
) )
subject = OfflineFirstNewsRepository( subject = OfflineFirstNewsRepository(
niaPreferencesDataSource = niaPreferencesDataSource,
newsResourceDao = newsResourceDao, newsResourceDao = newsResourceDao,
topicDao = topicDao, topicDao = topicDao,
network = network, network = network,
@ -130,6 +136,8 @@ class OfflineFirstNewsRepositoryTest {
@Test @Test
fun offlineFirstNewsRepository_sync_pulls_from_network() = fun offlineFirstNewsRepository_sync_pulls_from_network() =
testScope.runTest { testScope.runTest {
// User has not onboarded
niaPreferencesDataSource.setShouldHideOnboarding(false)
subject.syncWith(synchronizer) subject.syncWith(synchronizer)
val newsResourcesFromNetwork = network.getNewsResources() val newsResourcesFromNetwork = network.getNewsResources()
@ -151,16 +159,16 @@ class OfflineFirstNewsRepositoryTest {
actual = synchronizer.getChangeListVersions().newsResourceVersion, actual = synchronizer.getChangeListVersions().newsResourceVersion,
) )
// Notifier should have been called with new news resources // Notifier should not have been called
assertEquals( assertTrue(notifier.addedNewsResources.isEmpty())
expected = newsResourcesFromDb.map(NewsResource::id).sorted(),
actual = notifier.addedNewsResources.first().map(NewsResource::id).sorted(),
)
} }
@Test @Test
fun offlineFirstNewsRepository_sync_deletes_items_marked_deleted_on_network() = fun offlineFirstNewsRepository_sync_deletes_items_marked_deleted_on_network() =
testScope.runTest { testScope.runTest {
// User has not onboarded
niaPreferencesDataSource.setShouldHideOnboarding(false)
val newsResourcesFromNetwork = network.getNewsResources() val newsResourcesFromNetwork = network.getNewsResources()
.map(NetworkNewsResource::asEntity) .map(NetworkNewsResource::asEntity)
.map(NewsResourceEntity::asExternalModel) .map(NewsResourceEntity::asExternalModel)
@ -198,17 +206,16 @@ class OfflineFirstNewsRepositoryTest {
actual = synchronizer.getChangeListVersions().newsResourceVersion, actual = synchronizer.getChangeListVersions().newsResourceVersion,
) )
// Notifier should have been called with news resources from network that are not // Notifier should not have been called
// deleted assertTrue(notifier.addedNewsResources.isEmpty())
assertEquals(
expected = (newsResourcesFromNetwork.map(NewsResource::id) - deletedItems).sorted(),
actual = notifier.addedNewsResources.first().map(NewsResource::id).sorted(),
)
} }
@Test @Test
fun offlineFirstNewsRepository_incremental_sync_pulls_from_network() = fun offlineFirstNewsRepository_incremental_sync_pulls_from_network() =
testScope.runTest { testScope.runTest {
// User has not onboarded
niaPreferencesDataSource.setShouldHideOnboarding(false)
// Set news version to 7 // Set news version to 7
synchronizer.updateChangeListVersions { synchronizer.updateChangeListVersions {
copy(newsResourceVersion = 7) copy(newsResourceVersion = 7)
@ -244,11 +251,8 @@ class OfflineFirstNewsRepositoryTest {
actual = synchronizer.getChangeListVersions().newsResourceVersion, actual = synchronizer.getChangeListVersions().newsResourceVersion,
) )
// Notifier should have been called with only added news resources from network // Notifier should not have been called
assertEquals( assertTrue(notifier.addedNewsResources.isEmpty())
expected = newsResourcesFromNetwork.map(NewsResource::id).sorted(),
actual = notifier.addedNewsResources.first().map(NewsResource::id).sorted(),
)
} }
@Test @Test
@ -283,4 +287,68 @@ class OfflineFirstNewsRepositoryTest {
.sortedBy(NewsResourceTopicCrossRef::toString), .sortedBy(NewsResourceTopicCrossRef::toString),
) )
} }
@Test
fun offlineFirstNewsRepository_sends_notifications_for_newly_synced_news_that_is_followed() =
testScope.runTest {
// User has onboarded
niaPreferencesDataSource.setShouldHideOnboarding(true)
val networkNewsResources = network.getNewsResources()
// Follow roughly half the topics
val followedTopicIds = networkNewsResources
.flatMap(NetworkNewsResource::topicEntityShells)
.mapNotNull { topic ->
when (topic.id.chars().sum() % 2) {
0 -> topic.id
else -> null
}
}
.toSet()
// Set followed topics
niaPreferencesDataSource.setFollowedTopicIds(followedTopicIds)
subject.syncWith(synchronizer)
// Notifier should have been called with only news resources that have topics
// that the user follows
assertEquals(
expected = networkNewsResources
.filter { (it.topics intersect followedTopicIds).isNotEmpty() }
.map(NetworkNewsResource::id)
.sorted(),
actual = notifier.addedNewsResources.first().map(NewsResource::id).sorted(),
)
}
@Test
fun offlineFirstNewsRepository_does_not_send_notifications_for_existing_news_resources() =
testScope.runTest {
// User has onboarded
niaPreferencesDataSource.setShouldHideOnboarding(true)
val networkNewsResources = network.getNewsResources()
.map(NetworkNewsResource::asEntity)
val newsResources = networkNewsResources
.map(NewsResourceEntity::asExternalModel)
// Prepopulate dao with news resources
newsResourceDao.upsertNewsResources(networkNewsResources)
val followedTopicIds = newsResources
.flatMap(NewsResource::topics)
.map(Topic::id)
.toSet()
// Follow all topics
niaPreferencesDataSource.setFollowedTopicIds(followedTopicIds)
subject.syncWith(synchronizer)
// Notifier should not have been called bc all news resources existed previously
assertTrue(notifier.addedNewsResources.isEmpty())
}
} }

@ -47,7 +47,11 @@ class TestNewsResourceDao : NewsResourceDao {
filterNewsIds: Set<String>, filterNewsIds: Set<String>,
): Flow<List<PopulatedNewsResource>> = ): Flow<List<PopulatedNewsResource>> =
entitiesStateFlow entitiesStateFlow
.map { it.map(NewsResourceEntity::asPopulatedNewsResource) } .map { newsResourceEntities ->
newsResourceEntities.map { entity ->
entity.asPopulatedNewsResource(topicCrossReferences)
}
}
.map { resources -> .map { resources ->
var result = resources var result = resources
if (useFilterTopicIds) { if (useFilterTopicIds) {
@ -78,10 +82,6 @@ class TestNewsResourceDao : NewsResourceDao {
return entities.map { it.id.toLong() } return entities.map { it.id.toLong() }
} }
override suspend fun updateNewsResources(entities: List<NewsResourceEntity>) {
throw NotImplementedError("Unused in tests")
}
override suspend fun upsertNewsResources(newsResourceEntities: List<NewsResourceEntity>) { override suspend fun upsertNewsResources(newsResourceEntities: List<NewsResourceEntity>) {
entitiesStateFlow.update { oldValues -> entitiesStateFlow.update { oldValues ->
// New values come first so they overwrite old values // New values come first so they overwrite old values
@ -109,16 +109,20 @@ class TestNewsResourceDao : NewsResourceDao {
} }
} }
private fun NewsResourceEntity.asPopulatedNewsResource() = PopulatedNewsResource( private fun NewsResourceEntity.asPopulatedNewsResource(
topicCrossReferences: List<NewsResourceTopicCrossRef>,
) = PopulatedNewsResource(
entity = this, entity = this,
topics = listOf( topics = topicCrossReferences
TopicEntity( .filter { it.newsResourceId == id }
id = filteredInterestsIds.random(), .map { newsResourceTopicCrossRef ->
name = "name", TopicEntity(
shortDescription = "short description", id = newsResourceTopicCrossRef.topicId,
longDescription = "long description", name = "name",
url = "URL", shortDescription = "short description",
imageUrl = "image URL", longDescription = "long description",
), url = "URL",
), imageUrl = "image URL",
)
},
) )

@ -21,7 +21,6 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction import androidx.room.Transaction
import androidx.room.Update
import androidx.room.Upsert import androidx.room.Upsert
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceEntity import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceEntity
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceTopicCrossRef import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceTopicCrossRef
@ -72,12 +71,6 @@ interface NewsResourceDao {
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertOrIgnoreNewsResources(entities: List<NewsResourceEntity>): List<Long> suspend fun insertOrIgnoreNewsResources(entities: List<NewsResourceEntity>): List<Long>
/**
* Updates [entities] in the db that match the primary key, and no-ops if they don't
*/
@Update
suspend fun updateNewsResources(entities: List<NewsResourceEntity>)
/** /**
* Inserts or updates [newsResourceEntities] in the db under the specified primary keys * Inserts or updates [newsResourceEntities] in the db under the specified primary keys
*/ */

@ -17,6 +17,8 @@
package com.google.samples.apps.nowinandroid.sync.di package com.google.samples.apps.nowinandroid.sync.di
import com.google.samples.apps.nowinandroid.core.data.util.SyncManager import com.google.samples.apps.nowinandroid.core.data.util.SyncManager
import com.google.samples.apps.nowinandroid.sync.status.StubSyncSubscriber
import com.google.samples.apps.nowinandroid.sync.status.SyncSubscriber
import com.google.samples.apps.nowinandroid.sync.status.WorkManagerSyncManager import com.google.samples.apps.nowinandroid.sync.status.WorkManagerSyncManager
import dagger.Binds import dagger.Binds
import dagger.Module import dagger.Module
@ -30,4 +32,9 @@ interface SyncModule {
fun bindsSyncStatusMonitor( fun bindsSyncStatusMonitor(
syncStatusMonitor: WorkManagerSyncManager, syncStatusMonitor: WorkManagerSyncManager,
): SyncManager ): SyncManager
@Binds
fun bindsSyncSubscriber(
syncSubscriber: StubSyncSubscriber,
): SyncSubscriber
} }

@ -27,6 +27,7 @@ import androidx.work.ForegroundInfo
import androidx.work.NetworkType import androidx.work.NetworkType
import com.google.samples.apps.nowinandroid.sync.R import com.google.samples.apps.nowinandroid.sync.R
const val SYNC_TOPIC = "sync"
private const val SyncNotificationId = 0 private const val SyncNotificationId = 0
private const val SyncNotificationChannelID = "SyncNotificationChannel" private const val SyncNotificationChannelID = "SyncNotificationChannel"

@ -19,11 +19,10 @@ package com.google.samples.apps.nowinandroid.sync.services
import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage import com.google.firebase.messaging.RemoteMessage
import com.google.samples.apps.nowinandroid.core.data.util.SyncManager import com.google.samples.apps.nowinandroid.core.data.util.SyncManager
import com.google.samples.apps.nowinandroid.sync.initializers.SYNC_TOPIC
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
private const val SYNC_TOPIC = "sync"
@AndroidEntryPoint @AndroidEntryPoint
class SyncNotificationsService : FirebaseMessagingService() { class SyncNotificationsService : FirebaseMessagingService() {

@ -0,0 +1,31 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.sync.status
import android.util.Log
import javax.inject.Inject
private const val TAG = "StubSyncSubscriber"
/**
* Stub implementation of [SyncSubscriber]
*/
class StubSyncSubscriber @Inject constructor() : SyncSubscriber {
override suspend fun subscribe() {
Log.d(TAG, "Subscribing to sync")
}
}

@ -0,0 +1,24 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.sync.status
/**
* Subscribes to backend requested synchronization
*/
interface SyncSubscriber {
suspend fun subscribe()
}

@ -34,6 +34,7 @@ import com.google.samples.apps.nowinandroid.core.network.Dispatcher
import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO
import com.google.samples.apps.nowinandroid.sync.initializers.SyncConstraints import com.google.samples.apps.nowinandroid.sync.initializers.SyncConstraints
import com.google.samples.apps.nowinandroid.sync.initializers.syncForegroundInfo import com.google.samples.apps.nowinandroid.sync.initializers.syncForegroundInfo
import com.google.samples.apps.nowinandroid.sync.status.SyncSubscriber
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
@ -54,6 +55,7 @@ class SyncWorker @AssistedInject constructor(
private val newsRepository: NewsRepository, private val newsRepository: NewsRepository,
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher, @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
private val analyticsHelper: AnalyticsHelper, private val analyticsHelper: AnalyticsHelper,
private val syncSubscriber: SyncSubscriber,
) : CoroutineWorker(appContext, workerParams), Synchronizer { ) : CoroutineWorker(appContext, workerParams), Synchronizer {
override suspend fun getForegroundInfo(): ForegroundInfo = override suspend fun getForegroundInfo(): ForegroundInfo =
@ -63,6 +65,8 @@ class SyncWorker @AssistedInject constructor(
traceAsync("Sync", 0) { traceAsync("Sync", 0) {
analyticsHelper.logSyncStarted() analyticsHelper.logSyncStarted()
syncSubscriber.subscribe()
// First sync the repositories in parallel // First sync the repositories in parallel
val syncedSuccessfully = awaitAll( val syncedSuccessfully = awaitAll(
async { topicRepository.sync() }, async { topicRepository.sync() },

@ -0,0 +1,51 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.sync.di
import com.google.firebase.ktx.Firebase
import com.google.firebase.messaging.FirebaseMessaging
import com.google.firebase.messaging.ktx.messaging
import com.google.samples.apps.nowinandroid.core.data.util.SyncManager
import com.google.samples.apps.nowinandroid.sync.status.FirebaseSyncSubscriber
import com.google.samples.apps.nowinandroid.sync.status.SyncSubscriber
import com.google.samples.apps.nowinandroid.sync.status.WorkManagerSyncManager
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
interface SyncModule {
@Binds
fun bindsSyncStatusMonitor(
syncStatusMonitor: WorkManagerSyncManager,
): SyncManager
@Binds
fun bindsSyncSubscriber(
syncSubscriber: FirebaseSyncSubscriber,
): SyncSubscriber
companion object {
@Provides
@Singleton
fun provideFirebaseMessaging(): FirebaseMessaging = Firebase.messaging
}
}

@ -0,0 +1,35 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.sync.status
import com.google.firebase.messaging.FirebaseMessaging
import com.google.samples.apps.nowinandroid.sync.initializers.SYNC_TOPIC
import kotlinx.coroutines.tasks.await
import javax.inject.Inject
/**
* Implementation of [SyncSubscriber] that subscribes to the FCM [SYNC_TOPIC]
*/
class FirebaseSyncSubscriber @Inject constructor(
private val firebaseMessaging: FirebaseMessaging,
) : SyncSubscriber {
override suspend fun subscribe() {
firebaseMessaging
.subscribeToTopic(SYNC_TOPIC)
.await()
}
}
Loading…
Cancel
Save