From 37ff0070ee1f9740010f8ace981ee910057f9f16 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 22 Jul 2025 13:38:30 -0400 Subject: [PATCH] Solves the hanging AccountViewModel scope when Amethyst is forced closed. --- .../amethyst/ui/screen/FeedViewModel.kt | 15 ++--- .../amethyst/ui/screen/UserFeedViewModel.kt | 18 ++--- .../ui/screen/loggedIn/AccountViewModel.kt | 65 ++++++++----------- .../profile/zaps/LnZapFeedViewModel.kt | 17 ++--- .../loggedIn/settings/StringFeedViewModel.kt | 18 ++--- 5 files changed, 48 insertions(+), 85 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt index 094f9486c..d71826742 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt @@ -30,7 +30,6 @@ import com.vitorpamplona.amethyst.ui.dal.FeedFilter import com.vitorpamplona.amethyst.ui.feeds.FeedContentState import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.launch @Stable @@ -48,22 +47,18 @@ abstract class FeedViewModel( override fun invalidateData(ignoreIfDoing: Boolean) = feedState.invalidateData(ignoreIfDoing) - private var collectorJob: Job? = null - init { Log.d("Init", "Starting new Model: ${this.javaClass.simpleName}") - collectorJob = - viewModelScope.launch(Dispatchers.Default) { - LocalCache.live.newEventBundles.collect { newNotes -> - Log.d("Rendering Metrics", "Update feeds: ${this@FeedViewModel.javaClass.simpleName} with ${newNotes.size}") - feedState.updateFeedWith(newNotes) - } + viewModelScope.launch(Dispatchers.Default) { + LocalCache.live.newEventBundles.collect { newNotes -> + Log.d("Rendering Metrics", "Update feeds: ${this@FeedViewModel.javaClass.simpleName} with ${newNotes.size}") + feedState.updateFeedWith(newNotes) } + } } override fun onCleared() { Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") - collectorJob?.cancel() super.onCleared() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt index 8ee6a446c..45e7da9b5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt @@ -36,7 +36,6 @@ import com.vitorpamplona.ammolite.relays.BundledUpdate import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @@ -99,26 +98,19 @@ open class UserFeedViewModel( } } - var collectorJob: Job? = null - init { Log.d("Init", "${this.javaClass.simpleName}") - collectorJob = - viewModelScope.launch(Dispatchers.IO) { - checkNotInMainThread() - - LocalCache.live.newEventBundles.collect { newNotes -> - checkNotInMainThread() - - invalidateData() - } + viewModelScope.launch(Dispatchers.Default) { + LocalCache.live.newEventBundles.collect { newNotes -> + Log.d("Rendering Metrics", "Update feeds: ${this@UserFeedViewModel.javaClass.simpleName} with ${newNotes.size}") + invalidateData() } + } } override fun onCleared() { Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") bundler.cancel() - collectorJob?.cancel() super.onCleared() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index ba4798ba2..1b07a1496 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -82,7 +82,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.CardFeedState import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.CombinedZap import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.tor.TorSettings -import com.vitorpamplona.ammolite.relays.BundledInsert import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryBaseEvent import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryReadingStateEvent @@ -135,7 +134,6 @@ import kotlinx.collections.immutable.toImmutableSet import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -1224,55 +1222,46 @@ class AccountViewModel( override fun create(modelClass: Class): T = AccountViewModel(accountSettings, settings, app) as T } - private var collectorJobNew: Job? = null - private var collectorJobDeleted: Job? = null - private val bundlerInsert = BundledInsert>(3000, Dispatchers.Default) - init { Log.d("Init", "AccountViewModel") - collectorJobNew = - viewModelScope.launch(Dispatchers.Default) { - feedStates.init() - // awaits for init to finish before starting to capture new events. - LocalCache.live.newEventBundles.collect { newNotes -> - if (isDebug) { - Log.d( - "Rendering Metrics", - "Update feeds ${this@AccountViewModel} for ${account.userProfile().toBestDisplayName()} with ${newNotes.size} new notes", - ) - } - logTime("AccountViewModel newEventBundle Update with ${newNotes.size} new notes") { - feedStates.updateFeedsWith(newNotes) - upgradeAttestations() - viewModelScope.launch(Dispatchers.Default) { - newNotesPreProcessor.runNew(newNotes) - } + viewModelScope.launch(Dispatchers.Default) { + feedStates.init() + // awaits for init to finish before starting to capture new events. + LocalCache.live.newEventBundles.collect { newNotes -> + if (isDebug) { + Log.d( + "Rendering Metrics", + "Update feeds ${this@AccountViewModel} for ${account.userProfile().toBestDisplayName()} with ${newNotes.size} new notes", + ) + } + logTime("AccountViewModel newEventBundle Update with ${newNotes.size} new notes") { + feedStates.updateFeedsWith(newNotes) + upgradeAttestations() + viewModelScope.launch(Dispatchers.Default) { + newNotesPreProcessor.runNew(newNotes) } } } + } - collectorJobDeleted = - viewModelScope.launch(Dispatchers.Default) { - LocalCache.live.deletedEventBundles.collect { newNotes -> - if (isDebug) { - Log.d( - "Rendering Metrics", - "Update feeds ${this@AccountViewModel} for ${account.userProfile().toBestDisplayName()} with ${newNotes.size} new notes", - ) - } - logTime("AccountViewModel deletedEventBundle Update with ${newNotes.size} new notes") { - newNotesPreProcessor.runDeleted(newNotes) - } + viewModelScope.launch(Dispatchers.Default) { + LocalCache.live.deletedEventBundles.collect { newNotes -> + if (isDebug) { + Log.d( + "Rendering Metrics", + "Update feeds ${this@AccountViewModel} for ${account.userProfile().toBestDisplayName()} with ${newNotes.size} new notes", + ) + } + logTime("AccountViewModel deletedEventBundle Update with ${newNotes.size} new notes") { + newNotesPreProcessor.runDeleted(newNotes) } } + } } override fun onCleared() { Log.d("Init", "AccountViewModel onCleared") feedStates.destroy() - bundlerInsert.cancel() - collectorJobNew?.cancel() - collectorJobDeleted?.cancel() super.onCleared() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/LnZapFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/LnZapFeedViewModel.kt index 39a4943ab..77b8e7e56 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/LnZapFeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/LnZapFeedViewModel.kt @@ -32,7 +32,6 @@ import com.vitorpamplona.ammolite.relays.BundledUpdate import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @@ -85,26 +84,22 @@ open class LnZapFeedViewModel( } } - var collectorJob: Job? = null - init { Log.d("Init", "${this.javaClass.simpleName}") - collectorJob = - viewModelScope.launch(Dispatchers.IO) { + viewModelScope.launch(Dispatchers.IO) { + checkNotInMainThread() + + LocalCache.live.newEventBundles.collect { newNotes -> checkNotInMainThread() - LocalCache.live.newEventBundles.collect { newNotes -> - checkNotInMainThread() - - invalidateData() - } + invalidateData() } + } } override fun onCleared() { Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") bundler.cancel() - collectorJob?.cancel() super.onCleared() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt index 7f1abdfa5..a98bd98ef 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt @@ -35,7 +35,6 @@ import com.vitorpamplona.ammolite.relays.BundledUpdate import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @@ -98,26 +97,19 @@ open class StringFeedViewModel( } } - var collectorJob: Job? = null - init { Log.d("Init", this.javaClass.simpleName) - collectorJob = - viewModelScope.launch(Dispatchers.IO) { - checkNotInMainThread() - - LocalCache.live.newEventBundles.collect { newNotes -> - checkNotInMainThread() - - invalidateData() - } + viewModelScope.launch(Dispatchers.Default) { + LocalCache.live.newEventBundles.collect { newNotes -> + Log.d("Rendering Metrics", "Update feeds: ${this@StringFeedViewModel.javaClass.simpleName} with ${newNotes.size}") + invalidateData() } + } } override fun onCleared() { Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") bundler.cancel() - collectorJob?.cancel() super.onCleared() } }