Solves the hanging AccountViewModel scope when Amethyst is forced closed.

This commit is contained in:
Vitor Pamplona
2025-07-22 13:38:30 -04:00
parent 0f13202f84
commit 37ff0070ee
5 changed files with 48 additions and 85 deletions

View File

@@ -30,7 +30,6 @@ import com.vitorpamplona.amethyst.ui.dal.FeedFilter
import com.vitorpamplona.amethyst.ui.feeds.FeedContentState import com.vitorpamplona.amethyst.ui.feeds.FeedContentState
import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Stable @Stable
@@ -48,22 +47,18 @@ abstract class FeedViewModel(
override fun invalidateData(ignoreIfDoing: Boolean) = feedState.invalidateData(ignoreIfDoing) override fun invalidateData(ignoreIfDoing: Boolean) = feedState.invalidateData(ignoreIfDoing)
private var collectorJob: Job? = null
init { init {
Log.d("Init", "Starting new Model: ${this.javaClass.simpleName}") Log.d("Init", "Starting new Model: ${this.javaClass.simpleName}")
collectorJob = viewModelScope.launch(Dispatchers.Default) {
viewModelScope.launch(Dispatchers.Default) { LocalCache.live.newEventBundles.collect { newNotes ->
LocalCache.live.newEventBundles.collect { newNotes -> Log.d("Rendering Metrics", "Update feeds: ${this@FeedViewModel.javaClass.simpleName} with ${newNotes.size}")
Log.d("Rendering Metrics", "Update feeds: ${this@FeedViewModel.javaClass.simpleName} with ${newNotes.size}") feedState.updateFeedWith(newNotes)
feedState.updateFeedWith(newNotes)
}
} }
}
} }
override fun onCleared() { override fun onCleared() {
Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") Log.d("Init", "OnCleared: ${this.javaClass.simpleName}")
collectorJob?.cancel()
super.onCleared() super.onCleared()
} }
} }

View File

@@ -36,7 +36,6 @@ import com.vitorpamplona.ammolite.relays.BundledUpdate
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -99,26 +98,19 @@ open class UserFeedViewModel(
} }
} }
var collectorJob: Job? = null
init { init {
Log.d("Init", "${this.javaClass.simpleName}") Log.d("Init", "${this.javaClass.simpleName}")
collectorJob = viewModelScope.launch(Dispatchers.Default) {
viewModelScope.launch(Dispatchers.IO) { LocalCache.live.newEventBundles.collect { newNotes ->
checkNotInMainThread() Log.d("Rendering Metrics", "Update feeds: ${this@UserFeedViewModel.javaClass.simpleName} with ${newNotes.size}")
invalidateData()
LocalCache.live.newEventBundles.collect { newNotes ->
checkNotInMainThread()
invalidateData()
}
} }
}
} }
override fun onCleared() { override fun onCleared() {
Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") Log.d("Init", "OnCleared: ${this.javaClass.simpleName}")
bundler.cancel() bundler.cancel()
collectorJob?.cancel()
super.onCleared() super.onCleared()
} }
} }

View File

@@ -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.screen.loggedIn.notifications.CombinedZap
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.tor.TorSettings 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.ephemChat.chat.RoomId
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryBaseEvent import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryBaseEvent
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryReadingStateEvent import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryReadingStateEvent
@@ -135,7 +134,6 @@ import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@@ -1224,55 +1222,46 @@ class AccountViewModel(
override fun <T : ViewModel> create(modelClass: Class<T>): T = AccountViewModel(accountSettings, settings, app) as T override fun <T : ViewModel> create(modelClass: Class<T>): T = AccountViewModel(accountSettings, settings, app) as T
} }
private var collectorJobNew: Job? = null
private var collectorJobDeleted: Job? = null
private val bundlerInsert = BundledInsert<Set<Note>>(3000, Dispatchers.Default)
init { init {
Log.d("Init", "AccountViewModel") Log.d("Init", "AccountViewModel")
collectorJobNew = viewModelScope.launch(Dispatchers.Default) {
viewModelScope.launch(Dispatchers.Default) { feedStates.init()
feedStates.init() // awaits for init to finish before starting to capture new events.
// awaits for init to finish before starting to capture new events. LocalCache.live.newEventBundles.collect { newNotes ->
LocalCache.live.newEventBundles.collect { newNotes -> if (isDebug) {
if (isDebug) { Log.d(
Log.d( "Rendering Metrics",
"Rendering Metrics", "Update feeds ${this@AccountViewModel} for ${account.userProfile().toBestDisplayName()} with ${newNotes.size} new notes",
"Update feeds ${this@AccountViewModel} for ${account.userProfile().toBestDisplayName()} with ${newNotes.size} new notes", )
) }
} logTime("AccountViewModel newEventBundle Update with ${newNotes.size} new notes") {
logTime("AccountViewModel newEventBundle Update with ${newNotes.size} new notes") { feedStates.updateFeedsWith(newNotes)
feedStates.updateFeedsWith(newNotes) upgradeAttestations()
upgradeAttestations() viewModelScope.launch(Dispatchers.Default) {
viewModelScope.launch(Dispatchers.Default) { newNotesPreProcessor.runNew(newNotes)
newNotesPreProcessor.runNew(newNotes)
}
} }
} }
} }
}
collectorJobDeleted = viewModelScope.launch(Dispatchers.Default) {
viewModelScope.launch(Dispatchers.Default) { LocalCache.live.deletedEventBundles.collect { newNotes ->
LocalCache.live.deletedEventBundles.collect { newNotes -> if (isDebug) {
if (isDebug) { Log.d(
Log.d( "Rendering Metrics",
"Rendering Metrics", "Update feeds ${this@AccountViewModel} for ${account.userProfile().toBestDisplayName()} with ${newNotes.size} new notes",
"Update feeds ${this@AccountViewModel} for ${account.userProfile().toBestDisplayName()} with ${newNotes.size} new notes", )
) }
} logTime("AccountViewModel deletedEventBundle Update with ${newNotes.size} new notes") {
logTime("AccountViewModel deletedEventBundle Update with ${newNotes.size} new notes") { newNotesPreProcessor.runDeleted(newNotes)
newNotesPreProcessor.runDeleted(newNotes)
}
} }
} }
}
} }
override fun onCleared() { override fun onCleared() {
Log.d("Init", "AccountViewModel onCleared") Log.d("Init", "AccountViewModel onCleared")
feedStates.destroy() feedStates.destroy()
bundlerInsert.cancel()
collectorJobNew?.cancel()
collectorJobDeleted?.cancel()
super.onCleared() super.onCleared()
} }

View File

@@ -32,7 +32,6 @@ import com.vitorpamplona.ammolite.relays.BundledUpdate
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -85,26 +84,22 @@ open class LnZapFeedViewModel(
} }
} }
var collectorJob: Job? = null
init { init {
Log.d("Init", "${this.javaClass.simpleName}") Log.d("Init", "${this.javaClass.simpleName}")
collectorJob = viewModelScope.launch(Dispatchers.IO) {
viewModelScope.launch(Dispatchers.IO) { checkNotInMainThread()
LocalCache.live.newEventBundles.collect { newNotes ->
checkNotInMainThread() checkNotInMainThread()
LocalCache.live.newEventBundles.collect { newNotes -> invalidateData()
checkNotInMainThread()
invalidateData()
}
} }
}
} }
override fun onCleared() { override fun onCleared() {
Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") Log.d("Init", "OnCleared: ${this.javaClass.simpleName}")
bundler.cancel() bundler.cancel()
collectorJob?.cancel()
super.onCleared() super.onCleared()
} }
} }

View File

@@ -35,7 +35,6 @@ import com.vitorpamplona.ammolite.relays.BundledUpdate
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -98,26 +97,19 @@ open class StringFeedViewModel(
} }
} }
var collectorJob: Job? = null
init { init {
Log.d("Init", this.javaClass.simpleName) Log.d("Init", this.javaClass.simpleName)
collectorJob = viewModelScope.launch(Dispatchers.Default) {
viewModelScope.launch(Dispatchers.IO) { LocalCache.live.newEventBundles.collect { newNotes ->
checkNotInMainThread() Log.d("Rendering Metrics", "Update feeds: ${this@StringFeedViewModel.javaClass.simpleName} with ${newNotes.size}")
invalidateData()
LocalCache.live.newEventBundles.collect { newNotes ->
checkNotInMainThread()
invalidateData()
}
} }
}
} }
override fun onCleared() { override fun onCleared() {
Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") Log.d("Init", "OnCleared: ${this.javaClass.simpleName}")
bundler.cancel() bundler.cancel()
collectorJob?.cancel()
super.onCleared() super.onCleared()
} }
} }