From 966c0c5d3819605a0b13e6258181013cdeea44a6 Mon Sep 17 00:00:00 2001 From: Naman Khurmi Date: Thu, 20 Feb 2025 17:05:01 +0530 Subject: [PATCH] NTP-40266 | Revamp for home content deserialization (#15038) Signed-off-by: Naman Khurmi --- android/app/build.gradle | 2 +- .../com/naviapp/home/reducer/HomeReducer.kt | 7 +- .../usecase/HomeContentProcessingUseCase.kt | 313 ++++++++++++++++++ .../naviapp/home/viewmodel/HomeViewModel.kt | 128 ++----- .../cache/repository/NaviCacheRepository.kt | 34 +- .../main/java/com/navi/common/utils/Ext.kt | 24 ++ 6 files changed, 372 insertions(+), 136 deletions(-) create mode 100644 android/app/src/main/java/com/naviapp/home/usecase/HomeContentProcessingUseCase.kt diff --git a/android/app/build.gradle b/android/app/build.gradle index f9a7bc1d4d..871f30a0fd 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -129,7 +129,7 @@ android { targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - freeCompilerArgs += ["-Xstring-concat=inline"] + freeCompilerArgs += ["-Xstring-concat=inline","-Xcontext-receivers"] jvmTarget = '17' } lint { diff --git a/android/app/src/main/java/com/naviapp/home/reducer/HomeReducer.kt b/android/app/src/main/java/com/naviapp/home/reducer/HomeReducer.kt index a1c7e53523..101c8c9e6a 100644 --- a/android/app/src/main/java/com/naviapp/home/reducer/HomeReducer.kt +++ b/android/app/src/main/java/com/naviapp/home/reducer/HomeReducer.kt @@ -87,7 +87,7 @@ class HomeReducer : BaseReducer { is HpEvents.UpdateFrontLayerContent -> { val updatedList = updateScreenContent( - renderingFirstTime = previousState.isRenderingFirstTime, + renderingFirstTime = event.isRenderingFirstTime, newWidgets = event.content, oldWidgets = previousState.frontLayerContent, ) @@ -100,7 +100,7 @@ class HomeReducer : BaseReducer { is HpEvents.UpdatePrioritySectionData -> { val updatedList = updateScreenContent( - renderingFirstTime = previousState.isRenderingFirstTime, + renderingFirstTime = true, newWidgets = event.prioritySectionData.content ?: previousState.frontLayerContent, oldWidgets = previousState.frontLayerContent, @@ -157,7 +157,8 @@ sealed interface HpEvents : UiEvent { data object RenderedFirstTime : HpEvents data class UpdateFrontLayerContent( - val content: List> + val content: List>, + val isRenderingFirstTime: Boolean, ) : HpEvents data class UpdatePrioritySectionData(val prioritySectionData: HomePrioritySectionData) : diff --git a/android/app/src/main/java/com/naviapp/home/usecase/HomeContentProcessingUseCase.kt b/android/app/src/main/java/com/naviapp/home/usecase/HomeContentProcessingUseCase.kt new file mode 100644 index 0000000000..82c1d02714 --- /dev/null +++ b/android/app/src/main/java/com/naviapp/home/usecase/HomeContentProcessingUseCase.kt @@ -0,0 +1,313 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.naviapp.home.usecase + +import com.navi.analytics.utils.NaviTrackEvent +import com.navi.base.cache.model.NaviCacheAltSourceEntity +import com.navi.base.cache.model.NaviCacheEntity +import com.navi.base.cache.model.NaviCacheEntityInfo +import com.navi.base.cache.repository.NaviCacheRepositoryImpl +import com.navi.base.cache.util.NaviSharedDbKeys +import com.navi.common.alchemist.model.AlchemistScreenDefinition +import com.navi.common.utils.safeAsync +import com.navi.common.utils.safeLaunch +import com.naviapp.BuildConfig +import com.naviapp.home.reducer.HpEvents +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers + +class HomeContentProcessingUseCase +@Inject +constructor( + private val naviCacheRepository: NaviCacheRepositoryImpl, + private val deserializer: AsyncDeserialization, +) { + companion object { + private const val EVENT_NAME = "naviapp_home_content_processing_event" + private const val EVENT_KEY_CURRENT_STATUS = "currentStatus" + + // Cache related events + private const val CACHE_RETRIEVAL_NO_CONTENT = "Cache retrieval - No cached content found" + private const val CACHE_CONTENT_PROCESSING_START = "Starting cache content processing" + private const val CACHE_PRIORITY_SECTION_EXTRACTED = "Priority section extracted" + private const val CACHE_SCREEN_LAYOUT_PROCESSED = "Screen layout processed" + private const val CACHE_REMAINING_CONTENT_PROCESSED = "Remaining content processed" + private const val CACHE_CONTENT_PROCESSING_COMPLETED = "Cache content processing completed" + private const val CACHE_DATA_NOT_FOUND = "Home cache data not found during first launch" + private const val CACHE_DATA_LOADED = + "Home cache data loaded successfully during first launch" + private const val CACHE_DATA_RETRIEVAL_START = "Starting cache data retrieval" + + // Network related events + private const val NETWORK_FETCH_START = "Starting network data fetch" + private const val NETWORK_SUBSEQUENT_FETCH_START = "Starting subsequent network fetch" + private const val NETWORK_RESPONSE_NULL = "Network response is null - Context: %s" + private const val NETWORK_ERROR = "Network error - Context: %s" + private const val NETWORK_SUCCESS = "Network success - Context: %s" + + // Sync related events + private const val SYNC_STARTED = "Home sync started - First time render: %s" + private const val SYNC_CACHE_OPERATION_COMPLETED = + "Cache operation completed - Data loaded: %s" + private const val SYNC_FIRST_RENDER_NETWORK = "First render completed from network data" + private const val SYNC_REFRESH_CONTENT = + "Refreshing content - First render: %s, Widgets: %d" + } + + private fun statusTrackEvent(event: String) { + NaviTrackEvent.trackEvent( + EVENT_NAME, + eventValues = mapOf(EVENT_KEY_CURRENT_STATUS to event), + ) + } + + private fun statusTrackEventWithContext(eventTemplate: String, vararg args: Any) { + statusTrackEvent(eventTemplate.format(*args)) + } + + private fun refreshHomeScreenContent( + screenDefinition: AlchemistScreenDefinition, + eventHandler: (HpEvents) -> Unit, + isRenderingFirstTime: Boolean, + ) { + eventHandler(HpEvents.UpdateScreenWithoutContent(screenDefinition)) + + screenDefinition.screenStructure?.content?.widgets?.let { homeWidgets -> + eventHandler(HpEvents.UpdateFrontLayerContent(homeWidgets, isRenderingFirstTime)) + } + } + + private suspend fun retrieveHomeCacheData( + coroutineScope: CoroutineScope, + eventHandler: (HpEvents) -> Unit, + onCompletion: (NaviCacheEntity?) -> Unit, + ) { + val cachedHomeContent = naviCacheRepository.get(NaviSharedDbKeys.HOME_TAB.name) + + if (cachedHomeContent == null) { + statusTrackEvent(CACHE_RETRIEVAL_NO_CONTENT) + onCompletion(null) + return + } + + statusTrackEvent(CACHE_CONTENT_PROCESSING_START) + deserializer.setCacheEntity(cachedHomeContent.value) + + val priorityContentSection = deserializer.getPrioritySection() + eventHandler(HpEvents.UpdatePrioritySectionData(priorityContentSection)) + statusTrackEvent(CACHE_PRIORITY_SECTION_EXTRACTED) + + val screenLayoutDeferred = + coroutineScope.safeAsync { + val screenLayout = deserializer.getScreenDefinitionWithoutContent() + statusTrackEvent(CACHE_SCREEN_LAYOUT_PROCESSED) + screenLayout?.let { + eventHandler(HpEvents.UpdateScreenWithoutContent(screenLayout)) + } + } + + coroutineScope + .safeAsync { + val remainingContent = deserializer.getRemainingContent() + statusTrackEvent(CACHE_REMAINING_CONTENT_PROCESSED) + remainingContent?.let { content -> + screenLayoutDeferred.await() + eventHandler( + HpEvents.UpdateFrontLayerContent( + content = content, + isRenderingFirstTime = true, + ) + ) + } + } + .await() + + statusTrackEvent(CACHE_CONTENT_PROCESSING_COMPLETED) + onCompletion(cachedHomeContent) + } + + context(CoroutineScope) + fun synchronizeHomeContent( + isFirstTimeRender: Boolean, + fetchHomeContent: suspend () -> NaviCacheAltSourceEntity, + onContentSynchronized: () -> Unit, + eventHandler: (HpEvents) -> Unit, + ) { + statusTrackEventWithContext(SYNC_STARTED, isFirstTimeRender) + + if (isFirstTimeRender) { + handleFirstTimeRender(fetchHomeContent, onContentSynchronized, eventHandler) + } else { + handleSubsequentFetch(fetchHomeContent, onContentSynchronized, eventHandler) + } + } + + private fun CoroutineScope.handleFirstTimeRender( + fetchHomeContent: suspend () -> NaviCacheAltSourceEntity, + onContentSynchronized: () -> Unit, + eventHandler: (HpEvents) -> Unit, + ) { + var cacheDataLoaded = false + + val cacheLoadOperation = + launchCacheLoad( + eventHandler = eventHandler, + onContentSynchronized = onContentSynchronized, + onCacheLoaded = { cacheDataLoaded = it }, + ) + + launchNetworkFetchAndProcess( + fetchHomeContent = fetchHomeContent, + cacheLoadOperation = cacheLoadOperation, + cacheDataLoaded = cacheDataLoaded, + onContentSynchronized = onContentSynchronized, + eventHandler = eventHandler, + ) + } + + private fun CoroutineScope.handleSubsequentFetch( + fetchHomeContent: suspend () -> NaviCacheAltSourceEntity, + onContentSynchronized: () -> Unit, + eventHandler: (HpEvents) -> Unit, + ) { + safeLaunch(Dispatchers.IO) { + statusTrackEvent(NETWORK_SUBSEQUENT_FETCH_START) + val networkResponse = fetchNetworkData(fetchHomeContent) + + processNetworkResponse( + networkResponse = networkResponse, + contextType = ResponseContext.SUBSEQUENT_FETCH, + eventHandler = eventHandler, + onSuccess = { screenContent -> + screenContent?.let { + refreshScreenWithTracking( + screenContent = it, + eventHandler = eventHandler, + isFirstRender = false, + onContentSynchronized = onContentSynchronized, + ) + } + }, + ) + } + } + + private fun CoroutineScope.launchCacheLoad( + eventHandler: (HpEvents) -> Unit, + onContentSynchronized: () -> Unit, + onCacheLoaded: (Boolean) -> Unit, + ): Deferred = + safeAsync(Dispatchers.IO) { + statusTrackEvent(CACHE_DATA_RETRIEVAL_START) + retrieveHomeCacheData(coroutineScope = this, eventHandler = eventHandler) { cachedEntity + -> + if (cachedEntity == null) { + statusTrackEvent(CACHE_DATA_NOT_FOUND) + onCacheLoaded(false) + } else { + statusTrackEvent(CACHE_DATA_LOADED) + onCacheLoaded(true) + onContentSynchronized() + eventHandler(HpEvents.RenderedFirstTime) + } + } + } + + private fun CoroutineScope.launchNetworkFetchAndProcess( + fetchHomeContent: suspend () -> NaviCacheAltSourceEntity, + cacheLoadOperation: Deferred, + cacheDataLoaded: Boolean, + onContentSynchronized: () -> Unit, + eventHandler: (HpEvents) -> Unit, + ) { + safeLaunch(Dispatchers.IO) { + statusTrackEvent(NETWORK_FETCH_START) + val networkResponse = fetchNetworkData(fetchHomeContent) + + processNetworkResponse( + networkResponse = networkResponse, + contextType = ResponseContext.FIRST_LAUNCH, + eventHandler = eventHandler, + onSuccess = { parsedScreenContent -> + cacheLoadOperation.await() + statusTrackEventWithContext(SYNC_CACHE_OPERATION_COMPLETED, cacheDataLoaded) + + parsedScreenContent?.let { + refreshScreenWithTracking( + screenContent = it, + eventHandler = eventHandler, + isFirstRender = !cacheDataLoaded, + onContentSynchronized = onContentSynchronized, + ) + if (!cacheDataLoaded) { + statusTrackEvent(SYNC_FIRST_RENDER_NETWORK) + eventHandler(HpEvents.RenderedFirstTime) + } + } + }, + ) + } + } + + private suspend fun fetchNetworkData(fetchHomeContent: suspend () -> NaviCacheAltSourceEntity) = + naviCacheRepository.fetchFromAltSourceWithSimilarityCheck( + key = NaviSharedDbKeys.HOME_TAB.name, + version = BuildConfig.VERSION_CODE.toLong(), + getDataFromAltSource = fetchHomeContent, + ) + + private suspend fun processNetworkResponse( + networkResponse: NaviCacheEntityInfo?, + contextType: ResponseContext, + eventHandler: (HpEvents) -> Unit, + onSuccess: suspend (AlchemistScreenDefinition?) -> Unit, + ) { + when { + networkResponse == null -> { + statusTrackEventWithContext(NETWORK_RESPONSE_NULL, contextType) + } + networkResponse.isError -> { + statusTrackEventWithContext(NETWORK_ERROR, contextType) + if (contextType == ResponseContext.FIRST_LAUNCH) { + eventHandler(HpEvents.TriggerErrorState) + } + } + else -> { + statusTrackEventWithContext(NETWORK_SUCCESS, contextType) + val parsedContent = networkResponse.data?.let { deserializer.getScreen(it.value) } + onSuccess(parsedContent) + } + } + } + + private fun refreshScreenWithTracking( + screenContent: AlchemistScreenDefinition, + eventHandler: (HpEvents) -> Unit, + isFirstRender: Boolean, + onContentSynchronized: () -> Unit, + ) { + statusTrackEventWithContext( + SYNC_REFRESH_CONTENT, + isFirstRender, + screenContent.screenStructure?.content?.widgets?.size ?: 0, + ) + refreshHomeScreenContent( + screenDefinition = screenContent, + eventHandler = eventHandler, + isRenderingFirstTime = isFirstRender, + ) + onContentSynchronized() + } + + private enum class ResponseContext { + FIRST_LAUNCH, + SUBSEQUENT_FETCH, + } +} diff --git a/android/app/src/main/java/com/naviapp/home/viewmodel/HomeViewModel.kt b/android/app/src/main/java/com/naviapp/home/viewmodel/HomeViewModel.kt index 1f1dea5651..5d0887bd4a 100644 --- a/android/app/src/main/java/com/naviapp/home/viewmodel/HomeViewModel.kt +++ b/android/app/src/main/java/com/naviapp/home/viewmodel/HomeViewModel.kt @@ -39,10 +39,10 @@ import com.naviapp.home.reducer.HpEffects import com.naviapp.home.reducer.HpEvents import com.naviapp.home.reducer.HpStates import com.naviapp.home.respository.HomeRepository -import com.naviapp.home.usecase.AsyncDeserialization import com.naviapp.home.usecase.FetchHomeItemsUseCase import com.naviapp.home.usecase.HandleCtaUseCase import com.naviapp.home.usecase.HandleUpiUseCase +import com.naviapp.home.usecase.HomeContentProcessingUseCase import com.naviapp.models.response.NotificationSettings import com.naviapp.nux.handler.NewUserExperienceHandler import com.naviapp.utils.Constants.HomePageConstants.FETCH_HOME_ITEMS_TIMEOUT @@ -53,13 +53,10 @@ import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withTimeout @HiltViewModel @@ -68,7 +65,6 @@ class HomeViewModel constructor( private val homeRepository: HomeRepository, private val naviCacheRepository: NaviCacheRepositoryImpl, - private val deserializer: AsyncDeserialization, private val fetchHomeItemsUseCase: FetchHomeItemsUseCase, private val selectiveRefreshHandler: SelectiveRefreshHandler, private val ctaHandler: HandleCtaUseCase, @@ -78,18 +74,17 @@ constructor( val videoViewHelper: Lazy, val sectionVisibilityTracker: HomePageSectionImpressionTracker, val postRenderTaskExecutor: PostRenderTaskExecutor, + private val homeContentProcessingUseCase: HomeContentProcessingUseCase, ) : BaseMviViewModel( initialState = HpStates(), reducer = HomeReducer(), ) { var shouldRefreshHomeApi: Boolean = false - private var homeTabLastUpdateTimestamp: Long = System.currentTimeMillis() private var homePageRefreshJob: Job? = null val naviAnalyticsEventTracker by lazy { NaviAnalytics.naviAnalytics.Home() } private var analyticsStartTs = System.currentTimeMillis() private var isHomeUIRenderedEventLogged = false - private val mutex = Mutex() var isErrorNaeTriggered = false // Internet Connectivity @@ -132,12 +127,12 @@ constructor( } } - private suspend fun fetchHomeDataFromApi(naeScreenName: String) = + private suspend fun fetchHomeDataFromApi(naeScreenName: String, screenHash: String? = null) = fetchHomeItemsUseCase.fetchHomeItemFromAPI( connectivityObserver = connectivityObserver, availableAppVersionCode = PreferenceManager.getIntPreferenceApp(CURRENT_VERSION_IN_STORE), - screenHash = null, + screenHash = screenHash, naeScreenName = naeScreenName, onFailure = { errorMessage, errors -> setEffect { HpEffects.OnApiFailure(errorMessage, errors) } @@ -180,11 +175,7 @@ constructor( } naviAnalyticsEventTracker.onHomePageApiCall() analyticsStartTs = System.currentTimeMillis() - fetchHomeDataFromCache( - availableAppVersionCode = - PreferenceManager.getIntPreferenceApp(CURRENT_VERSION_IN_STORE), - naeScreenName = naeScreenName, - ) + fetchHomeDataFromCache(naeScreenName = naeScreenName) } private fun handleLoggedOutUser(activity: HomePageActivity) { @@ -196,17 +187,25 @@ constructor( ) } - private fun fetchHomeDataFromCache(availableAppVersionCode: Int?, naeScreenName: String) { + private fun fetchHomeDataFromCache(naeScreenName: String) { if (homePageRefreshJob?.isActive == true) return homePageRefreshJob = viewModelScope.safeLaunch(Dispatchers.IO) { handleDataModification() val screenHash = state.value.screenMetaData?.get(SCREEN_HASH) - fetchAndHandleCacheData( - availableAppVersionCode = availableAppVersionCode, - screenHash = screenHash, - naeScreenName = naeScreenName, - ) + with(viewModelScope) { + homeContentProcessingUseCase.synchronizeHomeContent( + isFirstTimeRender = state.value.isRenderingFirstTime, + eventHandler = { sendEvent(it) }, + onContentSynchronized = { finishProcessing() }, + fetchHomeContent = { + fetchHomeDataFromApi( + naeScreenName = naeScreenName, + screenHash = screenHash, + ) + }, + ) + } } } @@ -216,96 +215,9 @@ constructor( } } - private suspend fun fetchAndHandleCacheData( - availableAppVersionCode: Int?, - screenHash: String?, - naeScreenName: String, - ) { - naviCacheRepository - .getDataAndFetchFromAltSourceWithSimilarityCheck( - key = NaviSharedDbKeys.HOME_TAB.name, - version = BuildConfig.VERSION_CODE.toLong(), - getDataFromAltSource = { - fetchHomeItemsUseCase.fetchHomeItemFromAPI( - connectivityObserver = connectivityObserver, - availableAppVersionCode = availableAppVersionCode, - screenHash = screenHash, - naeScreenName = naeScreenName, - onFailure = { errorMessage, errors -> - setEffect { HpEffects.OnApiFailure(errorMessage, errors) } - }, - noInternetCallback = { - viewModelScope.safeLaunch(Dispatchers.IO) { - _internetConnectivity.emit(ConnectivityObserver.Status.Unavailable) - } - }, - ) - }, - ) - .collect { response -> - viewModelScope.safeLaunch(Dispatchers.IO) { - mutex.withLock { - if (response.isError) { - sendEvent(HpEvents.TriggerErrorState) - } - response.data?.let { data -> - if (state.value.isRenderingFirstTime) { - handleFirstLoad(data) - } else handleSubsequentLoad(data) - } - } - } - } - } - - private suspend fun handleFirstLoad(data: NaviCacheEntity) { - deserializer.setCacheEntity(data.value) - viewModelScope - .async { - sendEvent(HpEvents.UpdatePrioritySectionData(deserializer.getPrioritySection())) - } - .await() - viewModelScope - .async(Dispatchers.IO) { - val screenDefinitionWithoutContentDeferred = launch { - deserializer.getScreenDefinitionWithoutContent()?.let { screen -> - sendEvent(HpEvents.UpdateScreenWithoutContent(screen)) - } - } - launch { - deserializer.getRemainingContent()?.let { remainingItems -> - screenDefinitionWithoutContentDeferred.invokeOnCompletion { - sendEvent(HpEvents.UpdateFrontLayerContent(remainingItems)) - sendEvent(HpEvents.RenderedFirstTime) - } - } - } - } - .await() - finishProcessing(data) - } - - private suspend fun handleSubsequentLoad(data: NaviCacheEntity) { - viewModelScope - .async { - val screen = deserializer.getScreen(data.value) - screen.let { - sendEvent(HpEvents.UpdateScreenWithoutContent(it)) - it.screenStructure?.let { struct -> - struct.content?.widgets?.let { widgets -> - sendEvent(HpEvents.UpdateFrontLayerContent(widgets)) - } - } - } - } - .await() - finishProcessing(data) - } - - private fun finishProcessing(data: NaviCacheEntity) { + private fun finishProcessing() { setEffect { HpEffects.OnRenderActions } updateHomeApiTimestamp() - data.updatedAt.let { homeTabLastUpdateTimestamp = it } selectiveRefreshHandler.handleSuccessState(this@HomeViewModel) } diff --git a/android/navi-base/src/main/java/com/navi/base/cache/repository/NaviCacheRepository.kt b/android/navi-base/src/main/java/com/navi/base/cache/repository/NaviCacheRepository.kt index b054f1a96c..b948643bb8 100644 --- a/android/navi-base/src/main/java/com/navi/base/cache/repository/NaviCacheRepository.kt +++ b/android/navi-base/src/main/java/com/navi/base/cache/repository/NaviCacheRepository.kt @@ -11,7 +11,6 @@ import com.navi.base.cache.dao.NaviCacheDao import com.navi.base.cache.model.NaviCacheAltSourceEntity import com.navi.base.cache.model.NaviCacheEntity import com.navi.base.cache.model.NaviCacheEntityInfo -import com.navi.base.utils.orTrue import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged @@ -55,7 +54,7 @@ interface NaviCacheRepository { emitOnFailure: Boolean = false, ): Flow - fun getDataAndFetchFromAltSourceWithSimilarityCheck( + suspend fun fetchFromAltSourceWithSimilarityCheck( key: String, version: Long? = null, getDataFromAltSource: suspend () -> NaviCacheAltSourceEntity, @@ -67,7 +66,7 @@ interface NaviCacheRepository { return currentData.version == altData.version && currentDataValue == altDataValue }, - ): Flow + ): NaviCacheEntityInfo? suspend fun getLastUpdatedTime(key: String): Long } @@ -214,30 +213,22 @@ class NaviCacheRepositoryImpl @Inject constructor(private val naviCacheDao: Navi } } - override fun getDataAndFetchFromAltSourceWithSimilarityCheck( + override suspend fun fetchFromAltSourceWithSimilarityCheck( key: String, version: Long?, getDataFromAltSource: suspend () -> NaviCacheAltSourceEntity, isCurrentAndAltDataSame: (currentData: NaviCacheEntity?, altData: NaviCacheAltSourceEntity) -> Boolean, - ) = flow { + ): NaviCacheEntityInfo? { val currentValueInDB = get(key = key) - if (currentValueInDB != null) { // Its a valid value - emit( - NaviCacheEntityInfo( - data = currentValueInDB, - isComingFromAltSource = false, - isError = false, - ) - ) - } - val entityFromAltSource = getDataFromAltSource.invoke() if (entityFromAltSource.isSuccess.not() || entityFromAltSource.value.isNullOrEmpty()) { - if (currentValueInDB?.value.isNullOrEmpty().orTrue()) { - emit(NaviCacheEntityInfo(isError = true, data = null)) + return if (currentValueInDB == null) { + NaviCacheEntityInfo(isError = true) + } else { + null } } else { val naviCacheEntity = @@ -254,14 +245,9 @@ class NaviCacheRepositoryImpl @Inject constructor(private val naviCacheDao: Navi if (isDataSame.not()) { save(naviCacheEntity = naviCacheEntity) - emit( - NaviCacheEntityInfo( - data = naviCacheEntity, - isError = false, - isComingFromAltSource = true, - ) - ) + return NaviCacheEntityInfo(data = naviCacheEntity, isComingFromAltSource = true) } + return null } } diff --git a/android/navi-common/src/main/java/com/navi/common/utils/Ext.kt b/android/navi-common/src/main/java/com/navi/common/utils/Ext.kt index 05c655cdcd..428423bba4 100644 --- a/android/navi-common/src/main/java/com/navi/common/utils/Ext.kt +++ b/android/navi-common/src/main/java/com/navi/common/utils/Ext.kt @@ -87,10 +87,14 @@ import java.util.Locale import java.util.zip.GZIPOutputStream import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext +import kotlin.coroutines.cancellation.CancellationException import kotlin.time.Duration import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Deferred import kotlinx.coroutines.Job +import kotlinx.coroutines.async import kotlinx.coroutines.launch import okhttp3.Request import org.json.JSONObject @@ -652,6 +656,26 @@ fun CoroutineScope.safeLaunch( } } +fun CoroutineScope.safeAsync( + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> T, +): Deferred { + val exceptionHandler = CoroutineExceptionHandler { _, throwable -> throwable.log() } + + return async(exceptionHandler + context, start) { + try { + block() + } catch (e: CancellationException) { + e.log() + null + } catch (e: Exception) { + e.log() + null + } + } +} + fun Activity.startEnterAnimation() { this.overridePendingTransition(R.anim.parallax_slide_in_right, R.anim.parallax_slide_out_left) }