Skip to content

Commit 6ad5187

Browse files
authored
Fix StrictMode violations in the AttachmentsPicker (#6029)
* AttachmentsPickerSystemTabFactory strict mode. * Fix StrictMode violations in the AttachmentsPicker. * Update CHANGELOG.md. * Pass application context to StorageHelperWrapper. * Deprecate unused method. * Simplify docs. * Simplify docs.
1 parent 46a6be2 commit 6ad5187

File tree

12 files changed

+531
-67
lines changed

12 files changed

+531
-67
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
### 🐞 Fixed
7676

7777
### ⬆️ Improved
78+
- Fix `StrictMode` violations in the `AttachmentsPicker`. [#6029](https://github.com/GetStream/stream-chat-android/pull/6029)
7879

7980
### ✅ Added
8081

DEPRECATIONS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ This document lists deprecated constructs in the SDK, with their expected time
44

55
| API / Feature | Deprecated (warning) | Deprecated (error) | Removed | Notes |
66
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------|-----------------------|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
7+
| `AttachmentsPickerViewModel.loadData()` method | 2025.12.10 ⌛ | | | This method is deprecated because it is no longer used and will be removed in future versions. |
78
| `ChatClient.markThreadUnread(channelType: String, channelId: String, threadId: String, mesageId: String)` method | 2025.11.24 ⌛ | | | This method is deprecated because marking a thread as unread from a given message is currently not supported, and the passed `messageId` is ignored. Use `ChatClient.markThreadUnread(channelType: String, channelId: String, threadId: String)` instead. |
89
| `ChannelClient.markThreadUnread(threadId: String, mesageId: String)` method | 2025.11.24 ⌛ | | | This method is deprecated because marking a thread as unread from a given message is currently not supported, and the passed `messageId` is ignored. Use `ChannelClient.markThreadUnread(threadId: String)` instead. |
910
| `StatePluginConfig.backgroundSyncEnabled` property | 2025.11.24 ⌛ | | | This property has been deprecated and will be removed in the future. We recommend disabling it to avoid unnecessary background work. |

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ 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
3839
import androidx.compose.runtime.getValue
3940
import androidx.compose.runtime.mutableIntStateOf
4041
import androidx.compose.runtime.remember
@@ -61,6 +62,7 @@ import io.getstream.chat.android.compose.viewmodel.messages.AttachmentsPickerVie
6162
import io.getstream.chat.android.models.Attachment
6263
import io.getstream.chat.android.models.Channel
6364
import io.getstream.chat.android.ui.common.state.messages.MessageMode
65+
import kotlinx.coroutines.flow.collectLatest
6466

6567
/**
6668
* Represents the bottom bar UI that allows users to pick attachments. The picker renders its
@@ -90,10 +92,16 @@ public fun AttachmentsPicker(
9092
shape: Shape = ChatTheme.shapes.bottomSheet,
9193
messageMode: MessageMode = MessageMode.Normal,
9294
) {
95+
// Listen for attachments to be ready for upload
96+
LaunchedEffect(attachmentsPickerViewModel) {
97+
attachmentsPickerViewModel.attachmentsForUpload.collectLatest {
98+
onAttachmentsSelected(it)
99+
}
100+
}
93101
val saveAttachmentsOnDismiss = ChatTheme.attachmentPickerTheme.saveAttachmentsOnDismiss
94102
val dismissAction = {
95103
if (saveAttachmentsOnDismiss) {
96-
onAttachmentsSelected(attachmentsPickerViewModel.getSelectedAttachments())
104+
attachmentsPickerViewModel.getSelectedAttachmentsAsync()
97105
}
98106
onDismiss()
99107
}
@@ -144,7 +152,7 @@ public fun AttachmentsPicker(
144152
attachmentsPickerViewModel.changeAttachmentPickerMode(attachmentPickerMode) { false }
145153
},
146154
onSendAttachmentsClick = {
147-
onAttachmentsSelected(attachmentsPickerViewModel.getSelectedAttachments())
155+
attachmentsPickerViewModel.getSelectedAttachmentsAsync()
148156
},
149157
)
150158
}
@@ -171,7 +179,7 @@ public fun AttachmentsPicker(
171179
onAttachmentItemSelected = attachmentsPickerViewModel::changeSelectedAttachments,
172180
onAttachmentsChanged = { attachmentsPickerViewModel.attachments = it },
173181
onAttachmentsSubmitted = {
174-
onAttachmentsSelected(attachmentsPickerViewModel.getAttachmentsFromMetaData(it))
182+
attachmentsPickerViewModel.getAttachmentsFromMetadataAsync(it)
175183
},
176184
)
177185
}

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

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import androidx.compose.ui.res.painterResource
4444
import androidx.compose.ui.res.stringResource
4545
import androidx.compose.ui.unit.dp
4646
import androidx.lifecycle.compose.LocalLifecycleOwner
47+
import androidx.lifecycle.viewmodel.compose.viewModel
4748
import io.getstream.chat.android.compose.R
4849
import io.getstream.chat.android.compose.state.messages.attachments.AttachmentPickerItemState
4950
import io.getstream.chat.android.compose.state.messages.attachments.AttachmentsPickerMode
@@ -55,6 +56,7 @@ import io.getstream.chat.android.ui.common.permissions.FilesAccess
5556
import io.getstream.chat.android.ui.common.permissions.Permissions
5657
import io.getstream.chat.android.ui.common.state.messages.composer.AttachmentMetaData
5758
import io.getstream.chat.android.uiutils.util.openSystemSettings
59+
import kotlinx.coroutines.flow.collectLatest
5860

5961
/**
6062
* Holds the information required to add support for "files" tab in the attachment picker.
@@ -96,6 +98,7 @@ public class AttachmentsPickerFilesTabFactory : AttachmentsPickerTabFactory {
9698
* @param onAttachmentItemSelected Handler when the item selection state changes.
9799
* @param onAttachmentsSubmitted Handler to submit the selected attachments to the message composer.
98100
*/
101+
@Suppress("LongMethod")
99102
@Composable
100103
override fun PickerTabContent(
101104
onAttachmentPickerAction: (AttachmentPickerAction) -> Unit,
@@ -106,8 +109,27 @@ public class AttachmentsPickerFilesTabFactory : AttachmentsPickerTabFactory {
106109
) {
107110
val context = LocalContext.current
108111
val lifecycleOwner = LocalLifecycleOwner.current
109-
val storageHelper: StorageHelperWrapper = remember {
110-
StorageHelperWrapper(context)
112+
val processingViewModel = viewModel<AttachmentsProcessingViewModel>(
113+
factory = AttachmentsProcessingViewModelFactory(StorageHelperWrapper(context.applicationContext)),
114+
)
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+
}
111133
}
112134
var showPermanentlyDeniedSnackBar by remember { mutableStateOf(false) }
113135
val permissionLauncher =
@@ -118,9 +140,7 @@ public class AttachmentsPickerFilesTabFactory : AttachmentsPickerTabFactory {
118140
}
119141
val filesAccess by filesAccessAsState(context, lifecycleOwner) { value ->
120142
if (value != FilesAccess.DENIED) {
121-
onAttachmentsChanged(
122-
storageHelper.getFiles().map { AttachmentPickerItemState(it, false) },
123-
)
143+
processingViewModel.getFilesAsync()
124144
}
125145
}
126146

@@ -135,17 +155,7 @@ public class AttachmentsPickerFilesTabFactory : AttachmentsPickerTabFactory {
135155
files = attachments,
136156
onItemSelected = onAttachmentItemSelected,
137157
onBrowseFilesResult = { uris ->
138-
val attachments = storageHelper.getAttachmentsMetadataFromUris(uris)
139-
// Check if some of the files were filtered out due to upload config
140-
if (uris.size != attachments.size) {
141-
Toast.makeText(
142-
context,
143-
R.string.stream_compose_message_composer_file_not_supported,
144-
Toast.LENGTH_SHORT,
145-
).show()
146-
}
147-
148-
onAttachmentsSubmitted(attachments)
158+
processingViewModel.getAttachmentsMetadataFromUrisAsync(uris)
149159
},
150160
)
151161
},

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import androidx.compose.ui.res.painterResource
3535
import androidx.compose.ui.res.stringResource
3636
import androidx.compose.ui.unit.dp
3737
import androidx.lifecycle.compose.LocalLifecycleOwner
38+
import androidx.lifecycle.viewmodel.compose.viewModel
3839
import io.getstream.chat.android.compose.R
3940
import io.getstream.chat.android.compose.state.messages.attachments.AttachmentPickerItemState
4041
import io.getstream.chat.android.compose.state.messages.attachments.AttachmentsPickerMode
@@ -46,6 +47,7 @@ import io.getstream.chat.android.ui.common.permissions.Permissions
4647
import io.getstream.chat.android.ui.common.permissions.VisualMediaAccess
4748
import io.getstream.chat.android.ui.common.state.messages.composer.AttachmentMetaData
4849
import io.getstream.chat.android.uiutils.util.openSystemSettings
50+
import kotlinx.coroutines.flow.collectLatest
4951

5052
/**
5153
* Holds the information required to add support for "images" tab in the attachment picker.
@@ -98,13 +100,18 @@ public class AttachmentsPickerImagesTabFactory : AttachmentsPickerTabFactory {
98100
val permissions = Permissions.visualMediaPermissions()
99101
val context = LocalContext.current
100102
val lifecycleOwner = LocalLifecycleOwner.current
101-
val storageHelper: StorageHelperWrapper =
102-
remember { StorageHelperWrapper(context) }
103+
val processingViewModel = viewModel<AttachmentsProcessingViewModel>(
104+
factory = AttachmentsProcessingViewModelFactory(StorageHelperWrapper(context.applicationContext)),
105+
)
106+
LaunchedEffect(processingViewModel) {
107+
processingViewModel.mediaMetadata.collectLatest { metaData ->
108+
val items = metaData.map { AttachmentPickerItemState(it, false) }
109+
onAttachmentsChanged(items)
110+
}
111+
}
103112
val mediaAccess by visualMediaAccessAsState(context, lifecycleOwner) { value ->
104113
if (value != VisualMediaAccess.DENIED) {
105-
val media = storageHelper.getMedia()
106-
val mediaAttachments = media.map { AttachmentPickerItemState(it, false) }
107-
onAttachmentsChanged(mediaAttachments)
114+
processingViewModel.getMediaAsync()
108115
}
109116
}
110117

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

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import androidx.compose.ui.res.stringResource
6161
import androidx.compose.ui.unit.dp
6262
import androidx.compose.ui.window.Dialog
6363
import androidx.compose.ui.window.DialogProperties
64+
import androidx.lifecycle.viewmodel.compose.viewModel
6465
import com.google.accompanist.permissions.ExperimentalPermissionsApi
6566
import com.google.accompanist.permissions.PermissionState
6667
import com.google.accompanist.permissions.isGranted
@@ -81,6 +82,7 @@ import io.getstream.chat.android.ui.common.permissions.SystemAttachmentsPickerCo
8182
import io.getstream.chat.android.ui.common.permissions.toContractVisualMediaType
8283
import io.getstream.chat.android.ui.common.state.messages.composer.AttachmentMetaData
8384
import io.getstream.chat.android.ui.common.utils.isPermissionDeclared
85+
import kotlinx.coroutines.flow.collectLatest
8486

8587
/**
8688
* Holds the information required to add support for "files" tab in the attachment picker.
@@ -208,27 +210,33 @@ public class AttachmentsPickerSystemTabFactory(
208210
onAttachmentsSubmitted: (List<AttachmentMetaData>) -> Unit,
209211
) {
210212
val context = LocalContext.current
211-
val storageHelper: StorageHelperWrapper = remember {
212-
StorageHelperWrapper(context)
213+
214+
val processingViewModel = viewModel<AttachmentsProcessingViewModel>(
215+
factory = AttachmentsProcessingViewModelFactory(StorageHelperWrapper(context.applicationContext)),
216+
)
217+
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+
}
213230
}
214231

215232
val filePickerLauncher = rememberFilePickerLauncher { uri ->
216233
val uris = listOf(uri)
217-
val attachments = storageHelper.getAttachmentsMetadataFromUris(uris)
218-
// Check if some of the files were filtered out due to upload config
219-
if (uris.size != attachments.size) {
220-
Toast.makeText(
221-
context,
222-
R.string.stream_compose_message_composer_file_not_supported,
223-
Toast.LENGTH_SHORT,
224-
).show()
225-
}
226-
onAttachmentsSubmitted(attachments)
234+
processingViewModel.getAttachmentsMetadataFromUrisAsync(uris)
227235
}
228236

229237
val imagePickerLauncher =
230238
rememberVisualMediaPickerLauncher(config.visualMediaAllowMultiple) { uris ->
231-
onAttachmentsSubmitted(storageHelper.getAttachmentsMetadataFromUris(uris))
239+
processingViewModel.getAttachmentsMetadataFromUrisAsync(uris)
232240
}
233241

234242
val captureLauncher = rememberCaptureMediaLauncher(

0 commit comments

Comments
 (0)