val chatClient = ChatClient.instance()
chatClient.blockUser(userId = "user-id").enqueue(
onSuccess = {
// Handle success
},
onError = { error ->
// handle error
}
)
Blocking Users
Introduction
Blocking users is an essential feature in a chat app because it enhances user safety and experience. It allows individuals to protect themselves from harassment, spam, and unwanted interactions. By giving users control over their interactions, it helps maintain privacy, reduces the risk of online bullying, and promotes a respectful community atmosphere.
As a result, some app stores require this functionality as part of their review process.
Stream Chat
The Stream Chat SDK provides a way for blocking and unblocking users, as well as listing all of the blocked users.
When you block a user, you won’t receive any direct messages from that user anymore. However, if you share a group with other participants, you will still receive messages from the blocked user.
In this cookbook, we will see how to implement this feature in your chat apps, using the Stream Chat SDK.
Low-Level Client support
The low-level client provides the following methods related to user blocking:
Block a user
To block a user, you can call the blockUser
method from the ChatClient
:
Unblock a user
Similarly, to unblock a blocked user, you can call the unblockUser
method
from the ChatClient
:
chatClient.unblockUser(userId = "user-id").enqueue(
onSuccess = {
// Handle success
},
onError = { error ->
// handle error
}
)
Listing blocked users
In order to see the list of your blocked users, you can call the
queryBlockedUsers
method from ChatClient
:
chatClient.queryBlockedUsers().enqueue(
onSuccess = { userBlocks ->
val blockedUserIds = userBlocks.map(UserBlock::userId)
// Handle success
},
onError = { error ->
// Handle error
}
)
Or you can retrieve the blocked user IDs by reading the blockedUserIds
property from the current user:
val blockedUserIds = chatClient.getCurrentUser()?.blockedUserIds
You can then use the following code to check if a user is blocked, and show the corresponding block or unblock action:
if (blockedUserIds.contains(userId)) {
// User is blocked, show "Unblock action"
} else {
// User is not blocked, show "Block action"
}
Message actions
You can use the logic above to create your own custom message actions that will involve user blocking.
However, the Compose SDK provides the message blocking actions out of the box.
You can enable/disable the actions via the messageOptionsTheme
from the
ChatTheme
, by setting the isBlockUserVisible
flag to true/false
on the
optionVisibility
:
ChatTheme(
messageOptionsTheme = MessageOptionsTheme.defaultTheme(
optionVisibility = MessageOptionItemVisibility(isBlockUserVisible = false),
),
) {
MessagesScreen(
viewModelFactory = factory,
// Other properties ...
)
}
Note: The default value of isBlockUserVisible
is true
, so the block/unblock
actions are enabled by default.
Displaying blocked users
Next, let’s see how we can build a custom UI that will show the list of blocked users. This will allow easier overview for the users about who they blocked, as well as provide an easy way to unblock them if needed.
Here’s a screenshot showing how the list will look like:
First, lets define the BlockedUsersList
, a Composable
that can be included
in your screen:
@Composable
fun BlockedUsersList(
viewModel: BlockedUsersViewModel = viewModel(),
onBack: () -> Unit,
) {
val blockedUsers by viewModel.blockedUsers.collectAsStateWithLifecycle()
Column {
Heading(onBack = onBack)
if (blockedUsers.isEmpty()) {
Text(
text = "There are currently no blocked users.",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
fontSize = 16.sp,
)
} else {
LazyColumn {
items(blockedUsers) { user ->
BlockedUserItem(
user = user,
onUnblockClick = { viewModel.unblockUser(user) },
)
}
}
}
}
}
@Composable
fun Heading(onBack: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
IconButton(onClick = onBack) {
Icon(
painter = painterResource(
id = R.drawable.stream_compose_ic_arrow_back
),
contentDescription = "Back",
)
}
Spacer(modifier = Modifier.size(16.dp))
Text(
text = "Blocked Users",
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
)
}
}
@Composable
fun BlockedUserItem(
user: User,
onUnblockClick: () -> Unit,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
UserAvatar(
user = user,
modifier = Modifier.size(40.dp)
)
Spacer(modifier = Modifier.size(16.dp))
Text(
modifier = Modifier.weight(1f),
text = user.name,
fontWeight = FontWeight.Bold,
fontSize = 14.sp,
)
TextButton(onClick = onUnblockClick) {
Text(
text = "Unblock",
fontSize = 12.sp,
)
}
}
}
This component displays the currently blocked users in a list, and provides an “Unblock” action, which would unblock the user and remove it from the list.
The data is populated from a BlockedUsersViewModel
. Let’s see its
implementaton next:
class BlockedUsersViewModel(
private val chatClient: ChatClient = ChatClient.instance()
) : ViewModel() {
private val _blockedUsers: MutableStateFlow<List<User>> =
MutableStateFlow(emptyList())
val blockedUsers: StateFlow<List<User>>
get() = _blockedUsers
init {
loadBlockedUsers()
}
fun loadBlockedUsers() {
viewModelScope.launch {
// Fetch blocked users
val result = chatClient
.queryBlockedUsers()
.flatMap { userBlocks ->
// Map the blocked user IDs to a full user object
val blockedUserIds = userBlocks.map(UserBlock::userId)
chatClient.queryUsers(
query = QueryUsersRequest(
filter = Filters.`in`("id", blockedUserIds),
offset = 0,
limit = blockedUserIds.size,
)
)
}
.execute()
when (result) {
is Result.Success -> {
_blockedUsers.value = result.value
}
is Result.Failure -> {
// Handle error
}
}
}
}
fun unblockUser(user: User) {
viewModelScope.launch {
val result = chatClient.unblockUser(user.id).execute()
when (result) {
is Result.Success -> {
// Remove the blocked user if the unblock was successful
_blockedUsers.update { it - user }
}
is Result.Failure -> {
// Handle error
}
}
}
}
}
In the ViewModel above, we are using the low-level client capabilities we
explained at the beginning of the cookbook.
In the UI, we also want to present the names and images of the blocked users.
To do that, we need to load the User
object based on the blocked users ID.
In the loadBlockedUsers
method, we first fetch the blocked users IDs via the
queryBlockedUsers
operation, and then we fetch the full User
objects by
calling queryUsers
, where we pass the list of blocked user IDs as a filter
condition.
The unblockUser
method is similar to the code sample we discussed above.
Summary
In this cookbook we have seen the capabilities of the Stream Chat SDK for blocking users. We have also seen how to add message actions to block and unblock users, as well as a custom UI for displaying the blocked users.