取消已经开始的活动?

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

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

问题

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

TasksList.kt:

  1. @OptIn(ExperimentalMaterial3Api::class)
  2. @Composable
  3. fun TasksListScreen(
  4. modifier: Modifier = Modifier,
  5. onNavigate: (UiEvent.Navigate) -> Unit,
  6. viewModel: TaskListViewModel = hiltViewModel()
  7. ) {
  8. val tasks = viewModel.tasks.collectAsState(initial = emptyList())
  9. val snackbarHostState = remember { SnackbarHostState() }
  10. LaunchedEffect(key1 = true) {
  11. viewModel.uiEvent.collect { event ->
  12. when (event) {
  13. is UiEvent.ShowSnackbar -> {
  14. val result = snackbarHostState.showSnackbar(
  15. message = event.message,
  16. actionLabel = event.action,
  17. duration = SnackbarDuration.Long,
  18. )
  19. if (result == SnackbarResult.ActionPerformed) {
  20. viewModel.onEvent(TaskListEvent.OnUndoDeleteTask)
  21. }
  22. }
  23. is UiEvent.Navigate -> onNavigate(event)
  24. else -> Unit
  25. }
  26. }
  27. }
  28. Scaffold(
  29. snackbarHost = {
  30. SnackbarHost(snackbarHostState) { data ->
  31. Snackbar(
  32. shape = RoundedShapes.medium,
  33. actionColor = MaterialTheme.colorScheme.primary,
  34. contentColor = MaterialTheme.colorScheme.background,
  35. snackbarData = data
  36. )
  37. }
  38. },
  39. floatingActionButton = {
  40. FloatingActionButton(
  41. shape = RoundedShapes.medium,
  42. onClick = { viewModel.onEvent(TaskListEvent.OnAddTask) },
  43. containerColor = MaterialTheme.colorScheme.primary,
  44. contentColor = MaterialTheme.colorScheme.background
  45. ) {
  46. Icon(
  47. imageVector = Icons.Default.Add,
  48. contentDescription = stringResource(R.string.fab_cd)
  49. )
  50. }
  51. },
  52. topBar = {
  53. TopAppBar(
  54. title = { Text(stringResource(R.string.app_name)) },
  55. colors = TopAppBarDefaults.topAppBarColors(
  56. containerColor = MaterialTheme.colorScheme.primary,
  57. titleContentColor = MaterialTheme.colorScheme.background
  58. )
  59. }
  60. },
  61. ) { padding ->
  62. LazyColumn(
  63. state = rememberLazyListState(),
  64. verticalArrangement = spacedBy(12.dp),
  65. contentPadding = PaddingValues(vertical = 16.dp),
  66. modifier = modifier
  67. .fillMaxSize()
  68. .padding(padding)
  69. .padding(horizontal = 16.dp),
  70. ) {
  71. items(items = tasks.value, key = { task -> task.hashCode() }) { task ->
  72. val currentTask by rememberUpdatedState(newValue = task)
  73. val dismissState = rememberDismissState(confirmValueChange = {
  74. if (it == DismissValue.DismissedToStart) {
  75. viewModel.onEvent(TaskListEvent.OnDeleteTask(currentTask))
  76. }
  77. true
  78. })
  79. SwipeToDismiss(state = dismissState,
  80. directions = setOf(DismissDirection.EndToStart),
  81. background = { },
  82. dismissContent = {
  83. TaskItem(
  84. task = task, modifier = modifier
  85. )
  86. })
  87. }
  88. }
  89. }
  90. }

TaskListViewModel:

  1. @HiltViewModel
  2. class TaskListViewModel @Inject constructor(private val repository: TaskRepositoryImplementation) :
  3. ViewModel() {
  4. val tasks = repository.getTasks()
  5. private val _uiEvent = Channel<UiEvent>()
  6. val uiEvent = _uiEvent.receiveAsFlow()
  7. private var deletedTask: Task? = null
  8. fun onEvent(event: TaskListEvent) {
  9. when (event) {
  10. is TaskListEvent.OnAddTask -> {
  11. sendUiEvent(UiEvent.Navigate(Routes.ADD_EDIT_TASK))
  12. }
  13. is TaskListEvent.OnEditClick -> {
  14. sendUiEvent(UiEvent.Navigate(Routes.ADD_EDIT_TASK + "?taskId=${event.task.id}"))
  15. }
  16. is TaskListEvent.OnDeleteTask -> {
  17. val context = TaskApp.instance?.context
  18. viewModelScope.launch(Dispatchers.IO) {
  19. deletedTask = event.task
  20. repository.deleteTask(event.task)
  21. if (context != null) {
  22. sendUiEvent(
  23. UiEvent.ShowSnackbar(
  24. message = context.getString(R.string.snackbar_deleted),
  25. action = context.getString(R.string.snackbar_action)
  26. )
  27. )
  28. }
  29. }
  30. }
  31. is TaskListEvent.OnUndoDeleteTask -> {
  32. deletedTask?.let { task ->
  33. viewModelScope.launch {
  34. repository.addTask(task)
  35. }
  36. }
  37. }
  38. }
  39. }
  40. private fun sendUiEvent(event: UiEvent) {
  41. viewModelScope.launch(Dispatchers.IO) {
  42. _uiEvent.send(event)
  43. }
  44. }
  45. }
英文:

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:

  1. @OptIn(ExperimentalMaterial3Api::class)
  2. @Composable
  3. fun TasksListScreen(
  4. modifier: Modifier = Modifier,
  5. onNavigate: (UiEvent.Navigate) -&gt; Unit,
  6. viewModel: TaskListViewModel = hiltViewModel()
  7. ) {
  8. val tasks = viewModel.tasks.collectAsState(initial = emptyList())
  9. val snackbarHostState = remember { SnackbarHostState() }
  10. LaunchedEffect(key1 = true) {
  11. viewModel.uiEvent.collect { event -&gt;
  12. when (event) {
  13. is UiEvent.ShowSnackbar -&gt; {
  14. val result = snackbarHostState.showSnackbar(
  15. message = event.message,
  16. actionLabel = event.action,
  17. duration = SnackbarDuration.Long,
  18. )
  19. if (result == SnackbarResult.ActionPerformed) {
  20. viewModel.onEvent(TaskListEvent.OnUndoDeleteTask)
  21. }
  22. }
  23. is UiEvent.Navigate -&gt; onNavigate(event)
  24. else -&gt; Unit
  25. }
  26. }
  27. }
  28. Scaffold(
  29. snackbarHost = {
  30. SnackbarHost(snackbarHostState) { data -&gt;
  31. Snackbar(
  32. shape = RoundedShapes.medium,
  33. actionColor = MaterialTheme.colorScheme.primary,
  34. contentColor = MaterialTheme.colorScheme.background,
  35. snackbarData = data
  36. )
  37. }
  38. },
  39. floatingActionButton = {
  40. FloatingActionButton(
  41. shape = RoundedShapes.medium,
  42. onClick = { viewModel.onEvent(TaskListEvent.OnAddTask) },
  43. containerColor = MaterialTheme.colorScheme.primary,
  44. contentColor = MaterialTheme.colorScheme.background
  45. ) {
  46. Icon(
  47. imageVector = Icons.Default.Add,
  48. contentDescription = stringResource(R.string.fab_cd)
  49. )
  50. }
  51. },
  52. topBar = {
  53. TopAppBar(
  54. title = { Text(stringResource(R.string.app_name)) },
  55. colors = TopAppBarDefaults.topAppBarColors(
  56. containerColor = MaterialTheme.colorScheme.primary,
  57. titleContentColor = MaterialTheme.colorScheme.background
  58. )
  59. )
  60. },
  61. ) { padding -&gt;
  62. LazyColumn(
  63. state = rememberLazyListState(),
  64. verticalArrangement = spacedBy(12.dp),
  65. contentPadding = PaddingValues(vertical = 16.dp),
  66. modifier = modifier
  67. .fillMaxSize()
  68. .padding(padding)
  69. .padding(horizontal = 16.dp),
  70. ) {
  71. items(items = tasks.value, key = { task -&gt; task.hashCode() }) { task -&gt;
  72. val currentTask by rememberUpdatedState(newValue = task)
  73. val dismissState = rememberDismissState(confirmValueChange = {
  74. if (it == DismissValue.DismissedToStart) {
  75. viewModel.onEvent(TaskListEvent.OnDeleteTask(currentTask))
  76. }
  77. true
  78. })
  79. SwipeToDismiss(state = dismissState,
  80. directions = setOf(DismissDirection.EndToStart),
  81. background = { },
  82. dismissContent = {
  83. TaskItem(
  84. task = task, modifier = modifier
  85. )
  86. })
  87. }
  88. }
  89. }
  90. }

TaskListViewModel:

  1. @HiltViewModel
  2. class TaskListViewModel @Inject constructor(private val repository: TaskRepositoryImplementation) :
  3. ViewModel() {
  4. val tasks = repository.getTasks()
  5. private val _uiEvent = Channel&lt;UiEvent&gt;()
  6. val uiEvent = _uiEvent.receiveAsFlow()
  7. private var deletedTask: Task? = null
  8. fun onEvent(event: TaskListEvent) {
  9. when (event) {
  10. is TaskListEvent.OnAddTask -&gt; {
  11. sendUiEvent(UiEvent.Navigate(Routes.ADD_EDIT_TASK))
  12. }
  13. is TaskListEvent.OnEditClick -&gt; {
  14. sendUiEvent(UiEvent.Navigate(Routes.ADD_EDIT_TASK + &quot;?taskId=${event.task.id}&quot;))
  15. }
  16. is TaskListEvent.OnDeleteTask -&gt; {
  17. val context = TaskApp.instance?.context
  18. viewModelScope.launch(Dispatchers.IO) {
  19. deletedTask = event.task
  20. repository.deleteTask(event.task)
  21. if (context != null) {
  22. sendUiEvent(
  23. UiEvent.ShowSnackbar(
  24. message = context.getString(R.string.snackbar_deleted),
  25. action = context.getString(R.string.snackbar_action)
  26. )
  27. )
  28. }
  29. }
  30. }
  31. is TaskListEvent.OnUndoDeleteTask -&gt; {
  32. deletedTask?.let { task -&gt;
  33. viewModelScope.launch {
  34. repository.addTask(task)
  35. }
  36. }
  37. }
  38. }
  39. }
  40. private fun sendUiEvent(event: UiEvent) {
  41. viewModelScope.launch(Dispatchers.IO) {
  42. _uiEvent.send(event)
  43. }
  44. }
  45. }

答案1

得分: 2

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

  1. // 创建SnackbarController类以创建可取消的Snackbar
  2. // 受此存储库启发 [SnackbarController][1]
  3. // 公开方法**cancelActiveJob**以取消可见的Snackbar
  4. class SnackBarController(private val coroutineScope: CoroutineScope) {
  5. private var snackBarJob: Job? = null
  6. init {
  7. cancelActiveJob()
  8. }
  9. suspend fun showSnackBar(
  10. snackBarHostState: SnackbarHostState,
  11. message: String,
  12. actionLabel: String? = null,
  13. duration: SnackbarDuration
  14. ): SnackbarResult {
  15. var result: SnackbarResult = SnackbarResult.Dismissed
  16. if (snackBarJob == null) {
  17. snackBarJob = coroutineScope.launch {
  18. result = snackBarHostState.showSnackbar(
  19. message = message,
  20. actionLabel = actionLabel,
  21. duration = duration
  22. )
  23. cancelActiveJob()
  24. }
  25. } else {
  26. cancelActiveJob()
  27. snackBarJob = coroutineScope.launch {
  28. result = snackBarHostState.showSnackbar(
  29. message = message,
  30. actionLabel = actionLabel,
  31. duration = duration
  32. )
  33. cancelActiveJob()
  34. }
  35. }
  36. snackBarJob?.join()
  37. return result
  38. }
  39. fun cancelActiveJob() {
  40. snackBarJob?.let { job ->
  41. job.cancel()
  42. snackBarJob = Job()
  43. }
  44. }
  45. }

TaskListScreen中:

  1. val tasks = viewModel.tasks.collectAsState(initial = emptyList())
  2. val snackbarHostState = remember { SnackbarHostState() }
  3. val coroutineScope = rememberCoroutineScope()
  4. val snackBarController = SnackBarController(coroutineScope)
  5. LaunchedEffect(key1 = true) {
  6. viewModel.uiEvent.collectLatest { event ->
  7. when (event) {
  8. is UiEvent.ShowSnackbar -> {
  9. val result = snackBarController.showSnackBar(
  10. message = event.message,
  11. actionLabel = event.action,
  12. duration = SnackbarDuration.Long,
  13. )
  14. if (result == SnackbarResult.ActionPerformed) {
  15. viewModel.onEvent(TaskListEvent.OnUndoDeleteTask)
  16. }
  17. }
  18. is UiEvent.Navigate -> {
  19. // 如果要隐藏Snackbar
  20. snackBarController.cancelActiveJob()
  21. onNavigate(event)
  22. }
  23. else -> Unit
  24. }
  25. }
  26. }

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

  1. <details>
  2. <summary>英文:</summary>
  3. Create this SnackbarController class to create a cancellable snackbar
  4. Inspired by this repo [SnackbarController][1]
  5. Expose the method **cancelActiveJob** to cancel the visible snackbar
  6. class SnackBarController(private val coroutineScope: CoroutineScope) {
  7. private var snackBarJob: Job? = null
  8. init {
  9. cancelActiveJob()
  10. }
  11. suspend fun showSnackBar(
  12. snackBarHostState: SnackbarHostState,
  13. message: String,
  14. actionLabel: String? = null,
  15. duration: SnackbarDuration
  16. ): SnackbarResult {
  17. var result: SnackbarResult = SnackbarResult.Dismissed
  18. if (snackBarJob == null) {
  19. snackBarJob = coroutineScope.launch {
  20. result = snackBarHostState.showSnackbar(
  21. message = message,
  22. actionLabel = actionLabel,
  23. duration = duration
  24. )
  25. cancelActiveJob()
  26. }
  27. } else {
  28. cancelActiveJob()
  29. snackBarJob = coroutineScope.launch {
  30. result = snackBarHostState.showSnackbar(
  31. message = message,
  32. actionLabel = actionLabel,
  33. duration = duration
  34. )
  35. cancelActiveJob()
  36. }
  37. }
  38. snackBarJob?.join()
  39. return result
  40. }
  41. fun cancelActiveJob() {
  42. snackBarJob?.let { job -&gt;
  43. job.cancel()
  44. snackBarJob = Job()
  45. }
  46. }
  47. }
  48. In the TaskListScreen
  49. val tasks = viewModel.tasks.collectAsState(initial = emptyList())
  50. val snackbarHostState = remember { SnackbarHostState() }
  51. val coroutineScope = rememberCoroutineScope()
  52. val snackBarController = SnackBarController(coroutineScope)
  53. LaunchedEffect(key1 = true) {
  54. viewModel.uiEvent.collectLatest{ event -&gt;
  55. when (event) {
  56. is UiEvent.ShowSnackbar -&gt; {
  57. val result = snackBarController.showSnackBar(
  58. message = event.message,
  59. actionLabel = event.action,
  60. duration = SnackbarDuration.Long,
  61. )
  62. if (result == SnackbarResult.ActionPerformed) {
  63. viewModel.onEvent(TaskListEvent.OnUndoDeleteTask)
  64. }
  65. }
  66. is UiEvent.Navigate -&gt; {
  67. //if you want to hide the snackBar
  68. snackBarController.cancelActiveJob()
  69. onNavigate(event)
  70. }
  71. else -&gt; Unit
  72. }
  73. }
  74. }
  75. Note if you call snackbarController.showSnackbar() more than once
  76. It replaces the current visible snackbar
  77. [1]: https://github.com/mitchtabian/food2fork-compose/blob/d291d5114a0a5dd09e7d5ab2de3804607cfbd5d2/app/src/main/java/com/codingwithmitch/food2forkcompose/presentation/components/util/SnackbarController.kt
  78. </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:

确定