From 65274a6dc2e15365b559dbd427f6e827faaef224 Mon Sep 17 00:00:00 2001 From: andresmr Date: Tue, 16 Apr 2024 14:48:10 +0200 Subject: [PATCH] [ANDROAPP-5701] Check server available instead of internet connection Signed-off-by: andresmr --- .../utils/granularsync/GranularSyncModule.kt | 18 +++--- .../utils/granularsync/GranularSyncModule.kt | 2 + .../granularsync/GranularSyncPresenter.kt | 14 +++++ .../granularsync/GranularSyncRepository.kt | 9 ++- .../utils/granularsync/SyncStatusDialog.kt | 18 +++--- .../granularsync/GranularSyncPresenterTest.kt | 61 ++++++++++++++++++- gradle/libs.versions.toml | 2 +- 7 files changed, 106 insertions(+), 18 deletions(-) diff --git a/app/src/dhis/java/org/dhis2/utils/granularsync/GranularSyncModule.kt b/app/src/dhis/java/org/dhis2/utils/granularsync/GranularSyncModule.kt index 9a42713a10f..e05bbfe1f9a 100644 --- a/app/src/dhis/java/org/dhis2/utils/granularsync/GranularSyncModule.kt +++ b/app/src/dhis/java/org/dhis2/utils/granularsync/GranularSyncModule.kt @@ -60,19 +60,22 @@ class GranularSyncModule( view, repository, schedulerProvider, - object : DispatcherProvider { - override fun io() = Dispatchers.IO - - override fun computation() = Dispatchers.Default - - override fun ui() = Dispatchers.Main - }, + provideDispatchers(), syncContext, workManagerController, smsSyncProvider, ) } + @Provides + fun provideDispatchers() = object : DispatcherProvider { + override fun io() = Dispatchers.IO + + override fun computation() = Dispatchers.Default + + override fun ui() = Dispatchers.Main + } + @Provides fun granularSyncRepository( d2: D2, @@ -87,6 +90,7 @@ class GranularSyncModule( dhisProgramUtils, periodUtils, resourceManager, + provideDispatchers(), ) @Provides diff --git a/app/src/dhisUITesting/java/org/dhis2/utils/granularsync/GranularSyncModule.kt b/app/src/dhisUITesting/java/org/dhis2/utils/granularsync/GranularSyncModule.kt index 9a42713a10f..ecedce9eedc 100644 --- a/app/src/dhisUITesting/java/org/dhis2/utils/granularsync/GranularSyncModule.kt +++ b/app/src/dhisUITesting/java/org/dhis2/utils/granularsync/GranularSyncModule.kt @@ -80,6 +80,7 @@ class GranularSyncModule( periodUtils: DhisPeriodUtils, preferenceProvider: PreferenceProvider, resourceManager: ResourceManager, + dispatcherProvider: DispatcherProvider, ): GranularSyncRepository = GranularSyncRepository( d2, syncContext, @@ -87,6 +88,7 @@ class GranularSyncModule( dhisProgramUtils, periodUtils, resourceManager, + dispatcherProvider, ) @Provides diff --git a/app/src/main/java/org/dhis2/utils/granularsync/GranularSyncPresenter.kt b/app/src/main/java/org/dhis2/utils/granularsync/GranularSyncPresenter.kt index 3c651178c55..28d6b76b951 100644 --- a/app/src/main/java/org/dhis2/utils/granularsync/GranularSyncPresenter.kt +++ b/app/src/main/java/org/dhis2/utils/granularsync/GranularSyncPresenter.kt @@ -89,6 +89,9 @@ class GranularSyncPresenter( workerName = workerName() } + private val _serverAvailability = MutableLiveData() + val serverAvailability: LiveData = _serverAvailability + private fun loadSyncInfo(forcedState: State? = null) { viewModelScope.launch(dispatcher.io()) { val syncState = async { @@ -424,4 +427,15 @@ class GranularSyncPresenter( } } } + + fun checkServerAvailability() { + viewModelScope.launch(dispatcher.io()) { + try { + repository.checkServerAvailability() + _serverAvailability.value = true + } catch (error: RuntimeException) { + _serverAvailability.value = false + } + } + } } diff --git a/app/src/main/java/org/dhis2/utils/granularsync/GranularSyncRepository.kt b/app/src/main/java/org/dhis2/utils/granularsync/GranularSyncRepository.kt index 59a0f0ef833..61ae035dd14 100644 --- a/app/src/main/java/org/dhis2/utils/granularsync/GranularSyncRepository.kt +++ b/app/src/main/java/org/dhis2/utils/granularsync/GranularSyncRepository.kt @@ -1,6 +1,7 @@ package org.dhis2.utils.granularsync import io.reactivex.Single +import kotlinx.coroutines.withContext import org.dhis2.R import org.dhis2.commons.bindings.categoryOptionCombo import org.dhis2.commons.bindings.countEventImportConflicts @@ -41,6 +42,7 @@ import org.dhis2.commons.sync.ConflictType import org.dhis2.commons.sync.SyncContext import org.dhis2.commons.sync.SyncStatusItem import org.dhis2.commons.sync.SyncStatusType +import org.dhis2.commons.viewmodel.DispatcherProvider import org.dhis2.data.dhislogic.DhisProgramUtils import org.hisp.dhis.android.core.D2 import org.hisp.dhis.android.core.common.State @@ -57,6 +59,7 @@ class GranularSyncRepository( private val dhisProgramUtils: DhisProgramUtils, private val periodUtils: DhisPeriodUtils, private val resourceManager: ResourceManager, + private val dispatcher: DispatcherProvider, ) { fun getUiState(forcedState: State? = null): SyncUiState { @@ -554,7 +557,7 @@ class GranularSyncRepository( val teiMainAttribute = tei.let { d2.teiMainAttributes(it.uid(), programUid) - } ?: emptyList() + } val label = teiMainAttribute.firstOrNull()?.let { (attributeName, value) -> "$attributeName: $value" @@ -980,6 +983,10 @@ class GranularSyncRepository( .map { it.displayName() } } } + + suspend fun checkServerAvailability() = withContext(dispatcher.io()) { + d2.systemInfoModule().ping().blockingGet() + } } fun List.sortedByState(): List { diff --git a/app/src/main/java/org/dhis2/utils/granularsync/SyncStatusDialog.kt b/app/src/main/java/org/dhis2/utils/granularsync/SyncStatusDialog.kt index 51c33ad19ef..20f5429bf77 100644 --- a/app/src/main/java/org/dhis2/utils/granularsync/SyncStatusDialog.kt +++ b/app/src/main/java/org/dhis2/utils/granularsync/SyncStatusDialog.kt @@ -181,14 +181,18 @@ class SyncStatusDialog : BottomSheetDialogFragment(), GranularSyncContracts.View } private fun onSyncClick() { - when { - networkUtils.isOnline() -> syncGranular() - viewModel.canSendSMS() && - viewModel.isSMSEnabled(context?.showSMS() == true) -> syncSms() - - !networkUtils.isOnline() && - !viewModel.isSMSEnabled(context?.showSMS() == true) -> showSnackbar() + viewModel.serverAvailability.observe(viewLifecycleOwner) { isAvailable -> + val canSendSMS = viewModel.canSendSMS() + val isSMSEnabled = viewModel.isSMSEnabled(context?.showSMS() == true) + + when { + isAvailable -> syncGranular() + canSendSMS && isSMSEnabled -> syncSms() + else -> showSnackbar() + } } + + viewModel.checkServerAvailability() } private fun showSnackbar() { diff --git a/app/src/test/java/org/dhis2/utils/granularsync/GranularSyncPresenterTest.kt b/app/src/test/java/org/dhis2/utils/granularsync/GranularSyncPresenterTest.kt index a000609fd85..aed6ed2e9b5 100644 --- a/app/src/test/java/org/dhis2/utils/granularsync/GranularSyncPresenterTest.kt +++ b/app/src/test/java/org/dhis2/utils/granularsync/GranularSyncPresenterTest.kt @@ -8,7 +8,9 @@ import androidx.lifecycle.Observer import androidx.work.WorkInfo import io.reactivex.Completable import io.reactivex.Single +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.UnconfinedTestDispatcher import org.dhis2.commons.Constants import org.dhis2.commons.sync.ConflictType @@ -23,14 +25,18 @@ import org.hisp.dhis.android.core.common.ObjectWithUid import org.hisp.dhis.android.core.common.State import org.hisp.dhis.android.core.dataset.DataSet import org.hisp.dhis.android.core.dataset.DataSetElement +import org.junit.After import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.ArgumentMatchers.anyList import org.mockito.Mockito import org.mockito.kotlin.any import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doThrow import org.mockito.kotlin.mock import org.mockito.kotlin.times import org.mockito.kotlin.verify @@ -49,14 +55,25 @@ class GranularSyncPresenterTest { private val trampolineSchedulerProvider = TrampolineSchedulerProvider() private val testingDispatcher = UnconfinedTestDispatcher() private val testDispatcher: DispatcherProvider = mock { - on { io() } doReturn testingDispatcher - on { ui() } doReturn testingDispatcher + on { io() } doReturn Dispatchers.Unconfined + on { ui() } doReturn Dispatchers.Unconfined } private val workManager = Mockito.mock(WorkManagerController::class.java) private val smsSyncProvider: SMSSyncProvider = mock() private val context: Context = mock() private val syncContext: SyncContext = SyncContext.Global() + @Before + fun setUp() { +// Dispatchers.setMain(testingDispatcher) + } + + @After + fun tearDown() { +// Dispatchers.resetMain() +// testDispatcher.cleanupTestCoroutines() + } + @Test fun `should return tracker program error state`() { val presenter = GranularSyncPresenter( @@ -463,4 +480,44 @@ class GranularSyncPresenterTest { verify(workManager).syncDataForWorker(Constants.DATA_NOW, Constants.INITIAL_SYNC) verify(workInfoObserver).onChanged(anyList()) } + + @Test + fun shouldCheckAvailableConnection() = runBlocking { + whenever(repository.checkServerAvailability()) doReturn "pong" + + val presenter = GranularSyncPresenter( + d2, + view, + repository, + trampolineSchedulerProvider, + testDispatcher, + SyncContext.Global(), + workManager, + smsSyncProvider, + ) + + presenter.checkServerAvailability() + + assertTrue(presenter.serverAvailability.value!!) + } + + @Test + fun shouldCheckUnavailableConnection() = runBlocking { + whenever(repository.checkServerAvailability()) doThrow RuntimeException() + + val presenter = GranularSyncPresenter( + d2, + view, + repository, + trampolineSchedulerProvider, + testDispatcher, + SyncContext.Global(), + workManager, + smsSyncProvider, + ) + + presenter.checkServerAvailability() + + assertFalse(presenter.serverAvailability.value!!) + } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d818b325dcc..0f395580c78 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ hilt = '2.47' hiltCompiler = '1.0.0' jacoco = '0.8.10' designSystem = "0.2-20240412.132136-56" -dhis2sdk = "1.10.0-20240322.092247-38" +dhis2sdk = "1.10.0-20240410.081505-42" ruleEngine = "3.0.0-20240119.134348-12" expressionParser = "1.1.0-20240219.115041-14" appcompat = "1.6.1"