Replace StateViewModel with explicit backing fields (#3763)

Assisted-by: Claude:claude-opus-5
This commit is contained in:
AntsyLich
2026-08-15 01:53:06 +06:00
committed by GitHub
parent d00efa4bfc
commit b1efee47f5
21 changed files with 184 additions and 127 deletions
@@ -8,7 +8,6 @@ import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchViewModel
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.source.service.SourceManager
@@ -46,7 +45,7 @@ class MigrateSearchViewModel(
init {
viewModelScope.launch {
val manga = getManga.await(mangaId)!!
mutableState.update {
updateState {
it.copy(
from = manga,
searchQuery = manga.title,
@@ -6,6 +6,7 @@ import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
@@ -23,7 +24,9 @@ import eu.kanade.domain.track.interactor.AddTracks
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.util.removeCovers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.firstOrNull
@@ -31,7 +34,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import mihon.core.viewmodel.StateViewModel
import tachiyomi.core.common.preference.CheckboxState
import tachiyomi.core.common.preference.mapAsCheckboxState
import tachiyomi.core.common.util.lang.launchIO
@@ -68,7 +70,10 @@ class BrowseSourceViewModel(
private val updateManga: UpdateManga = Injekt.get(),
private val addTracks: AddTracks = Injekt.get(),
getIncognitoState: GetIncognitoState = Injekt.get(),
) : StateViewModel<BrowseSourceViewModel.State>(State(Listing.valueOf(listingQuery))) {
) : ViewModel() {
val state: StateFlow<BrowseSourceViewModel.State>
field = MutableStateFlow<BrowseSourceViewModel.State>(State(Listing.valueOf(listingQuery)))
companion object {
val SOURCE_ID_KEY = CreationExtras.Key<Long>()
@@ -89,7 +94,7 @@ class BrowseSourceViewModel(
val source = sourceManager.getOrStub(sourceId)
init {
mutableState.update {
state.update {
var query: String? = null
var listing = it.listing
@@ -142,15 +147,15 @@ class BrowseSourceViewModel(
}
fun resetFilters() {
mutableState.update { it.copy(filters = source.getFilterList()) }
state.update { it.copy(filters = source.getFilterList()) }
}
fun setListing(listing: Listing) {
mutableState.update { it.copy(listing = listing, toolbarQuery = null) }
state.update { it.copy(listing = listing, toolbarQuery = null) }
}
fun setFilters(filters: FilterList) {
mutableState.update {
state.update {
it.copy(
filters = filters,
)
@@ -161,7 +166,7 @@ class BrowseSourceViewModel(
val input = state.value.listing as? Listing.Search
?: Listing.Search(query = null, filters = source.getFilterList())
mutableState.update {
state.update {
it.copy(
listing = input.copy(
query = query ?: input.query,
@@ -201,7 +206,7 @@ class BrowseSourceViewModel(
}
}
mutableState.update {
state.update {
val listing = if (genreExists) {
Listing.Search(query = null, filters = defaultFilters)
} else {
@@ -310,11 +315,11 @@ class BrowseSourceViewModel(
}
fun setDialog(dialog: Dialog?) {
mutableState.update { it.copy(dialog = dialog) }
state.update { it.copy(dialog = dialog) }
}
fun setToolbarQuery(query: String?) {
mutableState.update { it.copy(toolbarQuery = query) }
state.update { it.copy(toolbarQuery = query) }
}
sealed class Listing(open val query: String?, open val filters: FilterList) {
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.browse.source.globalsearch
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.produceState
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.ExtensionManager
@@ -11,13 +12,14 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mihon.core.viewmodel.StateViewModel
import mihon.domain.manga.model.toDomainManga
import tachiyomi.core.common.preference.toggle
import tachiyomi.core.common.util.lang.launchIO
@@ -37,7 +39,16 @@ abstract class SearchViewModel(
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val preferences: SourcePreferences = Injekt.get(),
) : StateViewModel<SearchViewModel.State>(initialState) {
) : ViewModel() {
val state: StateFlow<State>
field = MutableStateFlow<State>(initialState)
// Subclasses can't touch the backing field (Kotlin forbids a visibility modifier on one),
// so state writes from them go through here.
protected fun updateState(function: (State) -> State) {
state.update(function)
}
private val coroutineDispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher()
private var searchJob: Job? = null
@@ -61,8 +72,8 @@ abstract class SearchViewModel(
init {
viewModelScope.launch {
preferences.globalSearchFilterState.changes().collectLatest { state ->
mutableState.update { it.copy(onlyShowHasResults = state) }
preferences.globalSearchFilterState.changes().collectLatest { onlyShowHasResults ->
state.update { it.copy(onlyShowHasResults = onlyShowHasResults) }
}
}
}
@@ -104,11 +115,11 @@ abstract class SearchViewModel(
}
fun updateSearchQuery(query: String?) {
mutableState.update { it.copy(searchQuery = query) }
state.update { it.copy(searchQuery = query) }
}
fun setSourceFilter(filter: SourceFilter) {
mutableState.update { it.copy(sourceFilter = filter) }
state.update { it.copy(sourceFilter = filter) }
search()
}
@@ -178,7 +189,7 @@ abstract class SearchViewModel(
}
private fun updateItems(items: Map<Source, SearchItemResult>) {
mutableState.update {
state.update {
it.copy(
items = items
.toSortedMap(sortComparator(items)),
@@ -193,12 +204,12 @@ abstract class SearchViewModel(
fun setMigrateDialog(currentId: Long, target: Manga) {
viewModelScope.launchIO {
val current = getManga.await(currentId) ?: return@launchIO
mutableState.update { it.copy(dialog = Dialog.Migrate(target, current)) }
state.update { it.copy(dialog = Dialog.Migrate(target, current)) }
}
}
fun clearDialog() {
mutableState.update { it.copy(dialog = null) }
state.update { it.copy(dialog = null) }
}
@Immutable
@@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.ui.deeplink
import androidx.compose.runtime.Immutable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
@@ -9,8 +10,9 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.online.ResolvableSource
import eu.kanade.tachiyomi.source.online.UriType
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import mihon.core.viewmodel.StateViewModel
import mihon.domain.manga.model.toDomainManga
import mihon.domain.source.interactor.UpdateMangaFromRemote
import tachiyomi.core.common.util.lang.launchIO
@@ -28,7 +30,10 @@ class DeepLinkViewModel(
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
private val getChapterByUrlAndMangaId: GetChapterByUrlAndMangaId = Injekt.get(),
private val updateMangaFromRemote: UpdateMangaFromRemote = Injekt.get(),
) : StateViewModel<DeepLinkViewModel.State>(State.Loading) {
) : ViewModel() {
val state: StateFlow<DeepLinkViewModel.State>
field = MutableStateFlow<DeepLinkViewModel.State>(State.Loading)
companion object {
val QUERY_KEY = CreationExtras.Key<String>()
@@ -58,7 +63,7 @@ class DeepLinkViewModel(
null
}
mutableState.update {
state.update {
if (manga == null) {
State.NoResults
} else {
@@ -8,6 +8,7 @@ import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue
import androidx.compose.ui.util.fastAny
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
@@ -41,6 +42,8 @@ import eu.kanade.tachiyomi.util.chapter.getNextUnread
import eu.kanade.tachiyomi.util.removeCovers
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
@@ -50,7 +53,6 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import logcat.LogPriority
import mihon.core.viewmodel.StateViewModel
import mihon.domain.chapter.interactor.FilterChaptersForDownload
import mihon.domain.source.interactor.UpdateMangaFromRemote
import tachiyomi.core.common.i18n.stringResource
@@ -116,7 +118,10 @@ class MangaViewModel(
private val filterChaptersForDownload: FilterChaptersForDownload = Injekt.get(),
private val updateMangaFromRemote: UpdateMangaFromRemote = Injekt.get(),
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
) : StateViewModel<MangaViewModel.State>(State.Loading) {
) : ViewModel() {
val state: StateFlow<State>
field = MutableStateFlow<State>(State.Loading)
companion object {
val MANGA_ID_KEY = CreationExtras.Key<Long>()
@@ -168,7 +173,7 @@ class MangaViewModel(
* Helper function to update the UI state only if it's currently in success state
*/
private inline fun updateSuccessState(func: (State.Success) -> State.Success) {
mutableState.update {
state.update {
when (it) {
State.Loading -> it
is State.Success -> func(it)
@@ -228,7 +233,7 @@ class MangaViewModel(
val needRefreshChapter = chapters.isEmpty()
// Show what we have earlier
mutableState.update {
state.update {
State.Success(
manga = manga,
source = Injekt.get<SourceManager>().getOrStub(manga.source),
@@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.compose.viewModel
@@ -13,8 +14,9 @@ import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.manga.MangaNotesScreen
import eu.kanade.presentation.util.Screen
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import mihon.core.viewmodel.StateViewModel
import tachiyomi.core.common.util.lang.launchNonCancellable
import tachiyomi.domain.manga.interactor.UpdateMangaNotes
import tachiyomi.domain.manga.model.Manga
@@ -46,7 +48,10 @@ class MangaNotesScreen(
class Model(
private val manga: Manga,
private val updateMangaNotes: UpdateMangaNotes = Injekt.get(),
) : StateViewModel<State>(State(manga, manga.notes)) {
) : ViewModel() {
val state: StateFlow<State>
field = MutableStateFlow<State>(State(manga, manga.notes))
companion object {
val MANGA_KEY = CreationExtras.Key<Manga>()
@@ -63,7 +68,7 @@ class MangaNotesScreen(
fun updateNotes(content: String) {
if (content == state.value.notes) return
mutableState.update {
state.update {
it.copy(notes = content)
}
@@ -59,6 +59,8 @@ import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -68,7 +70,6 @@ import kotlinx.coroutines.launch
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import logcat.LogPriority
import mihon.core.viewmodel.StateViewModel
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.util.lang.launchNonCancellable
import tachiyomi.core.common.util.lang.withIOContext
@@ -205,7 +206,10 @@ data class TrackInfoDialogHomeScreen(
private val mangaId: Long,
private val sourceId: Long,
private val getTracks: GetTracks = Injekt.get(),
) : StateViewModel<Model.State>(State()) {
) : ViewModel() {
val state: StateFlow<Model.State>
field = MutableStateFlow<Model.State>(State())
companion object {
val MANGA_ID_KEY = CreationExtras.Key<Long>()
@@ -231,7 +235,7 @@ data class TrackInfoDialogHomeScreen(
.catch { logcat(LogPriority.ERROR, it) }
.distinctUntilChanged()
.map { it.mapToTrackItem() }
.collectLatest { trackItems -> mutableState.update { it.copy(trackItems = trackItems) } }
.collectLatest { trackItems -> state.update { it.copy(trackItems = trackItems) } }
}
}
@@ -324,7 +328,10 @@ private data class TrackStatusSelectorScreen(
class Model(
private val track: Track,
private val tracker: Tracker,
) : StateViewModel<Model.State>(State(track.status)) {
) : ViewModel() {
val state: StateFlow<Model.State>
field = MutableStateFlow<Model.State>(State(track.status))
companion object {
val TRACK_KEY = CreationExtras.Key<Track>()
@@ -345,7 +352,7 @@ private data class TrackStatusSelectorScreen(
}
fun setSelection(selection: Long) {
mutableState.update { it.copy(selection = selection) }
state.update { it.copy(selection = selection) }
}
fun setStatus() {
@@ -393,7 +400,10 @@ private data class TrackChapterSelectorScreen(
class Model(
private val track: Track,
private val tracker: Tracker,
) : StateViewModel<Model.State>(State(track.lastChapterRead.toInt())) {
) : ViewModel() {
val state: StateFlow<Model.State>
field = MutableStateFlow<Model.State>(State(track.lastChapterRead.toInt()))
companion object {
val TRACK_KEY = CreationExtras.Key<Track>()
@@ -419,7 +429,7 @@ private data class TrackChapterSelectorScreen(
}
fun setSelection(selection: Int) {
mutableState.update { it.copy(selection = selection) }
state.update { it.copy(selection = selection) }
}
fun setChapter() {
@@ -467,7 +477,10 @@ private data class TrackScoreSelectorScreen(
class Model(
private val track: Track,
private val tracker: Tracker,
) : StateViewModel<Model.State>(State(tracker.displayScore(track))) {
) : ViewModel() {
val state: StateFlow<Model.State>
field = MutableStateFlow<Model.State>(State(tracker.displayScore(track)))
companion object {
val TRACK_KEY = CreationExtras.Key<Track>()
@@ -488,7 +501,7 @@ private data class TrackScoreSelectorScreen(
}
fun setSelection(selection: String) {
mutableState.update { it.copy(selection = selection) }
state.update { it.copy(selection = selection) }
}
fun setScore() {
@@ -788,7 +801,10 @@ data class TrackerSearchScreen(
private val currentUrl: String?,
initialQuery: String,
private val tracker: Tracker,
) : StateViewModel<Model.State>(State()) {
) : ViewModel() {
val state: StateFlow<Model.State>
field = MutableStateFlow<Model.State>(State())
companion object {
val MANGA_ID_KEY = CreationExtras.Key<Long>()
@@ -820,7 +836,7 @@ data class TrackerSearchScreen(
fun trackingSearch(query: String) {
viewModelScope.launch {
// To show loading state
mutableState.update { it.copy(queryResult = null, selected = null) }
state.update { it.copy(queryResult = null, selected = null) }
val result = withIOContext {
try {
@@ -830,7 +846,7 @@ data class TrackerSearchScreen(
Result.failure(e)
}
}
mutableState.update { oldState ->
state.update { oldState ->
oldState.copy(
queryResult = result,
selected = result.getOrNull()?.find { it.tracking_url == currentUrl },
@@ -844,7 +860,7 @@ data class TrackerSearchScreen(
}
fun updateSelection(selected: TrackSearch) {
mutableState.update { it.copy(selected = selected) }
state.update { it.copy(selected = selected) }
}
@Immutable
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.more
import android.content.Context
import android.content.Intent
import androidx.compose.runtime.Immutable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
@@ -17,10 +18,11 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.storage.saveTo
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import logcat.LogPriority
import mihon.core.viewmodel.StateViewModel
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.system.logcat
import uy.kohesive.injekt.Injekt
@@ -32,7 +34,10 @@ class NewUpdateScreenModel(
private val downloadLink: String,
private val context: Context = Injekt.get(),
private val network: NetworkHelper = Injekt.get(),
) : StateViewModel<NewUpdateScreenModel.State>(State(changelogInfo = changelogInfo)) {
) : ViewModel() {
val state: StateFlow<NewUpdateScreenModel.State>
field = MutableStateFlow<NewUpdateScreenModel.State>(State(changelogInfo = changelogInfo))
private val apkFile: File
get() = File(context.externalCacheDir, "update.apk")
@@ -43,16 +48,16 @@ class NewUpdateScreenModel(
if (downloadJob?.isActive == true) return
downloadJob = viewModelScope.launch {
mutableState.update { it.copy(downloadProgress = 0, stage = Stage.Downloading) }
state.update { it.copy(downloadProgress = 0, stage = Stage.Downloading) }
try {
withIOContext { downloadApk() }
mutableState.update { it.copy(downloadProgress = 100, stage = Stage.Downloaded) }
state.update { it.copy(downloadProgress = 100, stage = Stage.Downloaded) }
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
apkFile.delete()
mutableState.update { it.copy(stage = Stage.Failed) }
state.update { it.copy(stage = Stage.Failed) }
}
}
}
@@ -71,7 +76,7 @@ class NewUpdateScreenModel(
if (progress > savedProgress && currentTime - 200 > lastTick) {
savedProgress = progress
lastTick = currentTime
mutableState.update { it.copy(downloadProgress = progress) }
state.update { it.copy(downloadProgress = progress) }
}
}
}
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.stats
import androidx.compose.ui.util.fastDistinctBy
import androidx.compose.ui.util.fastFilter
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import eu.kanade.core.util.fastCountNot
import eu.kanade.presentation.more.stats.StatsScreenState
@@ -9,8 +10,9 @@ import eu.kanade.presentation.more.stats.data.StatsData
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.track.TrackerManager
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import mihon.core.viewmodel.StateViewModel
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.domain.history.interactor.GetTotalReadDuration
import tachiyomi.domain.library.model.LibraryManga
@@ -32,7 +34,10 @@ class StatsViewModel(
private val getTracks: GetTracks = Injekt.get(),
private val preferences: LibraryPreferences = Injekt.get(),
private val trackerManager: TrackerManager = Injekt.get(),
) : StateViewModel<StatsScreenState>(StatsScreenState.Loading) {
) : ViewModel() {
val state: StateFlow<StatsScreenState>
field = MutableStateFlow<StatsScreenState>(StatsScreenState.Loading)
private val loggedInTrackers by lazy { trackerManager.loggedInTrackers() }
@@ -73,7 +78,7 @@ class StatsViewModel(
trackerCount = loggedInTrackers.size,
)
mutableState.update {
state.update {
StatsScreenState.Success(
overview = overviewStatData,
titles = titlesStatData,
@@ -2,17 +2,16 @@ package eu.kanade.tachiyomi.ui.webview
import android.content.Context
import androidx.core.net.toUri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import eu.kanade.presentation.more.stats.StatsScreenState
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toShareIntent
import eu.kanade.tachiyomi.util.system.toast
import logcat.LogPriority
import mihon.core.viewmodel.StateViewModel
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.source.service.SourceManager
@@ -23,7 +22,7 @@ class WebViewViewModel(
val sourceId: Long?,
private val sourceManager: SourceManager = Injekt.get(),
private val network: NetworkHelper = Injekt.get(),
) : StateViewModel<StatsScreenState>(StatsScreenState.Loading) {
) : ViewModel() {
companion object {
val SOURCE_ID_KEY = CreationExtras.Key<Long?>()