From a755c03a12717c6b053540a96feefff720501c78 Mon Sep 17 00:00:00 2001 From: Nazar Rusnak Date: Mon, 22 Sep 2025 14:02:48 +0300 Subject: [PATCH 01/11] Separate synchronizer implementation from models --- settings.gradle | 2 ++ synchronizer/api/current.api | 29 ------------------- synchronizer/{ => core}/.gitignore | 0 synchronizer/{ => core}/build.gradle | 6 ++-- synchronizer/{ => core}/consumer-rules.pro | 0 synchronizer/core/gradle.properties | 4 +++ synchronizer/{ => core}/proguard-rules.pro | 0 .../{ => core}/src/main/AndroidManifest.xml | 0 .../synchronizer/core}/BaseSynchronizer.kt | 3 +- synchronizer/gradle.properties | 4 --- synchronizer/model/.gitignore | 1 + synchronizer/model/build.gradle | 16 ++++++++++ synchronizer/model/gradle.properties | 4 +++ .../synchonizer/model/Synchronizable.kt | 5 ++++ .../urlaunched/synchronizer/Synchronizable.kt | 5 ---- 15 files changed, 38 insertions(+), 41 deletions(-) delete mode 100644 synchronizer/api/current.api rename synchronizer/{ => core}/.gitignore (100%) rename synchronizer/{ => core}/build.gradle (94%) rename synchronizer/{ => core}/consumer-rules.pro (100%) create mode 100644 synchronizer/core/gradle.properties rename synchronizer/{ => core}/proguard-rules.pro (100%) rename synchronizer/{ => core}/src/main/AndroidManifest.xml (100%) rename synchronizer/{src/main/java/com/urlaunched/synchronizer => core/src/main/java/com/urlaunched/synchronizer/core}/BaseSynchronizer.kt (98%) delete mode 100644 synchronizer/gradle.properties create mode 100644 synchronizer/model/.gitignore create mode 100644 synchronizer/model/build.gradle create mode 100644 synchronizer/model/gradle.properties create mode 100644 synchronizer/model/src/main/java/com/urlaunched/android/synchonizer/model/Synchronizable.kt delete mode 100644 synchronizer/src/main/java/com/urlaunched/synchronizer/Synchronizable.kt diff --git a/settings.gradle b/settings.gradle index 177ef735..f28458d6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -49,3 +49,5 @@ include ':logger' include ':logger:aspect' include ':logger:annotations' include ':synchronizer' +include ':synchronizer:model' +include ':synchronizer:core' diff --git a/synchronizer/api/current.api b/synchronizer/api/current.api deleted file mode 100644 index d826f659..00000000 --- a/synchronizer/api/current.api +++ /dev/null @@ -1,29 +0,0 @@ -// Signature format: 4.0 -package com.urlaunched.synchronizer { - - public abstract class BaseSynchronizer { - ctor public BaseSynchronizer(); - method public final suspend Object? emitCancelDelete(com.urlaunched.synchronizer.Synchronizable value, kotlin.coroutines.Continuation); - method public final suspend Object? emitDelete(com.urlaunched.synchronizer.Synchronizable value, kotlin.coroutines.Continuation); - method public final suspend Object? emitUpdate(com.urlaunched.synchronizer.Synchronizable value, kotlin.coroutines.Continuation); - method public final kotlinx.coroutines.flow.MutableSharedFlow> getCancelDeleteModel(); - method public final kotlinx.coroutines.flow.MutableSharedFlow> getDeletedModel(); - method public final kotlinx.coroutines.flow.MutableSharedFlow> getUpdateModel(); - method public inline , ID> java.util.List synchronize(java.util.List, kotlinx.coroutines.CoroutineScope viewModelScope); - method public inline , reified R extends com.urlaunched.synchronizer.Synchronizable, ID> java.util.List synchronize(java.util.List, kotlinx.coroutines.CoroutineScope viewModelScope, kotlin.reflect.KClass relatedType, optional kotlin.jvm.functions.Function2 map); - method public inline , ID> kotlinx.coroutines.flow.Flow> synchronize(kotlinx.coroutines.flow.Flow>, kotlinx.coroutines.CoroutineScope viewModelScope); - method public inline , reified R extends com.urlaunched.synchronizer.Synchronizable, ID> kotlinx.coroutines.flow.Flow> synchronize(kotlinx.coroutines.flow.Flow>, kotlinx.coroutines.CoroutineScope viewModelScope, kotlin.reflect.KClass relatedType, optional kotlin.jvm.functions.Function2 map); - method public suspend inline , ID> void synchronize(T, kotlin.jvm.functions.Function1 onUpdate); - method public suspend inline , reified R extends com.urlaunched.synchronizer.Synchronizable, ID> void synchronize(T, kotlin.jvm.functions.Function1 onUpdate, kotlin.reflect.KClass relatedType, kotlin.jvm.functions.Function1 map); - property public final kotlinx.coroutines.flow.MutableSharedFlow> cancelDeleteModel; - property public final kotlinx.coroutines.flow.MutableSharedFlow> deletedModel; - property public final kotlinx.coroutines.flow.MutableSharedFlow> updateModel; - } - - public interface Synchronizable { - method public T getId(); - property public abstract T id; - } - -} - diff --git a/synchronizer/.gitignore b/synchronizer/core/.gitignore similarity index 100% rename from synchronizer/.gitignore rename to synchronizer/core/.gitignore diff --git a/synchronizer/build.gradle b/synchronizer/core/build.gradle similarity index 94% rename from synchronizer/build.gradle rename to synchronizer/core/build.gradle index 16a56fd1..265ec739 100644 --- a/synchronizer/build.gradle +++ b/synchronizer/core/build.gradle @@ -9,7 +9,7 @@ plugins { } android { - namespace 'com.urlaunched.synchronizer' + namespace 'com.urlaunched.synchronizer.core' compileSdk gradleDependencies.compileSdk defaultConfig { @@ -49,6 +49,9 @@ metalava { } dependencies { + // Models + api project(":synchronizer:model") + // Android implementation libs.androidCoreDependencies.core implementation libs.androidCoreDependencies.appcompat @@ -57,7 +60,6 @@ dependencies { // Kotlin implementation(platform(libs.kotlinDependencies.bom)) implementation libs.kotlinDependencies.coroutines - implementation libs.kotlinDependencies.serialization // Paging implementation libs.pagingDependencies.core diff --git a/synchronizer/consumer-rules.pro b/synchronizer/core/consumer-rules.pro similarity index 100% rename from synchronizer/consumer-rules.pro rename to synchronizer/core/consumer-rules.pro diff --git a/synchronizer/core/gradle.properties b/synchronizer/core/gradle.properties new file mode 100644 index 00000000..69539852 --- /dev/null +++ b/synchronizer/core/gradle.properties @@ -0,0 +1,4 @@ +POM_ARTIFACT_ID=synchronizer-core +POM_NAME=Data syncronizer core +POM_PACKAGING=aar +VERSION_NAME=LOCAL \ No newline at end of file diff --git a/synchronizer/proguard-rules.pro b/synchronizer/core/proguard-rules.pro similarity index 100% rename from synchronizer/proguard-rules.pro rename to synchronizer/core/proguard-rules.pro diff --git a/synchronizer/src/main/AndroidManifest.xml b/synchronizer/core/src/main/AndroidManifest.xml similarity index 100% rename from synchronizer/src/main/AndroidManifest.xml rename to synchronizer/core/src/main/AndroidManifest.xml diff --git a/synchronizer/src/main/java/com/urlaunched/synchronizer/BaseSynchronizer.kt b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/BaseSynchronizer.kt similarity index 98% rename from synchronizer/src/main/java/com/urlaunched/synchronizer/BaseSynchronizer.kt rename to synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/BaseSynchronizer.kt index 02b439cb..d2f71317 100644 --- a/synchronizer/src/main/java/com/urlaunched/synchronizer/BaseSynchronizer.kt +++ b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/BaseSynchronizer.kt @@ -1,9 +1,10 @@ -package com.urlaunched.synchronizer +package com.urlaunched.synchronizer.core import androidx.paging.PagingData import androidx.paging.cachedIn import androidx.paging.filter import androidx.paging.map +import com.urlaunched.android.synchonizer.model.Synchronizable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow diff --git a/synchronizer/gradle.properties b/synchronizer/gradle.properties deleted file mode 100644 index 298a5bcc..00000000 --- a/synchronizer/gradle.properties +++ /dev/null @@ -1,4 +0,0 @@ -POM_ARTIFACT_ID=synchronizer -POM_NAME=Data syncronizer -POM_PACKAGING=aar -VERSION_NAME=LOCAL \ No newline at end of file diff --git a/synchronizer/model/.gitignore b/synchronizer/model/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/synchronizer/model/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/synchronizer/model/build.gradle b/synchronizer/model/build.gradle new file mode 100644 index 00000000..3f3fc472 --- /dev/null +++ b/synchronizer/model/build.gradle @@ -0,0 +1,16 @@ +plugins { + id 'java-library' + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.ktlint) + alias(libs.plugins.metalava) + alias(libs.plugins.mavenPublish) +} +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") +} \ No newline at end of file diff --git a/synchronizer/model/gradle.properties b/synchronizer/model/gradle.properties new file mode 100644 index 00000000..d47881de --- /dev/null +++ b/synchronizer/model/gradle.properties @@ -0,0 +1,4 @@ +POM_ARTIFACT_ID=synchronizer-model +POM_NAME=Synchronizer models +POM_PACKAGING=aar +VERSION_NAME=LOCAL \ No newline at end of file diff --git a/synchronizer/model/src/main/java/com/urlaunched/android/synchonizer/model/Synchronizable.kt b/synchronizer/model/src/main/java/com/urlaunched/android/synchonizer/model/Synchronizable.kt new file mode 100644 index 00000000..60d9790f --- /dev/null +++ b/synchronizer/model/src/main/java/com/urlaunched/android/synchonizer/model/Synchronizable.kt @@ -0,0 +1,5 @@ +package com.urlaunched.android.synchonizer.model + +interface Synchronizable { + val id: T +} \ No newline at end of file diff --git a/synchronizer/src/main/java/com/urlaunched/synchronizer/Synchronizable.kt b/synchronizer/src/main/java/com/urlaunched/synchronizer/Synchronizable.kt deleted file mode 100644 index c62d71a5..00000000 --- a/synchronizer/src/main/java/com/urlaunched/synchronizer/Synchronizable.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.urlaunched.synchronizer - -interface Synchronizable { - val id: T -} \ No newline at end of file From c7c9530a24c554b65c5bb010f8074434a0b53e35 Mon Sep 17 00:00:00 2001 From: Nazar Rusnak Date: Mon, 22 Sep 2025 14:03:46 +0300 Subject: [PATCH 02/11] Remove base from name, make an object --- .../core/{BaseSynchronizer.kt => DataSynchronizer.kt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/{BaseSynchronizer.kt => DataSynchronizer.kt} (99%) diff --git a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/BaseSynchronizer.kt b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt similarity index 99% rename from synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/BaseSynchronizer.kt rename to synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt index d2f71317..277538dc 100644 --- a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/BaseSynchronizer.kt +++ b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt @@ -18,7 +18,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlin.reflect.KClass -abstract class BaseSynchronizer { +object DataSynchronizer { val updateModel: MutableSharedFlow> = MutableSharedFlow() val deletedModel: MutableSharedFlow> = MutableSharedFlow() val cancelDeleteModel: MutableSharedFlow> = MutableSharedFlow() From 9ce9554ed5512c5df629310a97a0d7ede849f772 Mon Sep 17 00:00:00 2001 From: Nazar Rusnak Date: Mon, 22 Sep 2025 15:56:02 +0300 Subject: [PATCH 03/11] Implement data synchronizer --- .../synchronizer/core/DataSynchronizer.kt | 293 +++++++++--------- 1 file changed, 153 insertions(+), 140 deletions(-) diff --git a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt index 277538dc..b883b3e1 100644 --- a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt +++ b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt @@ -7,64 +7,77 @@ import androidx.paging.map import com.urlaunched.android.synchonizer.model.Synchronizable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kotlin.reflect.KClass object DataSynchronizer { val updateModel: MutableSharedFlow> = MutableSharedFlow() val deletedModel: MutableSharedFlow> = MutableSharedFlow() val cancelDeleteModel: MutableSharedFlow> = MutableSharedFlow() - inline fun , reified R : Synchronizable, ID> Flow>.synchronize( + inline fun , reified RELATED : Synchronizable, ID> Flow>.synchronizeRelatedModel( viewModelScope: CoroutineScope, - relatedType: KClass, - crossinline map: (T, R) -> T = { t, _ -> t } - ): Flow> { - val localMap = MutableStateFlow>(mapOf()) + crossinline mapRelatedToActual: (ACTUAL, RELATED) -> ACTUAL, + crossinline mapActualToRelated: (ACTUAL) -> RELATED + ): Flow> { + val accumulatedUpdates = MutableStateFlow>(mapOf()) + val updatedData = mutableSetOf() viewModelScope.launch(Dispatchers.IO) { updateModel - .filter { it is T || it is R } - .collectLatest { item -> - item.let { updatedItem -> - updatedItem as Synchronizable - - localMap.update { currentData -> - val updatedMap = currentData.toMutableMap() - val currentItem = updatedMap[updatedItem.id] - - val mappedItem = if (updatedItem is R) { - currentItem?.let { current -> - map(current, updatedItem) - } - } else { - updatedItem as T + .filterIsInstance>() + .filter { it is RELATED } + .collectLatest { modelUpdate -> + accumulatedUpdates.update { currentData -> + val currentItem = currentData[modelUpdate.id] + + val mappedItem = if (modelUpdate is RELATED) { + currentItem?.let { current -> + mapRelatedToActual(current, modelUpdate) } + } else { + modelUpdate as ACTUAL + } - mappedItem?.let { updatedMap[updatedItem.id] = it } - updatedMap + if (mappedItem != null) { + currentData + Pair(mappedItem.id, mappedItem) + } else { + currentData } } } } return combine( - this.cachedIn(viewModelScope), - localMap - ) { pagingData, localMapValue -> + this + .onEach { + updatedData.clear() + } + .map { pagingData -> + pagingData.map { item -> + if (!updatedData.contains(item.id)) { + updateModel.emit(mapActualToRelated(item)) + updatedData.add(item.id) + } + + item + } + } + .cachedIn(viewModelScope), + accumulatedUpdates + ) { pagingData, updates -> pagingData.map { item -> - val updatedMap = localMap.value.toMutableMap() - updatedMap[item.id] = item - localMap.value = updatedMap - localMapValue[item.id] ?: item + updates[item.id] ?: item } }.cachedIn(viewModelScope) } @@ -72,160 +85,160 @@ object DataSynchronizer { inline fun , ID> Flow>.synchronize( viewModelScope: CoroutineScope ): Flow> { - val updatedLocalMap = MutableStateFlow>(mapOf()) - val deletedLocalMap = MutableStateFlow>(mapOf()) + val accumulatedUpdates = MutableStateFlow>(mapOf()) + val accumulatedDeletions = MutableStateFlow>(mapOf()) + val updatedData = mutableSetOf() viewModelScope.launch(Dispatchers.IO) { - updateModel.filter { it is T }.collectLatest { item -> - item.let { updatedItem -> - updatedItem as Synchronizable - - updatedLocalMap.update { currentData -> - val updatedMap = currentData.toMutableMap() - - updatedMap[updatedItem.id] = updatedItem as T - updatedMap + updateModel + .filterIsInstance() + .collectLatest { modelUpdate -> + accumulatedUpdates.update { currentData -> + currentData + Pair(modelUpdate.id, modelUpdate) } } - } } viewModelScope.launch(Dispatchers.IO) { - deletedModel.filter { it is T }.collectLatest { item -> - item.let { deletedItem -> - deletedItem as Synchronizable - - deletedLocalMap.update { currentData -> - val deletedMap = currentData.toMutableMap() - - deletedMap[deletedItem.id] = deletedItem as T - deletedMap + deletedModel + .filterIsInstance() + .collectLatest { modelDeletion -> + accumulatedDeletions.update { currentData -> + currentData + Pair(modelDeletion.id, modelDeletion) } } - } } viewModelScope.launch(Dispatchers.IO) { - cancelDeleteModel.filter { it is T }.collectLatest { item -> - item.let { cancelDeleteItem -> - cancelDeleteItem as Synchronizable - - deletedLocalMap.update { currentData -> - val deletedMap = currentData.toMutableMap() - deletedMap.remove(cancelDeleteItem.id) - deletedMap + cancelDeleteModel + .filterIsInstance() + .collectLatest { modelDeletionCancellation -> + accumulatedDeletions.update { currentData -> + currentData - modelDeletionCancellation.id } } - } } return combine( - this.cachedIn(viewModelScope), - updatedLocalMap - ) { pagingData, updatedData -> + this + .onEach { + updatedData.clear() + } + .map { pagingData -> + pagingData.map { item -> + if (!updatedData.contains(item.id)) { + updateModel.emit(item) + updatedData.add(item.id) + } + + item + } + } + .cachedIn(viewModelScope), + accumulatedUpdates, + accumulatedDeletions + ) { pagingData, updatedData, deletedData -> pagingData + .filter { item -> + !deletedData.contains(item.id) + } .map { item -> - val updatedMap = updatedLocalMap.value.toMutableMap() - updatedMap[item.id] = item - updatedLocalMap.value = updatedMap updatedData[item.id] ?: item } - }.combine(deletedLocalMap) { pagingData, deletedData -> - pagingData.filter { item -> - deletedData.containsKey(item.id).not() - } }.cachedIn(viewModelScope) } - inline fun , reified R : Synchronizable, ID> List.synchronize( - viewModelScope: CoroutineScope, - relatedType: KClass, - crossinline map: (T, R) -> T = { t, _ -> t } - ): List { - val localMap = MutableStateFlow>(mapOf()) - - viewModelScope.launch(Dispatchers.IO) { - updateModel - .filter { it is T || it is R } - .collectLatest { item -> - item.let { updatedItem -> - updatedItem as Synchronizable - - localMap.update { currentData -> - val updatedMap = currentData.toMutableMap() - val currentItem = updatedMap[updatedItem.id] - - val mappedItem = if (updatedItem is R) { - currentItem?.let { current -> - map(current, updatedItem) + suspend inline fun , ID> synchronizeList( + crossinline listGetter: () -> List, + crossinline onListUpdate: (List) -> Unit + ) { + coroutineScope { + launch { + updateModel + .filterIsInstance() + .collectLatest { updatedData -> + val currentList = listGetter() + + onListUpdate( + currentList.map { currentItem -> + if (currentItem.id == updatedData.id) { + updatedData + } else { + currentItem } - } else { - updatedItem as T } - - mappedItem?.let { updatedMap[updatedItem.id] = it } - updatedMap - } + ) } - } - } + } - return localMap.value.values.toList() - } + launch { + val deletedItemsPositions = mutableMapOf() - inline fun , ID> List.synchronize(viewModelScope: CoroutineScope): List { - val localMap = MutableStateFlow>(mapOf()) + launch { + deletedModel + .filterIsInstance() + .collectLatest { deletedModel -> + val currentList = listGetter() - viewModelScope.launch(Dispatchers.IO) { - updateModel - .filter { it is T } - .collectLatest { item -> - item.let { updatedItem -> - updatedItem as Synchronizable + currentList.indexOf(deletedModel).takeIf { it != -1 }?.let { index -> + deletedItemsPositions.put(deletedModel.id, index) - localMap.update { currentData -> - val updatedMap = currentData.toMutableMap() + onListUpdate( + currentList.filter { item -> item.id != deletedModel.id } + ) + } + } + } - val mappedItem = updatedItem as T - mappedItem.let { updatedMap[updatedItem.id] = it } - updatedMap + launch { + cancelDeleteModel + .filterIsInstance() + .collectLatest { cancelDeleteModel -> + val currentList = listGetter() + + deletedItemsPositions[cancelDeleteModel.id]?.let { index -> + deletedItemsPositions.remove(cancelDeleteModel.id) + onListUpdate( + currentList.toMutableList().apply { add(index, cancelDeleteModel) } + ) + } } - } } + } } - - return localMap.value.values.toList() } - suspend inline fun , reified R : Synchronizable, ID> T.synchronize( - crossinline onUpdate: (T) -> Unit, - relatedType: KClass, - crossinline map: (R) -> T + suspend inline fun , reified RELATED : Synchronizable, ID> synchronizeRelatedModelList( + crossinline listGetter: () -> List, + crossinline mapRelatedToActual: (ACTUAL, RELATED) -> ACTUAL, + crossinline onListUpdate: (List) -> Unit ) { updateModel - .filterNotNull() - .filter { it is T || it is R } - .filter { it.id == this.id } - .collectLatest { updatedItem -> - when (updatedItem) { - is R -> { - onUpdate(map(updatedItem)) - } - - is T -> { - onUpdate(updatedItem) + .filterIsInstance() + .collectLatest { updatedData -> + val currentList = listGetter() + + onListUpdate( + currentList.map { currentItem -> + if (currentItem.id == updatedData.id) { + mapRelatedToActual(currentItem, updatedData) + } else { + currentItem + } } - } + ) } } - suspend inline fun , ID> T.synchronize(crossinline onUpdate: (T) -> Unit) { + suspend inline fun , ID> synchronizeModel( + id: ID, + crossinline onUpdate: (MODEL) -> Unit + ) { updateModel - .filterNotNull() - .filter { it is T } - .filter { it.id == this.id } + .filterIsInstance() + .filter { it.id == id } .collectLatest { updatedItem -> - onUpdate(updatedItem as T) + onUpdate(updatedItem) } } From 53fe66d87fb74efc8b3a1626e698c06173ff54fe Mon Sep 17 00:00:00 2001 From: Nazar Rusnak Date: Mon, 22 Sep 2025 16:00:43 +0300 Subject: [PATCH 04/11] Avoid hardcoding dispatchers --- .../synchronizer/core/DataSynchronizer.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt index b883b3e1..db41d254 100644 --- a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt +++ b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt @@ -5,6 +5,7 @@ import androidx.paging.cachedIn import androidx.paging.filter import androidx.paging.map import com.urlaunched.android.synchonizer.model.Synchronizable +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope @@ -28,12 +29,13 @@ object DataSynchronizer { inline fun , reified RELATED : Synchronizable, ID> Flow>.synchronizeRelatedModel( viewModelScope: CoroutineScope, crossinline mapRelatedToActual: (ACTUAL, RELATED) -> ACTUAL, - crossinline mapActualToRelated: (ACTUAL) -> RELATED + crossinline mapActualToRelated: (ACTUAL) -> RELATED, + coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO ): Flow> { val accumulatedUpdates = MutableStateFlow>(mapOf()) val updatedData = mutableSetOf() - viewModelScope.launch(Dispatchers.IO) { + viewModelScope.launch(coroutineDispatcher) { updateModel .filterIsInstance>() .filter { it is RELATED } @@ -83,13 +85,14 @@ object DataSynchronizer { } inline fun , ID> Flow>.synchronize( - viewModelScope: CoroutineScope + viewModelScope: CoroutineScope, + coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO ): Flow> { val accumulatedUpdates = MutableStateFlow>(mapOf()) val accumulatedDeletions = MutableStateFlow>(mapOf()) val updatedData = mutableSetOf() - viewModelScope.launch(Dispatchers.IO) { + viewModelScope.launch(coroutineDispatcher) { updateModel .filterIsInstance() .collectLatest { modelUpdate -> @@ -99,7 +102,7 @@ object DataSynchronizer { } } - viewModelScope.launch(Dispatchers.IO) { + viewModelScope.launch(coroutineDispatcher) { deletedModel .filterIsInstance() .collectLatest { modelDeletion -> @@ -109,7 +112,7 @@ object DataSynchronizer { } } - viewModelScope.launch(Dispatchers.IO) { + viewModelScope.launch(coroutineDispatcher) { cancelDeleteModel .filterIsInstance() .collectLatest { modelDeletionCancellation -> From 2b0be9107c8a1b4a6e9284cd811c7bdb7e1e894f Mon Sep 17 00:00:00 2001 From: Nazar Rusnak Date: Mon, 22 Sep 2025 17:10:44 +0300 Subject: [PATCH 05/11] Fix related model paging update, move paging extensions outside of the object --- .../synchronizer/core/DataSynchronizer.kt | 238 +++++++++--------- 1 file changed, 113 insertions(+), 125 deletions(-) diff --git a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt index db41d254..f351578f 100644 --- a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt +++ b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt @@ -5,6 +5,9 @@ import androidx.paging.cachedIn import androidx.paging.filter import androidx.paging.map import com.urlaunched.android.synchonizer.model.Synchronizable +import com.urlaunched.synchronizer.core.DataSynchronizer.cancelDeleteModel +import com.urlaunched.synchronizer.core.DataSynchronizer.deletedModel +import com.urlaunched.synchronizer.core.DataSynchronizer.updateModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -26,131 +29,6 @@ object DataSynchronizer { val deletedModel: MutableSharedFlow> = MutableSharedFlow() val cancelDeleteModel: MutableSharedFlow> = MutableSharedFlow() - inline fun , reified RELATED : Synchronizable, ID> Flow>.synchronizeRelatedModel( - viewModelScope: CoroutineScope, - crossinline mapRelatedToActual: (ACTUAL, RELATED) -> ACTUAL, - crossinline mapActualToRelated: (ACTUAL) -> RELATED, - coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO - ): Flow> { - val accumulatedUpdates = MutableStateFlow>(mapOf()) - val updatedData = mutableSetOf() - - viewModelScope.launch(coroutineDispatcher) { - updateModel - .filterIsInstance>() - .filter { it is RELATED } - .collectLatest { modelUpdate -> - accumulatedUpdates.update { currentData -> - val currentItem = currentData[modelUpdate.id] - - val mappedItem = if (modelUpdate is RELATED) { - currentItem?.let { current -> - mapRelatedToActual(current, modelUpdate) - } - } else { - modelUpdate as ACTUAL - } - - if (mappedItem != null) { - currentData + Pair(mappedItem.id, mappedItem) - } else { - currentData - } - } - } - } - - return combine( - this - .onEach { - updatedData.clear() - } - .map { pagingData -> - pagingData.map { item -> - if (!updatedData.contains(item.id)) { - updateModel.emit(mapActualToRelated(item)) - updatedData.add(item.id) - } - - item - } - } - .cachedIn(viewModelScope), - accumulatedUpdates - ) { pagingData, updates -> - pagingData.map { item -> - updates[item.id] ?: item - } - }.cachedIn(viewModelScope) - } - - inline fun , ID> Flow>.synchronize( - viewModelScope: CoroutineScope, - coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO - ): Flow> { - val accumulatedUpdates = MutableStateFlow>(mapOf()) - val accumulatedDeletions = MutableStateFlow>(mapOf()) - val updatedData = mutableSetOf() - - viewModelScope.launch(coroutineDispatcher) { - updateModel - .filterIsInstance() - .collectLatest { modelUpdate -> - accumulatedUpdates.update { currentData -> - currentData + Pair(modelUpdate.id, modelUpdate) - } - } - } - - viewModelScope.launch(coroutineDispatcher) { - deletedModel - .filterIsInstance() - .collectLatest { modelDeletion -> - accumulatedDeletions.update { currentData -> - currentData + Pair(modelDeletion.id, modelDeletion) - } - } - } - - viewModelScope.launch(coroutineDispatcher) { - cancelDeleteModel - .filterIsInstance() - .collectLatest { modelDeletionCancellation -> - accumulatedDeletions.update { currentData -> - currentData - modelDeletionCancellation.id - } - } - } - - return combine( - this - .onEach { - updatedData.clear() - } - .map { pagingData -> - pagingData.map { item -> - if (!updatedData.contains(item.id)) { - updateModel.emit(item) - updatedData.add(item.id) - } - - item - } - } - .cachedIn(viewModelScope), - accumulatedUpdates, - accumulatedDeletions - ) { pagingData, updatedData, deletedData -> - pagingData - .filter { item -> - !deletedData.contains(item.id) - } - .map { item -> - updatedData[item.id] ?: item - } - }.cachedIn(viewModelScope) - } - suspend inline fun , ID> synchronizeList( crossinline listGetter: () -> List, crossinline onListUpdate: (List) -> Unit @@ -256,4 +134,114 @@ object DataSynchronizer { suspend fun emitCancelDelete(value: Synchronizable<*>) { cancelDeleteModel.emit(value) } +} + +inline fun , reified RELATED : Synchronizable, ID> Flow>.synchronizeRelatedModel( + viewModelScope: CoroutineScope, + crossinline mapRelatedToActual: (ACTUAL, RELATED) -> ACTUAL, + crossinline mapActualToRelated: (ACTUAL) -> RELATED, + coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO +): Flow> { + val accumulatedUpdates = MutableStateFlow>(mapOf()) + val updatedData = mutableSetOf() + + viewModelScope.launch(coroutineDispatcher) { + updateModel + .filterIsInstance() + .collectLatest { modelUpdate -> + accumulatedUpdates.update { currentData -> + currentData + Pair(modelUpdate.id, modelUpdate) + } + } + } + + return combine( + this + .onEach { + updatedData.clear() + } + .map { pagingData -> + pagingData.map { item -> + if (!updatedData.contains(item.id)) { + updateModel.emit(mapActualToRelated(item)) + updatedData.add(item.id) + } + + item + } + } + .cachedIn(viewModelScope), + accumulatedUpdates + ) { pagingData, updates -> + pagingData.map { item -> + updates[item.id]?.let { mapRelatedToActual(item, it) } ?: item + } + }.cachedIn(viewModelScope) +} + +inline fun , ID> Flow>.synchronize( + viewModelScope: CoroutineScope, + coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO +): Flow> { + val accumulatedUpdates = MutableStateFlow>(mapOf()) + val accumulatedDeletions = MutableStateFlow>(mapOf()) + val updatedData = mutableSetOf() + + viewModelScope.launch(coroutineDispatcher) { + updateModel + .filterIsInstance() + .collectLatest { modelUpdate -> + accumulatedUpdates.update { currentData -> + currentData + Pair(modelUpdate.id, modelUpdate) + } + } + } + + viewModelScope.launch(coroutineDispatcher) { + deletedModel + .filterIsInstance() + .collectLatest { modelDeletion -> + accumulatedDeletions.update { currentData -> + currentData + Pair(modelDeletion.id, modelDeletion) + } + } + } + + viewModelScope.launch(coroutineDispatcher) { + cancelDeleteModel + .filterIsInstance() + .collectLatest { modelDeletionCancellation -> + accumulatedDeletions.update { currentData -> + currentData - modelDeletionCancellation.id + } + } + } + + return combine( + this + .onEach { + updatedData.clear() + } + .map { pagingData -> + pagingData.map { item -> + if (!updatedData.contains(item.id)) { + updateModel.emit(item) + updatedData.add(item.id) + } + + item + } + } + .cachedIn(viewModelScope), + accumulatedUpdates, + accumulatedDeletions + ) { pagingData, updatedData, deletedData -> + pagingData + .filter { item -> + !deletedData.contains(item.id) + } + .map { item -> + updatedData[item.id] ?: item + } + }.cachedIn(viewModelScope) } \ No newline at end of file From 8140c92242d95de4b8833a63f70a9f232477995c Mon Sep 17 00:00:00 2001 From: Nazar Rusnak Date: Mon, 22 Sep 2025 17:21:17 +0300 Subject: [PATCH 06/11] Update shared flow configuration --- .../synchronizer/core/DataSynchronizer.kt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt index f351578f..7de49618 100644 --- a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt +++ b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt @@ -11,6 +11,7 @@ import com.urlaunched.synchronizer.core.DataSynchronizer.updateModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -25,9 +26,18 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch object DataSynchronizer { - val updateModel: MutableSharedFlow> = MutableSharedFlow() - val deletedModel: MutableSharedFlow> = MutableSharedFlow() - val cancelDeleteModel: MutableSharedFlow> = MutableSharedFlow() + val updateModel: MutableSharedFlow> = MutableSharedFlow( + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + val deletedModel: MutableSharedFlow> = MutableSharedFlow( + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + val cancelDeleteModel: MutableSharedFlow> = MutableSharedFlow( + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) suspend inline fun , ID> synchronizeList( crossinline listGetter: () -> List, From 61abf38c6318529ba0a8ad605e933ba58cd585cf Mon Sep 17 00:00:00 2001 From: Nazar Rusnak Date: Tue, 23 Sep 2025 09:30:58 +0300 Subject: [PATCH 07/11] Update coroutine scope naming --- .../synchronizer/core/DataSynchronizer.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt index 7de49618..2551f753 100644 --- a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt +++ b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt @@ -147,7 +147,7 @@ object DataSynchronizer { } inline fun , reified RELATED : Synchronizable, ID> Flow>.synchronizeRelatedModel( - viewModelScope: CoroutineScope, + coroutineScope: CoroutineScope, crossinline mapRelatedToActual: (ACTUAL, RELATED) -> ACTUAL, crossinline mapActualToRelated: (ACTUAL) -> RELATED, coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO @@ -155,7 +155,7 @@ inline fun , reified RELATED : Synchronizabl val accumulatedUpdates = MutableStateFlow>(mapOf()) val updatedData = mutableSetOf() - viewModelScope.launch(coroutineDispatcher) { + coroutineScope.launch(coroutineDispatcher) { updateModel .filterIsInstance() .collectLatest { modelUpdate -> @@ -180,24 +180,24 @@ inline fun , reified RELATED : Synchronizabl item } } - .cachedIn(viewModelScope), + .cachedIn(coroutineScope), accumulatedUpdates ) { pagingData, updates -> pagingData.map { item -> updates[item.id]?.let { mapRelatedToActual(item, it) } ?: item } - }.cachedIn(viewModelScope) + }.cachedIn(coroutineScope) } inline fun , ID> Flow>.synchronize( - viewModelScope: CoroutineScope, + coroutineScope: CoroutineScope, coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO ): Flow> { val accumulatedUpdates = MutableStateFlow>(mapOf()) val accumulatedDeletions = MutableStateFlow>(mapOf()) val updatedData = mutableSetOf() - viewModelScope.launch(coroutineDispatcher) { + coroutineScope.launch(coroutineDispatcher) { updateModel .filterIsInstance() .collectLatest { modelUpdate -> @@ -207,7 +207,7 @@ inline fun , ID> Flow>.synchronize( } } - viewModelScope.launch(coroutineDispatcher) { + coroutineScope.launch(coroutineDispatcher) { deletedModel .filterIsInstance() .collectLatest { modelDeletion -> @@ -217,7 +217,7 @@ inline fun , ID> Flow>.synchronize( } } - viewModelScope.launch(coroutineDispatcher) { + coroutineScope.launch(coroutineDispatcher) { cancelDeleteModel .filterIsInstance() .collectLatest { modelDeletionCancellation -> @@ -242,7 +242,7 @@ inline fun , ID> Flow>.synchronize( item } } - .cachedIn(viewModelScope), + .cachedIn(coroutineScope), accumulatedUpdates, accumulatedDeletions ) { pagingData, updatedData, deletedData -> @@ -253,5 +253,5 @@ inline fun , ID> Flow>.synchronize( .map { item -> updatedData[item.id] ?: item } - }.cachedIn(viewModelScope) + }.cachedIn(coroutineScope) } \ No newline at end of file From 9edcf38e3849747436d96bf11909a20b5b379c9e Mon Sep 17 00:00:00 2001 From: Nazar Rusnak Date: Tue, 23 Sep 2025 10:34:22 +0300 Subject: [PATCH 08/11] Add the ability to get updates flow --- .../com/urlaunched/synchronizer/core/DataSynchronizer.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt index 2551f753..b2bc9fcb 100644 --- a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt +++ b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt @@ -133,6 +133,8 @@ object DataSynchronizer { } } + inline fun > getUpdatesFlow() = updateModel.filterIsInstance() + suspend fun emitUpdate(value: Synchronizable<*>) { updateModel.emit(value) } @@ -149,7 +151,7 @@ object DataSynchronizer { inline fun , reified RELATED : Synchronizable, ID> Flow>.synchronizeRelatedModel( coroutineScope: CoroutineScope, crossinline mapRelatedToActual: (ACTUAL, RELATED) -> ACTUAL, - crossinline mapActualToRelated: (ACTUAL) -> RELATED, + crossinline mapActualToRelated: (ACTUAL) -> RELATED?, coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO ): Flow> { val accumulatedUpdates = MutableStateFlow>(mapOf()) @@ -173,7 +175,7 @@ inline fun , reified RELATED : Synchronizabl .map { pagingData -> pagingData.map { item -> if (!updatedData.contains(item.id)) { - updateModel.emit(mapActualToRelated(item)) + mapActualToRelated(item)?.let { updateModel.emit(it) } updatedData.add(item.id) } From 90277abc2cc2c0204fe27e20a442066305c7273e Mon Sep 17 00:00:00 2001 From: Nazar Rusnak Date: Tue, 23 Sep 2025 16:09:03 +0300 Subject: [PATCH 09/11] Export api --- synchronizer/core/api/current.api | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 synchronizer/core/api/current.api diff --git a/synchronizer/core/api/current.api b/synchronizer/core/api/current.api new file mode 100644 index 00000000..94db4573 --- /dev/null +++ b/synchronizer/core/api/current.api @@ -0,0 +1,27 @@ +// Signature format: 4.0 +package com.urlaunched.synchronizer.core { + + public final class DataSynchronizer { + method public suspend Object? emitCancelDelete(com.urlaunched.android.synchonizer.model.Synchronizable value, kotlin.coroutines.Continuation); + method public suspend Object? emitDelete(com.urlaunched.android.synchonizer.model.Synchronizable value, kotlin.coroutines.Continuation); + method public suspend Object? emitUpdate(com.urlaunched.android.synchonizer.model.Synchronizable value, kotlin.coroutines.Continuation); + method public kotlinx.coroutines.flow.MutableSharedFlow> getCancelDeleteModel(); + method public kotlinx.coroutines.flow.MutableSharedFlow> getDeletedModel(); + method public kotlinx.coroutines.flow.MutableSharedFlow> getUpdateModel(); + method public inline > getUpdatesFlow(); + method public suspend inline , ID> void synchronizeList(kotlin.jvm.functions.Function0> listGetter, kotlin.jvm.functions.Function1,? extends kotlin.Unit> onListUpdate); + method public suspend inline , ID> void synchronizeModel(ID id, kotlin.jvm.functions.Function1 onUpdate); + method public suspend inline , reified RELATED extends com.urlaunched.android.synchonizer.model.Synchronizable, ID> void synchronizeRelatedModelList(kotlin.jvm.functions.Function0> listGetter, kotlin.jvm.functions.Function2 mapRelatedToActual, kotlin.jvm.functions.Function1,? extends kotlin.Unit> onListUpdate); + property public final kotlinx.coroutines.flow.MutableSharedFlow> cancelDeleteModel; + property public final kotlinx.coroutines.flow.MutableSharedFlow> deletedModel; + property public final kotlinx.coroutines.flow.MutableSharedFlow> updateModel; + field public static final com.urlaunched.synchronizer.core.DataSynchronizer INSTANCE; + } + + public final class DataSynchronizerKt { + method public static inline , ID> kotlinx.coroutines.flow.Flow> synchronize(kotlinx.coroutines.flow.Flow>, kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher coroutineDispatcher); + method public static inline , reified RELATED extends com.urlaunched.android.synchonizer.model.Synchronizable, ID> kotlinx.coroutines.flow.Flow> synchronizeRelatedModel(kotlinx.coroutines.flow.Flow>, kotlinx.coroutines.CoroutineScope coroutineScope, kotlin.jvm.functions.Function2 mapRelatedToActual, kotlin.jvm.functions.Function1 mapActualToRelated, optional kotlinx.coroutines.CoroutineDispatcher coroutineDispatcher); + } + +} + From eda7a21db796a5cdac7abe136c8ed46f364bbf2c Mon Sep 17 00:00:00 2001 From: Nazar Rusnak Date: Tue, 23 Sep 2025 16:10:37 +0300 Subject: [PATCH 10/11] Align generics naming --- synchronizer/core/api/current.api | 4 +-- .../synchronizer/core/DataSynchronizer.kt | 26 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/synchronizer/core/api/current.api b/synchronizer/core/api/current.api index 94db4573..847f3212 100644 --- a/synchronizer/core/api/current.api +++ b/synchronizer/core/api/current.api @@ -9,7 +9,7 @@ package com.urlaunched.synchronizer.core { method public kotlinx.coroutines.flow.MutableSharedFlow> getDeletedModel(); method public kotlinx.coroutines.flow.MutableSharedFlow> getUpdateModel(); method public inline > getUpdatesFlow(); - method public suspend inline , ID> void synchronizeList(kotlin.jvm.functions.Function0> listGetter, kotlin.jvm.functions.Function1,? extends kotlin.Unit> onListUpdate); + method public suspend inline , ID> void synchronizeList(kotlin.jvm.functions.Function0> listGetter, kotlin.jvm.functions.Function1,? extends kotlin.Unit> onListUpdate); method public suspend inline , ID> void synchronizeModel(ID id, kotlin.jvm.functions.Function1 onUpdate); method public suspend inline , reified RELATED extends com.urlaunched.android.synchonizer.model.Synchronizable, ID> void synchronizeRelatedModelList(kotlin.jvm.functions.Function0> listGetter, kotlin.jvm.functions.Function2 mapRelatedToActual, kotlin.jvm.functions.Function1,? extends kotlin.Unit> onListUpdate); property public final kotlinx.coroutines.flow.MutableSharedFlow> cancelDeleteModel; @@ -19,7 +19,7 @@ package com.urlaunched.synchronizer.core { } public final class DataSynchronizerKt { - method public static inline , ID> kotlinx.coroutines.flow.Flow> synchronize(kotlinx.coroutines.flow.Flow>, kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher coroutineDispatcher); + method public static inline , ID> kotlinx.coroutines.flow.Flow> synchronize(kotlinx.coroutines.flow.Flow>, kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher coroutineDispatcher); method public static inline , reified RELATED extends com.urlaunched.android.synchonizer.model.Synchronizable, ID> kotlinx.coroutines.flow.Flow> synchronizeRelatedModel(kotlinx.coroutines.flow.Flow>, kotlinx.coroutines.CoroutineScope coroutineScope, kotlin.jvm.functions.Function2 mapRelatedToActual, kotlin.jvm.functions.Function1 mapActualToRelated, optional kotlinx.coroutines.CoroutineDispatcher coroutineDispatcher); } diff --git a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt index b2bc9fcb..6b5a8beb 100644 --- a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt +++ b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt @@ -39,14 +39,14 @@ object DataSynchronizer { onBufferOverflow = BufferOverflow.DROP_OLDEST ) - suspend inline fun , ID> synchronizeList( - crossinline listGetter: () -> List, - crossinline onListUpdate: (List) -> Unit + suspend inline fun , ID> synchronizeList( + crossinline listGetter: () -> List, + crossinline onListUpdate: (List) -> Unit ) { coroutineScope { launch { updateModel - .filterIsInstance() + .filterIsInstance() .collectLatest { updatedData -> val currentList = listGetter() @@ -67,7 +67,7 @@ object DataSynchronizer { launch { deletedModel - .filterIsInstance() + .filterIsInstance() .collectLatest { deletedModel -> val currentList = listGetter() @@ -83,7 +83,7 @@ object DataSynchronizer { launch { cancelDeleteModel - .filterIsInstance() + .filterIsInstance() .collectLatest { cancelDeleteModel -> val currentList = listGetter() @@ -191,17 +191,17 @@ inline fun , reified RELATED : Synchronizabl }.cachedIn(coroutineScope) } -inline fun , ID> Flow>.synchronize( +inline fun , ID> Flow>.synchronize( coroutineScope: CoroutineScope, coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO -): Flow> { - val accumulatedUpdates = MutableStateFlow>(mapOf()) - val accumulatedDeletions = MutableStateFlow>(mapOf()) +): Flow> { + val accumulatedUpdates = MutableStateFlow>(mapOf()) + val accumulatedDeletions = MutableStateFlow>(mapOf()) val updatedData = mutableSetOf() coroutineScope.launch(coroutineDispatcher) { updateModel - .filterIsInstance() + .filterIsInstance() .collectLatest { modelUpdate -> accumulatedUpdates.update { currentData -> currentData + Pair(modelUpdate.id, modelUpdate) @@ -211,7 +211,7 @@ inline fun , ID> Flow>.synchronize( coroutineScope.launch(coroutineDispatcher) { deletedModel - .filterIsInstance() + .filterIsInstance() .collectLatest { modelDeletion -> accumulatedDeletions.update { currentData -> currentData + Pair(modelDeletion.id, modelDeletion) @@ -221,7 +221,7 @@ inline fun , ID> Flow>.synchronize( coroutineScope.launch(coroutineDispatcher) { cancelDeleteModel - .filterIsInstance() + .filterIsInstance() .collectLatest { modelDeletionCancellation -> accumulatedDeletions.update { currentData -> currentData - modelDeletionCancellation.id From 6e0e1e33fdf0db485d8403e2a44794363475244c Mon Sep 17 00:00:00 2001 From: Nazar Rusnak Date: Tue, 30 Sep 2025 17:38:00 +0300 Subject: [PATCH 11/11] Update naming --- synchronizer/core/api/current.api | 16 ++--- ...taSynchronizer.kt => ModelSynchronizer.kt} | 58 +++++++++---------- 2 files changed, 37 insertions(+), 37 deletions(-) rename synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/{DataSynchronizer.kt => ModelSynchronizer.kt} (82%) diff --git a/synchronizer/core/api/current.api b/synchronizer/core/api/current.api index 847f3212..47ee9972 100644 --- a/synchronizer/core/api/current.api +++ b/synchronizer/core/api/current.api @@ -1,26 +1,26 @@ // Signature format: 4.0 package com.urlaunched.synchronizer.core { - public final class DataSynchronizer { + public final class ModelSynchronizer { method public suspend Object? emitCancelDelete(com.urlaunched.android.synchonizer.model.Synchronizable value, kotlin.coroutines.Continuation); method public suspend Object? emitDelete(com.urlaunched.android.synchonizer.model.Synchronizable value, kotlin.coroutines.Continuation); method public suspend Object? emitUpdate(com.urlaunched.android.synchonizer.model.Synchronizable value, kotlin.coroutines.Continuation); method public kotlinx.coroutines.flow.MutableSharedFlow> getCancelDeleteModel(); method public kotlinx.coroutines.flow.MutableSharedFlow> getDeletedModel(); method public kotlinx.coroutines.flow.MutableSharedFlow> getUpdateModel(); - method public inline > getUpdatesFlow(); - method public suspend inline , ID> void synchronizeList(kotlin.jvm.functions.Function0> listGetter, kotlin.jvm.functions.Function1,? extends kotlin.Unit> onListUpdate); - method public suspend inline , ID> void synchronizeModel(ID id, kotlin.jvm.functions.Function1 onUpdate); - method public suspend inline , reified RELATED extends com.urlaunched.android.synchonizer.model.Synchronizable, ID> void synchronizeRelatedModelList(kotlin.jvm.functions.Function0> listGetter, kotlin.jvm.functions.Function2 mapRelatedToActual, kotlin.jvm.functions.Function1,? extends kotlin.Unit> onListUpdate); + method public inline > getUpdatesFlow(); + method public suspend inline , ID> void synchronize(ID id, kotlin.jvm.functions.Function1 onUpdate); + method public suspend inline , ID> void synchronize(kotlin.jvm.functions.Function0> getList, kotlin.jvm.functions.Function1,? extends kotlin.Unit> onUpdate); + method public suspend inline , reified RELATED extends com.urlaunched.android.synchonizer.model.Synchronizable, ID> void synchronizeRelated(kotlin.jvm.functions.Function0> getList, kotlin.jvm.functions.Function2 map, kotlin.jvm.functions.Function1,? extends kotlin.Unit> onUpdate); property public final kotlinx.coroutines.flow.MutableSharedFlow> cancelDeleteModel; property public final kotlinx.coroutines.flow.MutableSharedFlow> deletedModel; property public final kotlinx.coroutines.flow.MutableSharedFlow> updateModel; - field public static final com.urlaunched.synchronizer.core.DataSynchronizer INSTANCE; + field public static final com.urlaunched.synchronizer.core.ModelSynchronizer INSTANCE; } - public final class DataSynchronizerKt { + public final class ModelSynchronizerKt { method public static inline , ID> kotlinx.coroutines.flow.Flow> synchronize(kotlinx.coroutines.flow.Flow>, kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher coroutineDispatcher); - method public static inline , reified RELATED extends com.urlaunched.android.synchonizer.model.Synchronizable, ID> kotlinx.coroutines.flow.Flow> synchronizeRelatedModel(kotlinx.coroutines.flow.Flow>, kotlinx.coroutines.CoroutineScope coroutineScope, kotlin.jvm.functions.Function2 mapRelatedToActual, kotlin.jvm.functions.Function1 mapActualToRelated, optional kotlinx.coroutines.CoroutineDispatcher coroutineDispatcher); + method public static inline , reified RELATED extends com.urlaunched.android.synchonizer.model.Synchronizable, ID> kotlinx.coroutines.flow.Flow> synchronizeRelated(kotlinx.coroutines.flow.Flow>, kotlinx.coroutines.CoroutineScope coroutineScope, kotlin.jvm.functions.Function2 mapRelatedToActual, kotlin.jvm.functions.Function1 mapActualToRelated, optional kotlinx.coroutines.CoroutineDispatcher coroutineDispatcher); } } diff --git a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/ModelSynchronizer.kt similarity index 82% rename from synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt rename to synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/ModelSynchronizer.kt index 6b5a8beb..7589332b 100644 --- a/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/DataSynchronizer.kt +++ b/synchronizer/core/src/main/java/com/urlaunched/synchronizer/core/ModelSynchronizer.kt @@ -5,9 +5,9 @@ import androidx.paging.cachedIn import androidx.paging.filter import androidx.paging.map import com.urlaunched.android.synchonizer.model.Synchronizable -import com.urlaunched.synchronizer.core.DataSynchronizer.cancelDeleteModel -import com.urlaunched.synchronizer.core.DataSynchronizer.deletedModel -import com.urlaunched.synchronizer.core.DataSynchronizer.updateModel +import com.urlaunched.synchronizer.core.ModelSynchronizer.cancelDeleteModel +import com.urlaunched.synchronizer.core.ModelSynchronizer.deletedModel +import com.urlaunched.synchronizer.core.ModelSynchronizer.updateModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -25,7 +25,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -object DataSynchronizer { +object ModelSynchronizer { val updateModel: MutableSharedFlow> = MutableSharedFlow( extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST @@ -39,18 +39,18 @@ object DataSynchronizer { onBufferOverflow = BufferOverflow.DROP_OLDEST ) - suspend inline fun , ID> synchronizeList( - crossinline listGetter: () -> List, - crossinline onListUpdate: (List) -> Unit + suspend inline fun , ID> synchronize( + crossinline getList: () -> List, + crossinline onUpdate: (updatedList: List) -> Unit ) { coroutineScope { launch { updateModel - .filterIsInstance() + .filterIsInstance() .collectLatest { updatedData -> - val currentList = listGetter() + val currentList = getList() - onListUpdate( + onUpdate( currentList.map { currentItem -> if (currentItem.id == updatedData.id) { updatedData @@ -67,14 +67,14 @@ object DataSynchronizer { launch { deletedModel - .filterIsInstance() + .filterIsInstance() .collectLatest { deletedModel -> - val currentList = listGetter() + val currentList = getList() currentList.indexOf(deletedModel).takeIf { it != -1 }?.let { index -> deletedItemsPositions.put(deletedModel.id, index) - onListUpdate( + onUpdate( currentList.filter { item -> item.id != deletedModel.id } ) } @@ -83,13 +83,13 @@ object DataSynchronizer { launch { cancelDeleteModel - .filterIsInstance() + .filterIsInstance() .collectLatest { cancelDeleteModel -> - val currentList = listGetter() + val currentList = getList() deletedItemsPositions[cancelDeleteModel.id]?.let { index -> deletedItemsPositions.remove(cancelDeleteModel.id) - onListUpdate( + onUpdate( currentList.toMutableList().apply { add(index, cancelDeleteModel) } ) } @@ -99,20 +99,20 @@ object DataSynchronizer { } } - suspend inline fun , reified RELATED : Synchronizable, ID> synchronizeRelatedModelList( - crossinline listGetter: () -> List, - crossinline mapRelatedToActual: (ACTUAL, RELATED) -> ACTUAL, - crossinline onListUpdate: (List) -> Unit + suspend inline fun , reified RELATED : Synchronizable, ID> synchronizeRelated( + crossinline getList: () -> List, + crossinline map: (ACTUAL, RELATED) -> ACTUAL, + crossinline onUpdate: (updatedList: List) -> Unit ) { updateModel .filterIsInstance() .collectLatest { updatedData -> - val currentList = listGetter() + val currentList = getList() - onListUpdate( + onUpdate( currentList.map { currentItem -> if (currentItem.id == updatedData.id) { - mapRelatedToActual(currentItem, updatedData) + map(currentItem, updatedData) } else { currentItem } @@ -121,19 +121,19 @@ object DataSynchronizer { } } - suspend inline fun , ID> synchronizeModel( + suspend inline fun , ID> synchronize( id: ID, - crossinline onUpdate: (MODEL) -> Unit + crossinline onUpdate: (ACTUAL) -> Unit ) { updateModel - .filterIsInstance() + .filterIsInstance() .filter { it.id == id } .collectLatest { updatedItem -> onUpdate(updatedItem) } } - inline fun > getUpdatesFlow() = updateModel.filterIsInstance() + inline fun > getUpdatesFlow() = updateModel.filterIsInstance() suspend fun emitUpdate(value: Synchronizable<*>) { updateModel.emit(value) @@ -148,10 +148,10 @@ object DataSynchronizer { } } -inline fun , reified RELATED : Synchronizable, ID> Flow>.synchronizeRelatedModel( +inline fun , reified RELATED : Synchronizable, ID> Flow>.synchronizeRelated( coroutineScope: CoroutineScope, crossinline mapRelatedToActual: (ACTUAL, RELATED) -> ACTUAL, - crossinline mapActualToRelated: (ACTUAL) -> RELATED?, + crossinline mapActualToRelated: (ACTUAL) -> RELATED, coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO ): Flow> { val accumulatedUpdates = MutableStateFlow>(mapOf())