Skip to content

Commit 7b1639d

Browse files
authored
Fix file picker regression - replace flow collection with callbacks (#6104)
* Replace flow collection with callbacks. * Run callbacks on Dispatcher.Main. * Update CHANGELOG.md. * Fix case where saveAttachmentsOnDismiss = true. * Fix case where saveAttachmentsOnDismiss = true.
1 parent 56e4a9a commit 7b1639d

9 files changed

Lines changed: 146 additions & 180 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171

7272
## stream-chat-android-compose
7373
### 🐞 Fixed
74+
- Fix file picker and media capture not working in cases when the attachments picker composable is disposed. [#6104](https://github.com/GetStream/stream-chat-android/pull/6104)
7475
- Fix quoting edited messages showing outdated text. [#6107](https://github.com/GetStream/stream-chat-android/pull/6107)
7576

7677
### ⬆️ Improved

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentsPicker.kt

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import androidx.compose.material3.Icon
3535
import androidx.compose.material3.IconButton
3636
import androidx.compose.material3.Surface
3737
import androidx.compose.runtime.Composable
38-
import androidx.compose.runtime.LaunchedEffect
3938
import androidx.compose.runtime.getValue
4039
import androidx.compose.runtime.mutableIntStateOf
4140
import androidx.compose.runtime.remember
@@ -62,7 +61,6 @@ import io.getstream.chat.android.compose.viewmodel.messages.AttachmentsPickerVie
6261
import io.getstream.chat.android.models.Attachment
6362
import io.getstream.chat.android.models.Channel
6463
import io.getstream.chat.android.ui.common.state.messages.MessageMode
65-
import kotlinx.coroutines.flow.collectLatest
6664

6765
/**
6866
* Represents the bottom bar UI that allows users to pick attachments. The picker renders its
@@ -92,18 +90,16 @@ public fun AttachmentsPicker(
9290
shape: Shape = ChatTheme.shapes.bottomSheet,
9391
messageMode: MessageMode = MessageMode.Normal,
9492
) {
95-
// Listen for attachments to be ready for upload
96-
LaunchedEffect(attachmentsPickerViewModel) {
97-
attachmentsPickerViewModel.attachmentsForUpload.collectLatest {
98-
onAttachmentsSelected(it)
99-
}
100-
}
10193
val saveAttachmentsOnDismiss = ChatTheme.attachmentPickerTheme.saveAttachmentsOnDismiss
10294
val dismissAction = {
10395
if (saveAttachmentsOnDismiss) {
104-
attachmentsPickerViewModel.getSelectedAttachmentsAsync()
96+
attachmentsPickerViewModel.getSelectedAttachmentsAsync { attachments ->
97+
onAttachmentsSelected(attachments)
98+
onDismiss()
99+
}
100+
} else {
101+
onDismiss()
105102
}
106-
onDismiss()
107103
}
108104
BackHandler(onBack = dismissAction)
109105
// Cross-validate requested tabFactories with the allowed ones from BE
@@ -152,7 +148,9 @@ public fun AttachmentsPicker(
152148
attachmentsPickerViewModel.changeAttachmentPickerMode(attachmentPickerMode) { false }
153149
},
154150
onSendAttachmentsClick = {
155-
attachmentsPickerViewModel.getSelectedAttachmentsAsync()
151+
attachmentsPickerViewModel.getSelectedAttachmentsAsync { attachments ->
152+
onAttachmentsSelected(attachments)
153+
}
156154
},
157155
)
158156
}
@@ -179,7 +177,9 @@ public fun AttachmentsPicker(
179177
onAttachmentItemSelected = attachmentsPickerViewModel::changeSelectedAttachments,
180178
onAttachmentsChanged = { attachmentsPickerViewModel.attachments = it },
181179
onAttachmentsSubmitted = {
182-
attachmentsPickerViewModel.getAttachmentsFromMetadataAsync(it)
180+
attachmentsPickerViewModel.getAttachmentsFromMetadataAsync(it) { attachments ->
181+
onAttachmentsSelected(attachments)
182+
}
183183
},
184184
)
185185
}

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerFilesTabFactory.kt

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package io.getstream.chat.android.compose.ui.messages.attachments.factory
1818

19+
import android.content.Context
1920
import android.widget.Toast
2021
import androidx.activity.compose.rememberLauncherForActivityResult
2122
import androidx.activity.result.contract.ActivityResultContracts
@@ -56,7 +57,6 @@ import io.getstream.chat.android.ui.common.permissions.FilesAccess
5657
import io.getstream.chat.android.ui.common.permissions.Permissions
5758
import io.getstream.chat.android.ui.common.state.messages.composer.AttachmentMetaData
5859
import io.getstream.chat.android.uiutils.util.openSystemSettings
59-
import kotlinx.coroutines.flow.collectLatest
6060

6161
/**
6262
* Holds the information required to add support for "files" tab in the attachment picker.
@@ -112,25 +112,6 @@ public class AttachmentsPickerFilesTabFactory : AttachmentsPickerTabFactory {
112112
val processingViewModel = viewModel<AttachmentsProcessingViewModel>(
113113
factory = AttachmentsProcessingViewModelFactory(StorageHelperWrapper(context.applicationContext)),
114114
)
115-
LaunchedEffect(processingViewModel) {
116-
processingViewModel.attachmentsMetadataFromUris.collectLatest { metadata ->
117-
// Check if some of the files were filtered out due to upload config
118-
if (metadata.uris.size != metadata.attachmentsMetadata.size) {
119-
Toast.makeText(
120-
context,
121-
R.string.stream_compose_message_composer_file_not_supported,
122-
Toast.LENGTH_SHORT,
123-
).show()
124-
}
125-
onAttachmentsSubmitted(metadata.attachmentsMetadata)
126-
}
127-
}
128-
LaunchedEffect(processingViewModel) {
129-
processingViewModel.filesMetadata.collectLatest { metaData ->
130-
val items = metaData.map { AttachmentPickerItemState(it, false) }
131-
onAttachmentsChanged(items)
132-
}
133-
}
134115
var showPermanentlyDeniedSnackBar by remember { mutableStateOf(false) }
135116
val permissionLauncher =
136117
rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
@@ -140,7 +121,10 @@ public class AttachmentsPickerFilesTabFactory : AttachmentsPickerTabFactory {
140121
}
141122
val filesAccess by filesAccessAsState(context, lifecycleOwner) { value ->
142123
if (value != FilesAccess.DENIED) {
143-
processingViewModel.getFilesAsync()
124+
processingViewModel.getFilesAsync { metadata ->
125+
val items = metadata.map { AttachmentPickerItemState(it, false) }
126+
onAttachmentsChanged(items)
127+
}
144128
}
145129
}
146130

@@ -155,7 +139,10 @@ public class AttachmentsPickerFilesTabFactory : AttachmentsPickerTabFactory {
155139
files = attachments,
156140
onItemSelected = onAttachmentItemSelected,
157141
onBrowseFilesResult = { uris ->
158-
processingViewModel.getAttachmentsMetadataFromUrisAsync(uris)
142+
processingViewModel.getAttachmentsMetadataFromUrisAsync(uris) { metadata ->
143+
showErrorIfNeeded(context, metadata)
144+
onAttachmentsSubmitted(metadata.attachmentsMetadata)
145+
}
159146
},
160147
)
161148
},
@@ -283,4 +270,14 @@ public class AttachmentsPickerFilesTabFactory : AttachmentsPickerTabFactory {
283270
)
284271
}
285272
}
273+
274+
private fun showErrorIfNeeded(context: Context, metadata: AttachmentsMetadataFromUris) {
275+
if (metadata.uris.size != metadata.attachmentsMetadata.size) {
276+
Toast.makeText(
277+
context,
278+
R.string.stream_compose_message_composer_file_not_supported,
279+
Toast.LENGTH_SHORT,
280+
).show()
281+
}
282+
}
286283
}

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerImagesTabFactory.kt

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ import io.getstream.chat.android.ui.common.permissions.Permissions
4747
import io.getstream.chat.android.ui.common.permissions.VisualMediaAccess
4848
import io.getstream.chat.android.ui.common.state.messages.composer.AttachmentMetaData
4949
import io.getstream.chat.android.uiutils.util.openSystemSettings
50-
import kotlinx.coroutines.flow.collectLatest
5150

5251
/**
5352
* Holds the information required to add support for "images" tab in the attachment picker.
@@ -103,15 +102,12 @@ public class AttachmentsPickerImagesTabFactory : AttachmentsPickerTabFactory {
103102
val processingViewModel = viewModel<AttachmentsProcessingViewModel>(
104103
factory = AttachmentsProcessingViewModelFactory(StorageHelperWrapper(context.applicationContext)),
105104
)
106-
LaunchedEffect(processingViewModel) {
107-
processingViewModel.mediaMetadata.collectLatest { metaData ->
108-
val items = metaData.map { AttachmentPickerItemState(it, false) }
109-
onAttachmentsChanged(items)
110-
}
111-
}
112105
val mediaAccess by visualMediaAccessAsState(context, lifecycleOwner) { value ->
113106
if (value != VisualMediaAccess.DENIED) {
114-
processingViewModel.getMediaAsync()
107+
processingViewModel.getMediaAsync { metadata ->
108+
val items = metadata.map { AttachmentPickerItemState(it, false) }
109+
onAttachmentsChanged(items)
110+
}
115111
}
116112
}
117113

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerSystemTabFactory.kt

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package io.getstream.chat.android.compose.ui.messages.attachments.factory
1818

1919
import android.Manifest
2020
import android.app.Activity
21+
import android.content.Context
2122
import android.content.Intent
2223
import android.net.Uri
2324
import android.widget.Toast
@@ -82,7 +83,6 @@ import io.getstream.chat.android.ui.common.permissions.SystemAttachmentsPickerCo
8283
import io.getstream.chat.android.ui.common.permissions.toContractVisualMediaType
8384
import io.getstream.chat.android.ui.common.state.messages.composer.AttachmentMetaData
8485
import io.getstream.chat.android.ui.common.utils.isPermissionDeclared
85-
import kotlinx.coroutines.flow.collectLatest
8686

8787
/**
8888
* Holds the information required to add support for "files" tab in the attachment picker.
@@ -215,28 +215,20 @@ public class AttachmentsPickerSystemTabFactory(
215215
factory = AttachmentsProcessingViewModelFactory(StorageHelperWrapper(context.applicationContext)),
216216
)
217217

218-
LaunchedEffect(processingViewModel) {
219-
processingViewModel.attachmentsMetadataFromUris.collectLatest { metadata ->
220-
// Check if some of the files were filtered out due to upload config
221-
if (metadata.uris.size != metadata.attachmentsMetadata.size) {
222-
Toast.makeText(
223-
context,
224-
R.string.stream_compose_message_composer_file_not_supported,
225-
Toast.LENGTH_SHORT,
226-
).show()
227-
}
228-
onAttachmentsSubmitted(metadata.attachmentsMetadata)
229-
}
230-
}
231-
232218
val filePickerLauncher = rememberFilePickerLauncher { uri ->
233219
val uris = listOf(uri)
234-
processingViewModel.getAttachmentsMetadataFromUrisAsync(uris)
220+
processingViewModel.getAttachmentsMetadataFromUrisAsync(uris) { metadata ->
221+
showErrorIfNeeded(context, metadata)
222+
onAttachmentsSubmitted(metadata.attachmentsMetadata)
223+
}
235224
}
236225

237226
val imagePickerLauncher =
238227
rememberVisualMediaPickerLauncher(config.visualMediaAllowMultiple) { uris ->
239-
processingViewModel.getAttachmentsMetadataFromUrisAsync(uris)
228+
processingViewModel.getAttachmentsMetadataFromUrisAsync(uris) { metadata ->
229+
showErrorIfNeeded(context, metadata)
230+
onAttachmentsSubmitted(metadata.attachmentsMetadata)
231+
}
240232
}
241233

242234
val captureLauncher = rememberCaptureMediaLauncher(
@@ -341,6 +333,16 @@ public class AttachmentsPickerSystemTabFactory(
341333
addCategory(Intent.CATEGORY_OPENABLE)
342334
}
343335
}
336+
337+
private fun showErrorIfNeeded(context: Context, metadata: AttachmentsMetadataFromUris) {
338+
if (metadata.uris.size != metadata.attachmentsMetadata.size) {
339+
Toast.makeText(
340+
context,
341+
R.string.stream_compose_message_composer_file_not_supported,
342+
Toast.LENGTH_SHORT,
343+
).show()
344+
}
345+
}
344346
}
345347

346348
@Composable

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsProcessingViewModel.kt

Lines changed: 28 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,8 @@ import androidx.lifecycle.viewModelScope
2323
import io.getstream.chat.android.compose.ui.util.StorageHelperWrapper
2424
import io.getstream.chat.android.core.internal.coroutines.DispatcherProvider
2525
import io.getstream.chat.android.ui.common.state.messages.composer.AttachmentMetaData
26-
import kotlinx.coroutines.flow.MutableSharedFlow
27-
import kotlinx.coroutines.flow.SharedFlow
28-
import kotlinx.coroutines.flow.asSharedFlow
2926
import kotlinx.coroutines.launch
27+
import kotlinx.coroutines.withContext
3028

3129
/**
3230
* ViewModel responsible for asynchronous processing of attachment metadata.
@@ -40,67 +38,50 @@ internal class AttachmentsProcessingViewModel(
4038
private val storageHelper: StorageHelperWrapper,
4139
) : ViewModel() {
4240

43-
private val _attachmentsMetadataFromUris =
44-
MutableSharedFlow<AttachmentsMetadataFromUris>(extraBufferCapacity = 1)
45-
private val _filesMetadata =
46-
MutableSharedFlow<List<AttachmentMetaData>>(extraBufferCapacity = 1)
47-
private val _mediaMetadata =
48-
MutableSharedFlow<List<AttachmentMetaData>>(extraBufferCapacity = 1)
49-
50-
/**
51-
* Flow of events emitted when attachments metadata is retrieved from URIs.
52-
*/
53-
val attachmentsMetadataFromUris: SharedFlow<AttachmentsMetadataFromUris> =
54-
_attachmentsMetadataFromUris.asSharedFlow()
55-
56-
/**
57-
* Flow of events emitted when files metadata is retrieved.
58-
*/
59-
val filesMetadata: SharedFlow<List<AttachmentMetaData>> =
60-
_filesMetadata.asSharedFlow()
61-
62-
/**
63-
* Flow of events emitted when media metadata is retrieved.
64-
*/
65-
val mediaMetadata: SharedFlow<List<AttachmentMetaData>> =
66-
_mediaMetadata.asSharedFlow()
67-
6841
/**
6942
* Processes a list of attachment URIs in the background and emits the result.
70-
* Observe the [attachmentsMetadataFromUris] flow to be notified about the result.
7143
*
7244
* @param uris The list of URIs to process.
45+
* @param onComplete The callback passing the processed attachments.
7346
*/
74-
fun getAttachmentsMetadataFromUrisAsync(uris: List<Uri>) {
75-
viewModelScope.launch(DispatcherProvider.IO) {
76-
val metadata = storageHelper.getAttachmentsMetadataFromUris(uris)
77-
val attachmentsMetadataFromUris = AttachmentsMetadataFromUris(
78-
uris = uris,
79-
attachmentsMetadata = metadata,
80-
)
81-
_attachmentsMetadataFromUris.emit(attachmentsMetadataFromUris)
47+
fun getAttachmentsMetadataFromUrisAsync(uris: List<Uri>, onComplete: (AttachmentsMetadataFromUris) -> Unit) {
48+
viewModelScope.launch {
49+
val attachmentsMetadataFromUris = withContext(DispatcherProvider.IO) {
50+
val metadata = storageHelper.getAttachmentsMetadataFromUris(uris)
51+
AttachmentsMetadataFromUris(
52+
uris = uris,
53+
attachmentsMetadata = metadata,
54+
)
55+
}
56+
onComplete(attachmentsMetadataFromUris)
8257
}
8358
}
8459

8560
/**
8661
* Retrieves files metadata asynchronously and emits the result.
87-
* Observe the [filesMetadata] flow to be notified about the result.
62+
*
63+
* @param onComplete The callback passing the resolved files.
8864
*/
89-
fun getFilesAsync() {
90-
viewModelScope.launch(DispatcherProvider.IO) {
91-
val metadata = storageHelper.getFiles()
92-
_filesMetadata.emit(metadata)
65+
fun getFilesAsync(onComplete: (List<AttachmentMetaData>) -> Unit) {
66+
viewModelScope.launch {
67+
val metadata = withContext(DispatcherProvider.IO) {
68+
storageHelper.getFiles()
69+
}
70+
onComplete(metadata)
9371
}
9472
}
9573

9674
/**
9775
* Retrieves media metadata asynchronously and emits the result.
98-
* Observe the [mediaMetadata] flow to be notified about the result.
76+
*
77+
* @param onComplete The callback passing the resolved files.
9978
*/
100-
fun getMediaAsync() {
101-
viewModelScope.launch(DispatcherProvider.IO) {
102-
val metadata = storageHelper.getMedia()
103-
_mediaMetadata.emit(metadata)
79+
fun getMediaAsync(onComplete: (List<AttachmentMetaData>) -> Unit) {
80+
viewModelScope.launch {
81+
val metadata = withContext(DispatcherProvider.IO) {
82+
storageHelper.getMedia()
83+
}
84+
onComplete(metadata)
10485
}
10586
}
10687
}

0 commit comments

Comments
 (0)