diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt index d339c26700..e2ae763026 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt @@ -21,6 +21,7 @@ import com.google.android.fhir.FhirEngine import com.google.android.fhir.db.ResourceNotFoundException import com.google.android.fhir.get import com.google.android.fhir.logicalId +import java.io.FileNotFoundException import java.net.UnknownHostException import java.util.LinkedList import java.util.Locale @@ -36,6 +37,7 @@ import org.hl7.fhir.r4.model.Composition import org.hl7.fhir.r4.model.ListResource import org.hl7.fhir.r4.model.Resource import org.hl7.fhir.r4.model.ResourceType +import org.smartregister.fhircore.engine.R import org.smartregister.fhircore.engine.configuration.app.ConfigService import org.smartregister.fhircore.engine.data.remote.fhir.resource.FhirResourceDataSource import org.smartregister.fhircore.engine.util.DispatcherProvider @@ -194,35 +196,40 @@ constructor( // For appId that ends with suffix /debug e.g. app/debug, we load configurations from assets // extract appId by removing the suffix e.g. app from above example val loadFromAssets = appId.endsWith(DEBUG_SUFFIX, ignoreCase = true) + val parsedAppId = appId.substringBefore("/").trim() if (loadFromAssets) { - val parsedAppId = appId.substringBefore("/").trim() - context - .assets - .open(String.format(COMPOSITION_CONFIG_PATH, parsedAppId)) - .bufferedReader() - .readText() - .decodeResourceFromString() - .run { - val iconConfigs = - retrieveCompositionSections().filter { - it.focus.hasIdentifier() && isIconConfig(it.focus.identifier.value) + try { + context + .assets + .open(String.format(COMPOSITION_CONFIG_PATH, parsedAppId)) + .bufferedReader() + .readText() + .decodeResourceFromString() + .run { + val iconConfigs = + retrieveCompositionSections().filter { + it.focus.hasIdentifier() && isIconConfig(it.focus.identifier.value) + } + if (iconConfigs.isNotEmpty()) { + val ids = iconConfigs.joinToString(",") { it.focus.extractId() } + fhirResourceDataSource.getResource( + "${ResourceType.Binary.name}?${Composition.SP_RES_ID}=$ids" + ) + .entry + .forEach { addOrUpdate(it.resource) } } - if (iconConfigs.isNotEmpty()) { - val ids = iconConfigs.joinToString(",") { it.focus.extractId() } - fhirResourceDataSource.getResource( - "${ResourceType.Binary.name}?${Composition.SP_RES_ID}=$ids" - ) - .entry - .forEach { addOrUpdate(it.resource) } + populateConfigurationsMap( + composition = this, + loadFromAssets = true, + appId = parsedAppId, + configsLoadedCallback = configsLoadedCallback, + context = context + ) } - populateConfigurationsMap( - composition = this, - loadFromAssets = true, - appId = parsedAppId, - configsLoadedCallback = configsLoadedCallback, - context = context - ) - } + } catch (fileNotFoundException: FileNotFoundException) { + Timber.e("Missing app configs for app ID: $parsedAppId", fileNotFoundException) + withContext(dispatcherProvider.main()) { configsLoadedCallback(false) } + } } else { fhirEngine.searchCompositionByIdentifier(appId)?.run { populateConfigurationsMap(context, this, false, appId, configsLoadedCallback) diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/ApplicationConfiguration.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/ApplicationConfiguration.kt index 234df9408c..2e84003cb2 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/ApplicationConfiguration.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/ApplicationConfiguration.kt @@ -28,7 +28,7 @@ data class ApplicationConfiguration( val remoteSyncPageSize: Int = 100, val languages: List = listOf("en"), val useDarkTheme: Boolean = false, - val syncInterval: Long = 30, + val syncInterval: Long = 15, val syncStrategies: List = listOf(), val loginConfig: LoginConfig = LoginConfig(), val deviceToDeviceSync: DeviceToDeviceSyncConfig? = null, diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/di/CoreModule.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/di/CoreModule.kt index eb6346326f..ea7e238407 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/di/CoreModule.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/di/CoreModule.kt @@ -18,10 +18,12 @@ package org.smartregister.fhircore.engine.di import android.accounts.AccountManager import android.content.Context +import androidx.work.WorkManager import ca.uhn.fhir.context.FhirContext import ca.uhn.fhir.context.FhirVersionEnum import com.google.android.fhir.FhirEngine import com.google.android.fhir.knowledge.KnowledgeManager +import com.google.android.fhir.sync.Sync import com.google.android.fhir.workflow.FhirOperator import dagger.Module import dagger.Provides @@ -35,7 +37,7 @@ import org.hl7.fhir.r4.utils.FHIRPathEngine import org.smartregister.fhircore.engine.util.helper.TransformSupportServices @InstallIn(SingletonComponent::class) -@Module(includes = [NetworkModule::class, DispatcherModule::class]) +@Module(includes = [NetworkModule::class, DispatcherModule::class, WorkManagerModule::class]) class CoreModule { @Singleton @@ -67,4 +69,6 @@ class CoreModule { @Provides fun provideFhirOperator(fhirEngine: FhirEngine): FhirOperator = FhirOperator(fhirContext = FhirContext.forCached(FhirVersionEnum.R4), fhirEngine = fhirEngine) + + @Singleton @Provides fun provideSync(workManager: WorkManager) = Sync(workManager) } diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/AppSyncWorker.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/AppSyncWorker.kt index b8362e45d4..357767894d 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/AppSyncWorker.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/AppSyncWorker.kt @@ -27,7 +27,6 @@ import com.google.android.fhir.sync.FhirSyncWorker import com.google.android.fhir.sync.UploadConfiguration import dagger.assisted.Assisted import dagger.assisted.AssistedInject -import org.smartregister.fhircore.engine.data.local.DefaultRepository @HiltWorker class AppSyncWorker @@ -36,7 +35,7 @@ constructor( @Assisted appContext: Context, @Assisted workerParams: WorkerParameters, val syncListenerManager: SyncListenerManager, - val defaultRepository: DefaultRepository, + val openSrpFhirEngine: FhirEngine, val appTimeStampContext: AppTimeStampContext, ) : FhirSyncWorker(appContext, workerParams) { @@ -45,13 +44,12 @@ constructor( override fun getDownloadWorkManager(): DownloadWorkManager = OpenSrpDownloadManager( syncParams = syncListenerManager.loadSyncParams(), - context = appTimeStampContext, - defaultRepository + context = appTimeStampContext ) /** Disable ETag for upload */ override fun getUploadConfiguration(): UploadConfiguration = UploadConfiguration(useETagForUpload = false) - override fun getFhirEngine(): FhirEngine = defaultRepository.fhirEngine + override fun getFhirEngine(): FhirEngine = openSrpFhirEngine } diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/OpenSrpDownloadManager.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/OpenSrpDownloadManager.kt index 57379b110e..48cf3f43af 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/OpenSrpDownloadManager.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/OpenSrpDownloadManager.kt @@ -22,17 +22,14 @@ import com.google.android.fhir.sync.download.ResourceParamsBasedDownloadWorkMana import com.google.android.fhir.sync.download.ResourceSearchParams import org.hl7.fhir.r4.model.Resource import org.hl7.fhir.r4.model.ResourceType -import org.smartregister.fhircore.engine.data.local.DefaultRepository import org.smartregister.fhircore.engine.util.extension.updateLastUpdated -/** Created by Ephraim Kigamba - nek.eam@gmail.com on 13-06-2023. */ class OpenSrpDownloadManager( syncParams: ResourceSearchParams, val context: ResourceParamsBasedDownloadWorkManager.TimestampContext, - val defaultRepository: DefaultRepository ) : DownloadWorkManager { - val downloadWorkManager = ResourceParamsBasedDownloadWorkManager(syncParams, context) + private val downloadWorkManager = ResourceParamsBasedDownloadWorkManager(syncParams, context) override suspend fun getNextRequest(): Request? = downloadWorkManager.getNextRequest() @@ -40,8 +37,6 @@ class OpenSrpDownloadManager( downloadWorkManager.getSummaryRequestUrls() override suspend fun processResponse(response: Resource): Collection { - return downloadWorkManager.processResponse(response).apply { - forEach { it.updateLastUpdated() } - } + return downloadWorkManager.processResponse(response).onEach { it.updateLastUpdated() } } } diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/SyncBroadcaster.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/SyncBroadcaster.kt index e293eec6d2..dbfd038097 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/SyncBroadcaster.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/SyncBroadcaster.kt @@ -30,13 +30,13 @@ import java.util.concurrent.TimeUnit import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import kotlinx.coroutines.flow.shareIn import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry import org.smartregister.fhircore.engine.util.DispatcherProvider import timber.log.Timber @@ -44,7 +44,7 @@ import timber.log.Timber /** * This class is used to trigger one time and periodic syncs. A new instance of this class is * created each time because a new instance of [ResourceParamsBasedDownloadWorkManager] is needed - * everytime sync is triggered. This class should not be provided as a singleton. The + * everytime sync is triggered; this class SHOULD NOT be provided as a singleton. The * [SyncJobStatus] events are sent to the registered [OnSyncListener] maintained by the * [SyncListenerManager] */ @@ -55,62 +55,43 @@ constructor( val fhirEngine: FhirEngine, val syncListenerManager: SyncListenerManager, val dispatcherProvider: DispatcherProvider, + val sync: Sync, @ApplicationContext val context: Context, ) { - fun runSync(syncSharedFlow: MutableSharedFlow) { - val coroutineScope = CoroutineScope(dispatcherProvider.main()) + /** + * Run one time sync. The [SyncJobStatus] will be broadcast to all the registered [OnSyncListener] + * 's + */ + suspend fun runOneTimeSync() = coroutineScope { Timber.i("Running one time sync...") - coroutineScope.launch { - syncSharedFlow - .onEach { - syncListenerManager.onSyncListeners.forEach { onSyncListener -> - onSyncListener.onSync(it) - } - } - .handleErrors() - .launchIn(this) - } - - coroutineScope.launch(dispatcherProvider.main()) { - Sync.oneTimeSync(context).collect { syncSharedFlow.emit(it) } - } + sync.oneTimeSync().handleSyncJobStatus(this) } - private fun Flow.handleErrors(): Flow = catch { throwable -> Timber.e(throwable) } - /** * Schedule periodic sync periodically as defined in the application config interval. The - * [SyncJobStatus] will be broadcast to the listeners + * [SyncJobStatus] will be broadcast to all the registered [OnSyncListener]'s */ @OptIn(ExperimentalCoroutinesApi::class) - fun schedulePeriodicSync(periodicSyncSharedFlow: MutableSharedFlow) { + suspend fun schedulePeriodicSync(interval: Long = 15) = coroutineScope { Timber.i("Scheduling periodic sync...") + sync + .periodicSync( + PeriodicSyncConfiguration( + syncConstraints = + Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build(), + repeat = RepeatInterval(interval = interval, timeUnit = TimeUnit.MINUTES) + ) + ) + .handleSyncJobStatus(this) + } - // Launch in main to observer UI updates that should ONLY happen on main thread - val coroutineScope = CoroutineScope(dispatcherProvider.main()) - coroutineScope.launch { - periodicSyncSharedFlow - .onEach { - syncListenerManager.onSyncListeners.forEach { onSyncListener -> - onSyncListener.onSync(it) - } - } - .handleErrors() - .launchIn(this) - - // Switch to io thread when triggering periodic sync - withContext(dispatcherProvider.io()) { - Sync.periodicSync( - context, - PeriodicSyncConfiguration( - syncConstraints = - Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build(), - repeat = RepeatInterval(interval = 15, timeUnit = TimeUnit.MINUTES) - ) - ) - .collect { periodicSyncSharedFlow.emit(it) } + private fun Flow.handleSyncJobStatus(coroutineScope: CoroutineScope) { + this.onEach { + syncListenerManager.onSyncListeners.forEach { onSyncListener -> onSyncListener.onSync(it) } } - } + .catch { throwable -> Timber.e("Encountered an error during sync:", throwable) } + .shareIn(coroutineScope, SharingStarted.Eagerly, 1) + .launchIn(coroutineScope) } } diff --git a/android/engine/src/test/java/org/smartregister/fhircore/engine/sync/AppSyncWorkerTest.kt b/android/engine/src/test/java/org/smartregister/fhircore/engine/sync/AppSyncWorkerTest.kt index c03cd5a981..39dd5a920d 100644 --- a/android/engine/src/test/java/org/smartregister/fhircore/engine/sync/AppSyncWorkerTest.kt +++ b/android/engine/src/test/java/org/smartregister/fhircore/engine/sync/AppSyncWorkerTest.kt @@ -27,7 +27,6 @@ import io.mockk.verify import org.hl7.fhir.r4.model.ResourceType import org.junit.Assert import org.junit.Test -import org.smartregister.fhircore.engine.data.local.DefaultRepository import org.smartregister.fhircore.engine.robolectric.RobolectricTest class AppSyncWorkerTest : RobolectricTest() { @@ -37,17 +36,15 @@ class AppSyncWorkerTest : RobolectricTest() { val syncParams = emptyMap() val syncListenerManager = mockk() val fhirEngine = mockk() - val defaultRepository = mockk() val taskExecutor = mockk() val timeContext = mockk() - every { defaultRepository.fhirEngine } returns fhirEngine every { taskExecutor.backgroundExecutor } returns mockk() every { workerParams.taskExecutor } returns taskExecutor every { syncListenerManager.loadSyncParams() } returns syncParams val appSyncWorker = - AppSyncWorker(mockk(), workerParams, syncListenerManager, defaultRepository, timeContext) + AppSyncWorker(mockk(), workerParams, syncListenerManager, fhirEngine, timeContext) appSyncWorker.getDownloadWorkManager() verify { syncListenerManager.loadSyncParams() } diff --git a/android/engine/src/test/java/org/smartregister/fhircore/engine/sync/SyncBroadcasterTest.kt b/android/engine/src/test/java/org/smartregister/fhircore/engine/sync/SyncBroadcasterTest.kt index 20b6ff5e84..71e4e8a6b6 100644 --- a/android/engine/src/test/java/org/smartregister/fhircore/engine/sync/SyncBroadcasterTest.kt +++ b/android/engine/src/test/java/org/smartregister/fhircore/engine/sync/SyncBroadcasterTest.kt @@ -46,21 +46,13 @@ import org.smartregister.fhircore.engine.util.extension.isIn class SyncBroadcasterTest : RobolectricTest() { @get:Rule(order = 0) val hiltAndroidRule = HiltAndroidRule(this) - @get:Rule(order = 1) val coroutineTestRule = CoroutineTestRule() - @Inject lateinit var sharedPreferencesHelper: SharedPreferencesHelper - @Inject lateinit var configService: ConfigService - private val configurationRegistry: ConfigurationRegistry = Faker.buildTestConfigurationRegistry() - private val fhirEngine = mockk() - private lateinit var syncListenerManager: SyncListenerManager - private lateinit var syncBroadcaster: SyncBroadcaster - private val context = ApplicationProvider.getApplicationContext() @Before @@ -81,6 +73,7 @@ class SyncBroadcasterTest : RobolectricTest() { fhirEngine = fhirEngine, dispatcherProvider = coroutineTestRule.testDispatcherProvider, syncListenerManager = syncListenerManager, + sync = mockk(relaxed = true), context = context ) ) diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index f5f24a2536..6f1070453d 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -23,7 +23,7 @@ desugar-jdk-libs = "1.1.5" easy-rules-jexl = "4.1.0" espresso-core = "3.5.0" fhir-common-utils = "0.0.2-SNAPSHOT" -fhir-engine = "0.1.0-beta03-preview9-SNAPSHOT" +fhir-engine = "0.1.0-beta03-preview9.1-SNAPSHOT" foundation = "1.3.1" fragment-ktx = "1.5.5" fragment-testing = "1.5.2" diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/appsetting/AppSettingViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/appsetting/AppSettingViewModel.kt index da31033f4b..a44721cb15 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/appsetting/AppSettingViewModel.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/appsetting/AppSettingViewModel.kt @@ -176,12 +176,16 @@ constructor( } fun loadConfigurations(context: Context) { - viewModelScope.launch(dispatcherProvider.io()) { - appId.value?.let { thisAppId -> - configurationRegistry.loadConfigurations(thisAppId, context) { + appId.value?.let { thisAppId -> + viewModelScope.launch(dispatcherProvider.io()) { + configurationRegistry.loadConfigurations(thisAppId, context) { loadConfigSuccessful -> showProgressBar.postValue(false) - sharedPreferencesHelper.write(SharedPreferenceKey.APP_ID.name, thisAppId) - context.getActivity()?.launchActivityWithNoBackStackHistory() + if (loadConfigSuccessful) { + sharedPreferencesHelper.write(SharedPreferenceKey.APP_ID.name, thisAppId) + context.getActivity()?.launchActivityWithNoBackStackHistory() + } else { + _error.postValue(context.getString(R.string.application_not_supported, appId.value)) + } } } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt index 7983fe6da8..fa3a44adb2 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt @@ -121,11 +121,14 @@ open class AppMainActivity : BaseMultiLanguageActivity(), QuestionnaireHandler, // Setup the drawer and schedule jobs appMainViewModel.run { - lifecycleScope.launch { retrieveAppMainUiState() } + lifecycleScope.launch { + retrieveAppMainUiState() + if (isDeviceOnline()) + syncBroadcaster.schedulePeriodicSync(applicationConfiguration.syncInterval) + else showToast(getString(R.string.sync_failed), Toast.LENGTH_LONG) + } schedulePeriodicJobs() } - - runSync(syncBroadcaster) } override fun onResume() { @@ -196,15 +199,4 @@ open class AppMainActivity : BaseMultiLanguageActivity(), QuestionnaireHandler, } } } - - private fun runSync(syncBroadcaster: SyncBroadcaster) { - syncBroadcaster.run { - if (isDeviceOnline()) { - with(appMainViewModel.syncSharedFlow) { - runSync(this) - schedulePeriodicSync(this) - } - } else context.showToast(context.getString(R.string.sync_failed), Toast.LENGTH_LONG) - } - } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModel.kt index 2ce395566d..ffafe61427 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModel.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModel.kt @@ -36,7 +36,6 @@ import java.util.Locale import java.util.TimeZone import javax.inject.Inject import kotlin.time.Duration -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.hl7.fhir.r4.model.Binary @@ -96,7 +95,6 @@ constructor( val fhirCarePlanGenerator: FhirCarePlanGenerator, ) : ViewModel() { - val syncSharedFlow = MutableSharedFlow() val appMainUiState: MutableState = mutableStateOf( appMainUiStateOf( @@ -168,9 +166,9 @@ constructor( } } is AppMainEvent.SyncData -> { - if (event.context.isDeviceOnline()) { - syncBroadcaster.runSync(syncSharedFlow) - } else + if (event.context.isDeviceOnline()) + viewModelScope.launch { syncBroadcaster.runOneTimeSync() } + else event.context.showToast(event.context.getString(R.string.sync_failed), Toast.LENGTH_LONG) } is AppMainEvent.OpenRegistersBottomSheet -> displayRegisterBottomSheet(event) diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/usersetting/UserSettingViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/usersetting/UserSettingViewModel.kt index 45847a0939..40055ce60b 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/usersetting/UserSettingViewModel.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/usersetting/UserSettingViewModel.kt @@ -23,7 +23,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.work.WorkManager import com.google.android.fhir.FhirEngine -import com.google.android.fhir.sync.SyncJobStatus import dagger.hilt.android.lifecycle.HiltViewModel import java.util.Locale import javax.inject.Inject @@ -70,7 +69,6 @@ constructor( val showDBResetConfirmationDialog = MutableLiveData(false) val progressBarState = MutableLiveData(Pair(false, 0)) val unsyncedResourcesMutableSharedFlow = MutableSharedFlow>>() - private val syncSharedFlow = MutableSharedFlow() private val applicationConfiguration: ApplicationConfiguration by lazy { configurationRegistry.retrieveConfiguration(ConfigType.Application) } @@ -104,9 +102,9 @@ constructor( } } is UserSettingsEvent.SyncData -> { - if (event.context.isDeviceOnline()) { - syncBroadcaster.runSync(syncSharedFlow) - } else + if (event.context.isDeviceOnline()) + viewModelScope.launch(dispatcherProvider.main()) { syncBroadcaster.runOneTimeSync() } + else event.context.showToast(event.context.getString(R.string.sync_failed), Toast.LENGTH_LONG) } is UserSettingsEvent.SwitchLanguage -> { diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainActivityTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainActivityTest.kt index 48fab8fa56..60f63001aa 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainActivityTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainActivityTest.kt @@ -17,7 +17,6 @@ package org.smartregister.fhircore.quest.ui.main import android.app.Activity -import android.content.Context import android.content.Intent import androidx.activity.result.ActivityResult import androidx.compose.material.ExperimentalMaterialApi @@ -32,25 +31,19 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.just import io.mockk.mockk -import io.mockk.mockkStatic import io.mockk.runs import io.mockk.slot import io.mockk.spyk -import io.mockk.unmockkStatic -import io.mockk.verify import org.hl7.fhir.r4.model.QuestionnaireResponse import org.junit.Assert import org.junit.Before import org.junit.Rule import org.junit.Test import org.robolectric.Robolectric -import org.robolectric.util.ReflectionHelpers import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry import org.smartregister.fhircore.engine.configuration.QuestionnaireConfig -import org.smartregister.fhircore.engine.sync.SyncBroadcaster import org.smartregister.fhircore.engine.task.FhirCarePlanGenerator import org.smartregister.fhircore.engine.util.SharedPreferenceKey -import org.smartregister.fhircore.engine.util.extension.isDeviceOnline import org.smartregister.fhircore.quest.app.fakes.Faker import org.smartregister.fhircore.quest.event.AppEvent import org.smartregister.fhircore.quest.event.EventBus @@ -218,58 +211,9 @@ class AppMainActivityTest : ActivityRobolectricTest() { coVerify { eventBus.triggerEvent(any()) } } - @Test - fun testRunSyncWhenDeviceIsOnline() { - - mockkStatic(Context::isDeviceOnline) - - every { appMainActivity.isDeviceOnline() } returns true - - val syncBroadcaster = - mockk { - every { runSync(any()) } returns Unit - every { schedulePeriodicSync(any()) } returns Unit - } - - ReflectionHelpers.callInstanceMethod( - appMainActivity, - "runSync", - ReflectionHelpers.ClassParameter(SyncBroadcaster::class.java, syncBroadcaster) - ) - - verify(exactly = 1) { syncBroadcaster.runSync(any()) } - verify(exactly = 1) { syncBroadcaster.schedulePeriodicSync(any()) } - - unmockkStatic(Context::isDeviceOnline) - } - - @Test - fun testDoNotRunSyncWhenDeviceIsOffline() { - - mockkStatic(Context::isDeviceOnline) - - every { appMainActivity.isDeviceOnline() } returns false - - val syncBroadcaster = mockk() - - every { syncBroadcaster.context } returns appMainActivity - - ReflectionHelpers.callInstanceMethod( - appMainActivity, - "runSync", - ReflectionHelpers.ClassParameter(SyncBroadcaster::class.java, syncBroadcaster) - ) - - verify(exactly = 0) { syncBroadcaster.runSync(any()) } - verify(exactly = 0) { syncBroadcaster.schedulePeriodicSync(any()) } - - unmockkStatic(Context::isDeviceOnline) - } - @Test fun testStartForResult() { val event = appMainActivity.startForResult - Assert.assertNotNull(event) } } diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModelTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModelTest.kt index bc5f59bb8e..c339252b94 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModelTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModelTest.kt @@ -143,7 +143,7 @@ class AppMainViewModelTest : RobolectricTest() { val appMainEvent = AppMainEvent.SyncData(application) appMainViewModel.onEvent(appMainEvent) - verify(exactly = 1) { syncBroadcaster.runSync(any()) } + coVerify(exactly = 1) { syncBroadcaster.runOneTimeSync() } } @Test @@ -154,7 +154,7 @@ class AppMainViewModelTest : RobolectricTest() { val appMainEvent = AppMainEvent.SyncData(context) appMainViewModel.onEvent(appMainEvent) - verify(exactly = 0) { syncBroadcaster.runSync(any()) } + coVerify(exactly = 0) { syncBroadcaster.runOneTimeSync() } val errorMessage = context.getString(R.string.sync_failed) coVerify { context.showToast(errorMessage, Toast.LENGTH_LONG) } diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/usersetting/UserSettingFragmentTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/usersetting/UserSettingFragmentTest.kt index ca5de99ecd..8793a6f213 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/usersetting/UserSettingFragmentTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/usersetting/UserSettingFragmentTest.kt @@ -80,6 +80,7 @@ class UserSettingFragmentTest : RobolectricTest() { fhirEngine = mockk(), dispatcherProvider = this.coroutineTestRule.testDispatcherProvider, syncListenerManager = mockk(relaxed = true), + sync = mockk(relaxed = true), context = application ) diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/usersetting/UserSettingViewModelTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/usersetting/UserSettingViewModelTest.kt index 6f35901dca..f468a76b09 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/usersetting/UserSettingViewModelTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/usersetting/UserSettingViewModelTest.kt @@ -22,10 +22,12 @@ import android.widget.Toast import androidx.test.core.app.ApplicationProvider import androidx.work.WorkManager import com.google.android.fhir.FhirEngine +import com.google.android.fhir.sync.Sync import dagger.hilt.android.testing.BindValue import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltTestApplication +import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.just @@ -66,17 +68,18 @@ class UserSettingViewModelTest : RobolectricTest() { @get:Rule var hiltRule = HiltAndroidRule(this) @BindValue var configurationRegistry = Faker.buildTestConfigurationRegistry() - lateinit var userSettingViewModel: UserSettingViewModel - lateinit var accountAuthenticator: AccountAuthenticator - lateinit var secureSharedPreference: SecureSharedPreference lateinit var fhirEngine: FhirEngine private var sharedPreferencesHelper: SharedPreferencesHelper private var configService: ConfigService private lateinit var syncBroadcaster: SyncBroadcaster + private lateinit var userSettingViewModel: UserSettingViewModel + private lateinit var accountAuthenticator: AccountAuthenticator + private lateinit var secureSharedPreference: SecureSharedPreference private val context = ApplicationProvider.getApplicationContext() private val resourceService: FhirResourceService = mockk() private val workManager = mockk(relaxed = true, relaxUnitFun = true) private var fhirResourceDataSource: FhirResourceDataSource + private val sync = mockk(relaxed = true) init { sharedPreferencesHelper = SharedPreferencesHelper(context = context, gson = mockk()) @@ -99,6 +102,7 @@ class UserSettingViewModelTest : RobolectricTest() { fhirEngine = mockk(), dispatcherProvider = this.coroutineTestRule.testDispatcherProvider, syncListenerManager = mockk(relaxed = true), + sync = sync, context = context ) ) @@ -120,9 +124,9 @@ class UserSettingViewModelTest : RobolectricTest() { @Test fun testRunSyncWhenDeviceIsOnline() { - every { syncBroadcaster.runSync(any()) } returns Unit + coEvery { syncBroadcaster.runOneTimeSync() } returns Unit userSettingViewModel.onEvent(UserSettingsEvent.SyncData(context)) - verify(exactly = 1) { syncBroadcaster.runSync(any()) } + coVerify(exactly = 1) { syncBroadcaster.runOneTimeSync() } } @Test @@ -131,10 +135,10 @@ class UserSettingViewModelTest : RobolectricTest() { val context = mockk(relaxed = true) { every { isDeviceOnline() } returns false } - every { syncBroadcaster.runSync(any()) } returns Unit + coEvery { syncBroadcaster.runOneTimeSync() } returns Unit userSettingViewModel.onEvent(UserSettingsEvent.SyncData(context)) - verify(exactly = 0) { syncBroadcaster.runSync(any()) } + coVerify(exactly = 0) { syncBroadcaster.runOneTimeSync() } val errorMessage = context.getString(R.string.sync_failed) coVerify { context.showToast(errorMessage, Toast.LENGTH_LONG) }