取消已经开始的活动?

huangapple go评论76阅读模式
英文:

How do I cancel an event after another one has started?

问题

我遇到一个问题,即在Snackbar出现后,FAB在Snackbar消息的时间段内不执行导航。如果我在Snackbar显示时多次点击它,它会堆叠并打开多个相同屏幕的实例(仅在Snackbar消失后)。如何在点击FAB时取消显示Snackbar并在任何时候保留FAB的功能?

TasksList.kt:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TasksListScreen(
    modifier: Modifier = Modifier,
    onNavigate: (UiEvent.Navigate) -> Unit,
    viewModel: TaskListViewModel = hiltViewModel()
) {
    val tasks = viewModel.tasks.collectAsState(initial = emptyList())
    val snackbarHostState = remember { SnackbarHostState() }
    LaunchedEffect(key1 = true) {
        viewModel.uiEvent.collect { event ->
            when (event) {
                is UiEvent.ShowSnackbar -> {
                    val result = snackbarHostState.showSnackbar(
                        message = event.message,
                        actionLabel = event.action,
                        duration = SnackbarDuration.Long,
                    )
                    if (result == SnackbarResult.ActionPerformed) {
                        viewModel.onEvent(TaskListEvent.OnUndoDeleteTask)
                    }
                }
                is UiEvent.Navigate -> onNavigate(event)
                else -> Unit
            }
        }
    }
    Scaffold(
        snackbarHost = {
            SnackbarHost(snackbarHostState) { data ->
                Snackbar(
                    shape = RoundedShapes.medium,
                    actionColor = MaterialTheme.colorScheme.primary,
                    contentColor = MaterialTheme.colorScheme.background,
                    snackbarData = data
                )
            }
        },
        floatingActionButton = {
            FloatingActionButton(
                shape = RoundedShapes.medium,
                onClick = { viewModel.onEvent(TaskListEvent.OnAddTask) },
                containerColor = MaterialTheme.colorScheme.primary,
                contentColor = MaterialTheme.colorScheme.background
            ) {
                Icon(
                    imageVector = Icons.Default.Add,
                    contentDescription = stringResource(R.string.fab_cd)
                )
            }
        },
        topBar = {
            TopAppBar(
                title = { Text(stringResource(R.string.app_name)) },
                colors = TopAppBarDefaults.topAppBarColors(
                    containerColor = MaterialTheme.colorScheme.primary,
                    titleContentColor = MaterialTheme.colorScheme.background
                )
            }
        },
    ) { padding ->
        LazyColumn(
            state = rememberLazyListState(),
            verticalArrangement = spacedBy(12.dp),
            contentPadding = PaddingValues(vertical = 16.dp),
            modifier = modifier
                .fillMaxSize()
                .padding(padding)
                .padding(horizontal = 16.dp),
        ) {

            items(items = tasks.value, key = { task -> task.hashCode() }) { task ->
                val currentTask by rememberUpdatedState(newValue = task)
                val dismissState = rememberDismissState(confirmValueChange = {
                    if (it == DismissValue.DismissedToStart) {
                        viewModel.onEvent(TaskListEvent.OnDeleteTask(currentTask))
                    }
                    true
                })
                SwipeToDismiss(state = dismissState,
                    directions = setOf(DismissDirection.EndToStart),
                    background = { },
                    dismissContent = {
                        TaskItem(
                            task = task, modifier = modifier
                        )
                    })
            }
        }
    }
}

TaskListViewModel:

@HiltViewModel
class TaskListViewModel @Inject constructor(private val repository: TaskRepositoryImplementation) :
    ViewModel() {

    val tasks = repository.getTasks()

    private val _uiEvent = Channel<UiEvent>()
    val uiEvent = _uiEvent.receiveAsFlow()

    private var deletedTask: Task? = null

    fun onEvent(event: TaskListEvent) {
        when (event) {
            is TaskListEvent.OnAddTask -> {
                sendUiEvent(UiEvent.Navigate(Routes.ADD_EDIT_TASK))
            }
            is TaskListEvent.OnEditClick -> {
                sendUiEvent(UiEvent.Navigate(Routes.ADD_EDIT_TASK + "?taskId=${event.task.id}"))
            }
            is TaskListEvent.OnDeleteTask -> {
                val context = TaskApp.instance?.context
                viewModelScope.launch(Dispatchers.IO) {
                    deletedTask = event.task
                    repository.deleteTask(event.task)
                    if (context != null) {
                        sendUiEvent(
                            UiEvent.ShowSnackbar(
                                message = context.getString(R.string.snackbar_deleted),
                                action = context.getString(R.string.snackbar_action)
                            )
                        )
                    }
                }

            }
            is TaskListEvent.OnUndoDeleteTask -> {
                deletedTask?.let { task ->
                    viewModelScope.launch {
                        repository.addTask(task)
                    }
                }

            }
        }
    }

    private fun sendUiEvent(event: UiEvent) {
        viewModelScope.launch(Dispatchers.IO) {
            _uiEvent.send(event)
        }
    }

}
英文:

I am having an issue, where after a Snackbar appears the FAB won't perform navigation for the period of the Snackbar message. If I click it multiple times when the Snackbar is displayed it will stack and open multiple instances of one screen (only after the Snackbar is gone). How do I cancel showing the Snackbar on click of the FAB and preserve the functionality of the FAB at all times?

TasksList.kt:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TasksListScreen(
modifier: Modifier = Modifier,
onNavigate: (UiEvent.Navigate) -&gt; Unit,
viewModel: TaskListViewModel = hiltViewModel()
) {
val tasks = viewModel.tasks.collectAsState(initial = emptyList())
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(key1 = true) {
viewModel.uiEvent.collect { event -&gt;
when (event) {
is UiEvent.ShowSnackbar -&gt; {
val result = snackbarHostState.showSnackbar(
message = event.message,
actionLabel = event.action,
duration = SnackbarDuration.Long,
)
if (result == SnackbarResult.ActionPerformed) {
viewModel.onEvent(TaskListEvent.OnUndoDeleteTask)
}
}
is UiEvent.Navigate -&gt; onNavigate(event)
else -&gt; Unit
}
}
}
Scaffold(
snackbarHost = {
SnackbarHost(snackbarHostState) { data -&gt;
Snackbar(
shape = RoundedShapes.medium,
actionColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.background,
snackbarData = data
)
}
},
floatingActionButton = {
FloatingActionButton(
shape = RoundedShapes.medium,
onClick = { viewModel.onEvent(TaskListEvent.OnAddTask) },
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.background
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = stringResource(R.string.fab_cd)
)
}
},
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.app_name)) },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = MaterialTheme.colorScheme.background
)
)
},
) { padding -&gt;
LazyColumn(
state = rememberLazyListState(),
verticalArrangement = spacedBy(12.dp),
contentPadding = PaddingValues(vertical = 16.dp),
modifier = modifier
.fillMaxSize()
.padding(padding)
.padding(horizontal = 16.dp),
) {
items(items = tasks.value, key = { task -&gt; task.hashCode() }) { task -&gt;
val currentTask by rememberUpdatedState(newValue = task)
val dismissState = rememberDismissState(confirmValueChange = {
if (it == DismissValue.DismissedToStart) {
viewModel.onEvent(TaskListEvent.OnDeleteTask(currentTask))
}
true
})
SwipeToDismiss(state = dismissState,
directions = setOf(DismissDirection.EndToStart),
background = { },
dismissContent = {
TaskItem(
task = task, modifier = modifier
)
})
}
}
}
}

TaskListViewModel:

@HiltViewModel
class TaskListViewModel @Inject constructor(private val repository: TaskRepositoryImplementation) :
ViewModel() {
val tasks = repository.getTasks()
private val _uiEvent = Channel&lt;UiEvent&gt;()
val uiEvent = _uiEvent.receiveAsFlow()
private var deletedTask: Task? = null
fun onEvent(event: TaskListEvent) {
when (event) {
is TaskListEvent.OnAddTask -&gt; {
sendUiEvent(UiEvent.Navigate(Routes.ADD_EDIT_TASK))
}
is TaskListEvent.OnEditClick -&gt; {
sendUiEvent(UiEvent.Navigate(Routes.ADD_EDIT_TASK + &quot;?taskId=${event.task.id}&quot;))
}
is TaskListEvent.OnDeleteTask -&gt; {
val context = TaskApp.instance?.context
viewModelScope.launch(Dispatchers.IO) {
deletedTask = event.task
repository.deleteTask(event.task)
if (context != null) {
sendUiEvent(
UiEvent.ShowSnackbar(
message = context.getString(R.string.snackbar_deleted),
action = context.getString(R.string.snackbar_action)
)
)
}
}
}
is TaskListEvent.OnUndoDeleteTask -&gt; {
deletedTask?.let { task -&gt;
viewModelScope.launch {
repository.addTask(task)
}
}
}
}
}
private fun sendUiEvent(event: UiEvent) {
viewModelScope.launch(Dispatchers.IO) {
_uiEvent.send(event)
}
}
}

答案1

得分: 2

以下是您要的代码的中文翻译:

// 创建SnackbarController类以创建可取消的Snackbar

// 受此存储库启发 [SnackbarController][1]

// 公开方法**cancelActiveJob**以取消可见的Snackbar

class SnackBarController(private val coroutineScope: CoroutineScope) {
    private var snackBarJob: Job? = null

    init {
        cancelActiveJob()
    }

    suspend fun showSnackBar(
        snackBarHostState: SnackbarHostState,
        message: String,
        actionLabel: String? = null,
        duration: SnackbarDuration
    ): SnackbarResult {
        var result: SnackbarResult = SnackbarResult.Dismissed
        if (snackBarJob == null) {
            snackBarJob = coroutineScope.launch {
                result = snackBarHostState.showSnackbar(
                    message = message,
                    actionLabel = actionLabel,
                    duration = duration
                )
                cancelActiveJob()
            }
        } else {
            cancelActiveJob()
            snackBarJob = coroutineScope.launch {
                result = snackBarHostState.showSnackbar(
                    message = message,
                    actionLabel = actionLabel,
                    duration = duration
                )
                cancelActiveJob()
            }
        }
        snackBarJob?.join()
        return result
    }

    fun cancelActiveJob() {
        snackBarJob?.let { job ->
            job.cancel()
            snackBarJob = Job()
        }
    }
}

TaskListScreen中:

val tasks = viewModel.tasks.collectAsState(initial = emptyList())
val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
val snackBarController = SnackBarController(coroutineScope)

LaunchedEffect(key1 = true) {
    viewModel.uiEvent.collectLatest { event ->
        when (event) {
            is UiEvent.ShowSnackbar -> {
                val result = snackBarController.showSnackBar(
                    message = event.message,
                    actionLabel = event.action,
                    duration = SnackbarDuration.Long,
                )
                if (result == SnackbarResult.ActionPerformed) {
                    viewModel.onEvent(TaskListEvent.OnUndoDeleteTask)
                }
            }
            is UiEvent.Navigate -> {
                // 如果要隐藏Snackbar
                snackBarController.cancelActiveJob()
                onNavigate(event)
            }
            else -> Unit
        }
    }
}

注意:如果多次调用snackbarController.showSnackBar(),它会替换当前可见的Snackbar。


<details>
<summary>英文:</summary>
Create this SnackbarController class to create a cancellable snackbar
Inspired by this repo [SnackbarController][1]
Expose the method **cancelActiveJob** to cancel the visible snackbar
class SnackBarController(private val coroutineScope: CoroutineScope) {
private var snackBarJob: Job? = null
init {
cancelActiveJob()
}
suspend fun showSnackBar(
snackBarHostState: SnackbarHostState,
message: String,
actionLabel: String? = null,
duration: SnackbarDuration
): SnackbarResult {
var result: SnackbarResult = SnackbarResult.Dismissed
if (snackBarJob == null) {
snackBarJob = coroutineScope.launch {
result = snackBarHostState.showSnackbar(
message = message,
actionLabel = actionLabel,
duration = duration
)
cancelActiveJob()
}
} else {
cancelActiveJob()
snackBarJob = coroutineScope.launch {
result = snackBarHostState.showSnackbar(
message = message,
actionLabel = actionLabel,
duration = duration
)
cancelActiveJob()
}
}
snackBarJob?.join()
return result
}
fun cancelActiveJob() {
snackBarJob?.let { job -&gt;
job.cancel()
snackBarJob = Job()
}
}
}
In the TaskListScreen
val tasks = viewModel.tasks.collectAsState(initial = emptyList())
val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
val snackBarController = SnackBarController(coroutineScope)
LaunchedEffect(key1 = true) {
viewModel.uiEvent.collectLatest{ event -&gt;
when (event) {
is UiEvent.ShowSnackbar -&gt; {
val result = snackBarController.showSnackBar(
message = event.message,
actionLabel = event.action,
duration = SnackbarDuration.Long,
)
if (result == SnackbarResult.ActionPerformed) {
viewModel.onEvent(TaskListEvent.OnUndoDeleteTask)
}
}
is UiEvent.Navigate -&gt; {
//if you want to hide the snackBar
snackBarController.cancelActiveJob()
onNavigate(event)
}
else -&gt; Unit
}
}
}
Note if you call snackbarController.showSnackbar() more than once
It replaces the current visible snackbar
[1]: https://github.com/mitchtabian/food2fork-compose/blob/d291d5114a0a5dd09e7d5ab2de3804607cfbd5d2/app/src/main/java/com/codingwithmitch/food2forkcompose/presentation/components/util/SnackbarController.kt
</details>

huangapple
  • 本文由 发表于 2023年2月18日 02:51:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/75488259.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定