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:

val chatClient = ChatClient.instance()
chatClient.blockUser(userId = "user-id").enqueue(
    onSuccess = {
        // Handle success
    },
    onError = { error ->
        // handle error
    }
)

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:

Screenshot showing the blocked users UI

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.

© Getstream.io, Inc. All Rights Reserved.