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

Give unique name for periodic and oneTime sync worker #2053

Merged
merged 3 commits into from
Jul 4, 2023
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,8 @@ import com.google.android.fhir.NetworkConfiguration
import com.google.android.fhir.ServerConfiguration
import com.google.android.fhir.datacapture.DataCaptureConfig
import com.google.android.fhir.datacapture.XFhirQueryResolver
import com.google.android.fhir.demo.data.FhirSyncWorker
import com.google.android.fhir.search.search
import com.google.android.fhir.sync.Sync
import com.google.android.fhir.sync.remote.HttpLogger
import org.hl7.fhir.r4.model.Patient
import timber.log.Timber

class FhirApplication : Application(), DataCaptureConfig.Provider {
Expand All @@ -46,7 +43,6 @@ class FhirApplication : Application(), DataCaptureConfig.Provider {
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
Patient.IDENTIFIER
FhirEngineProvider.init(
FhirEngineConfiguration(
enableEncryptionIfSupported = true,
Expand All @@ -63,7 +59,6 @@ class FhirApplication : Application(), DataCaptureConfig.Provider {
)
)
)
Sync.oneTimeSync<FhirSyncWorker>(this)

dataCaptureConfig =
DataCaptureConfig().apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import androidx.work.Constraints
import com.google.android.fhir.demo.data.FhirSyncWorker
import com.google.android.fhir.demo.data.DemoFhirSyncWorker
import com.google.android.fhir.sync.PeriodicSyncConfiguration
import com.google.android.fhir.sync.RepeatInterval
import com.google.android.fhir.sync.Sync
Expand All @@ -50,12 +50,13 @@ class MainActivityViewModel(application: Application) : AndroidViewModel(applica

init {
viewModelScope.launch {
Sync.periodicSync<FhirSyncWorker>(
Sync.periodicSync<DemoFhirSyncWorker>(
application.applicationContext,
PeriodicSyncConfiguration(
syncConstraints = Constraints.Builder().build(),
repeat = RepeatInterval(interval = 15, timeUnit = TimeUnit.MINUTES)
)
periodicSyncConfiguration =
PeriodicSyncConfiguration(
syncConstraints = Constraints.Builder().build(),
repeat = RepeatInterval(interval = 15, timeUnit = TimeUnit.MINUTES)
)
)
.shareIn(this, SharingStarted.Eagerly, 10)
.collect { _pollState.emit(it) }
Expand All @@ -64,7 +65,7 @@ class MainActivityViewModel(application: Application) : AndroidViewModel(applica

fun triggerOneTimeSync() {
viewModelScope.launch {
Sync.oneTimeSync<FhirSyncWorker>(getApplication())
Sync.oneTimeSync<DemoFhirSyncWorker>(getApplication())
.shareIn(this, SharingStarted.Eagerly, 10)
.collect { _pollState.emit(it) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import com.google.android.fhir.sync.AcceptLocalConflictResolver
import com.google.android.fhir.sync.DownloadWorkManager
import com.google.android.fhir.sync.FhirSyncWorker

class FhirSyncWorker(appContext: Context, workerParams: WorkerParameters) :
class DemoFhirSyncWorker(appContext: Context, workerParams: WorkerParameters) :
FhirSyncWorker(appContext, workerParams) {

override fun getDownloadWorkManager(): DownloadWorkManager {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright 2022 Google LLC
*
* 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
*
* http://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.android.fhir.sync

import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.work.Constraints
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import androidx.work.testing.WorkManagerTestInitHelper
import com.google.android.fhir.FhirEngine
import com.google.android.fhir.testing.TestDataSourceImpl
import com.google.android.fhir.testing.TestDownloadManagerImpl
import com.google.android.fhir.testing.TestFhirEngineImpl
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.transformWhile
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class SyncInstrumentedTest {

private val context: Context = InstrumentationRegistry.getInstrumentation().targetContext

class TestSyncWorker(appContext: Context, workerParams: WorkerParameters) :
FhirSyncWorker(appContext, workerParams) {

override fun getFhirEngine(): FhirEngine = TestFhirEngineImpl
override fun getDataSource(): DataSource = TestDataSourceImpl
override fun getDownloadWorkManager(): DownloadWorkManager = TestDownloadManagerImpl()
override fun getConflictResolver() = AcceptRemoteConflictResolver
}

@Test
fun oneTime_worker_runs() {
WorkManagerTestInitHelper.initializeTestWorkManager(context)
val workManager = WorkManager.getInstance(context)
runBlocking {
Sync.oneTimeSync<TestSyncWorker>(context = context)
.transformWhile {
emit(it is SyncJobStatus.Finished)
it !is SyncJobStatus.Finished
}
.shareIn(this, SharingStarted.Eagerly, 5)
}

assertThat(workManager.getWorkInfosByTag(TestSyncWorker::class.java.name).get().first().state)
.isEqualTo(WorkInfo.State.SUCCEEDED)
}

@Test
fun periodic_worker_still_queued_to_run_after_oneTime_worker_started() {
WorkManagerTestInitHelper.initializeTestWorkManager(context)
val workManager = WorkManager.getInstance(context)
// run and wait for periodic worker to finish
runBlocking {
Sync.periodicSync<TestSyncWorker>(
context = context,
periodicSyncConfiguration =
PeriodicSyncConfiguration(
syncConstraints = Constraints.Builder().build(),
repeat = RepeatInterval(interval = 15, timeUnit = TimeUnit.MINUTES)
),
)
.transformWhile {
emit(it)
it !is SyncJobStatus.Finished
}
.shareIn(this, SharingStarted.Eagerly, 5)
}

// Verify the periodic worker completed the run, and is queued to run again
val periodicWorkerId =
workManager.getWorkInfosByTag(TestSyncWorker::class.java.name).get().first().id
assertThat(workManager.getWorkInfoById(periodicWorkerId).get().state)
.isEqualTo(WorkInfo.State.ENQUEUED)

// Start and complete a oneTime job, and verify it does not remove the periodic worker
runBlocking {
Sync.oneTimeSync<TestSyncWorker>(context = context)
.transformWhile {
emit(it)
it !is SyncJobStatus.Finished
}
.shareIn(this, SharingStarted.Eagerly, 5)
}
assertThat(workManager.getWorkInfosByTag(TestSyncWorker::class.java.name).get().size)
.isEqualTo(2)

// Move forward to the next epoch to trigger the periodic sync
val testDriver = WorkManagerTestInitHelper.getTestDriver(context)
testDriver?.setPeriodDelayMet(periodicWorkerId)

assertThat(workManager.getWorkInfoById(periodicWorkerId).get().state)
.isEqualTo(WorkInfo.State.RUNNING)
}
}
24 changes: 13 additions & 11 deletions engine/src/main/java/com/google/android/fhir/sync/Sync.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ object Sync {
context: Context,
retryConfiguration: RetryConfiguration? = defaultRetryConfiguration
): Flow<SyncJobStatus> {
val flow = getWorkerInfo<W>(context)
val uniqueWorkName = "${W::class.java.name}-oneTimeSync"
val flow = getWorkerInfo(context, uniqueWorkName)
WorkManager.getInstance(context)
.enqueueUniqueWork(
W::class.java.name,
uniqueWorkName,
ExistingWorkPolicy.KEEP,
createOneTimeWorkRequest(retryConfiguration, W::class.java)
)
Expand All @@ -80,20 +81,21 @@ object Sync {
context: Context,
periodicSyncConfiguration: PeriodicSyncConfiguration
): Flow<SyncJobStatus> {
val flow = getWorkerInfo<W>(context)
val uniqueWorkName = "${W::class.java.name}-periodicSync"
val flow = getWorkerInfo(context, uniqueWorkName)
WorkManager.getInstance(context)
.enqueueUniquePeriodicWork(
W::class.java.name,
uniqueWorkName,
ExistingPeriodicWorkPolicy.KEEP,
createPeriodicWorkRequest(periodicSyncConfiguration, W::class.java)
)
return flow
}

/** Gets the worker info for the [FhirSyncWorker] */
inline fun <reified W : FhirSyncWorker> getWorkerInfo(context: Context) =
fun getWorkerInfo(context: Context, workName: String) =
WorkManager.getInstance(context)
.getWorkInfosForUniqueWorkLiveData(W::class.java.name)
.getWorkInfosForUniqueWorkLiveData(workName)
.asFlow()
.flatMapConcat { it.asFlow() }
.mapNotNull { workInfo ->
Expand All @@ -106,11 +108,6 @@ object Sync {
}
}

/** Gets the timestamp of the last sync job. */
fun getLastSyncTimestamp(context: Context): OffsetDateTime? {
return DatastoreUtil(context).readLastSyncTimestamp()
}

@PublishedApi
internal inline fun <W : FhirSyncWorker> createOneTimeWorkRequest(
retryConfiguration: RetryConfiguration?,
Expand Down Expand Up @@ -155,4 +152,9 @@ object Sync {
}
return periodicWorkRequestBuilder.build()
}

/** Gets the timestamp of the last sync job. */
fun getLastSyncTimestamp(context: Context): OffsetDateTime? {
return DatastoreUtil(context).readLastSyncTimestamp()
}
}