Reputation: 25
I'm starting to get into Jetpack Compose and am trying to implement a list where you can swipe to delete an item but you can also restore it via a Snackbar action. In the old XML-Layout-based world I managed to do this before but I cannot get it to work in Jetpack Compose.
I can add a swipe-to-delete behaviour that successfully deletes the item and I can also show the Snackbar afterwards. Inserting the item back into the list technically seems to work as well, but the problem seems to be that rememberSwipeToDismissBoxState()
remembers that I swiped this item and keeps its state. So when the item is re-inserted, the if
statement that checks the state to see if the item should be deleted just immediately triggers and deletes the item again.
The item itself is an entity saved in an SQLite database that I access via Room.
Does anybody know how or even if such functionalilty can be implemented using Compose?
I currently have the following code:
The list:
@Composable
fun WatchHistory(
viewModel: WatchHistoryViewModel = viewModel(),
navigator: Navigator = Navigator.current,
) {
val entries = viewModel.entries.collectAsLazyPagingItems()
val coroutineScope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
topBar = {
TopAppBar(
title = {
Text(
text = stringResource(id = R.string.watch_history_title),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.basicMarquee()
)
},
navigationIcon = {
IconButton(onClick = { navigator.back() }) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = null
)
}
},
actions = { WatchHistoryMenu() },
)
},
content = { contentPadding ->
LazyColumn(contentPadding = contentPadding) {
items(count = entries.itemCount, key = entries.itemKey { it.historyId }) { index ->
entries.get(index)?.let { item ->
WatchHistoryItem(
item = item,
onDelete = { id ->
coroutineScope.launch {
viewModel.deleteEntry(id)
val result =
snackbarHostState.showSnackbar("Entry deleted", "Restore")
if (result == SnackbarResult.ActionPerformed) {
viewModel.undoDelete(item.toWatchHistoryEntry())
}
}
}
)
}
}
}
},
contentWindowInsets = WindowInsets.safeDrawing
)
}
The item:
@Composable
fun WatchHistoryItem(
item: WatchHistoryVideoEntity,
onDelete: (id: Int) -> Unit,
) {
val swipeToDismissState = rememberSwipeToDismissBoxState()
if (swipeToDismissState.currentValue == SwipeToDismissBoxValue.EndToStart) {
onDelete(item.historyId)
}
SwipeToDismissBox(
state = swipeToDismissState,
backgroundContent = {}
) {
VideoItem(item = item.video)
}
}
The entities. The actual table contains WatchHistoryEntryEntity
, the WatchHistoryVideoEntity
gets created via a join with another table.
@Entity(tableName = "watchHistory", indices = [Index("watchDate")])
@JsonClass(generateAdapter = true)
data class WatchHistoryEntryEntity(
@PrimaryKey(autoGenerate = true)
override val id: Int = 0,
@Json(name = "episodeId") val videoId: String,
val watchDate: Instant = Instant.now()
) : BaseEntity<Int>
data class WatchHistoryVideoEntity(
val historyId: Int,
val watchDate: Instant,
@Embedded val video: VideoEntity
) {
fun toWatchHistoryEntry() = WatchHistoryEntryEntity(
id = historyId,
videoId = video.id,
watchDate = watchDate,
)
}
Upvotes: 1
Views: 472