You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Jetpack Compose开发安卓聊天App时getStream插件频道不显示

问题:GetStream Compose聊天应用频道无法显示

我正在使用GetStream插件结合Jetpack Compose开发一款简易安卓聊天应用,已通过控制台创建了部分频道,但这些频道在应用中无法显示。参考官方教程时发现,教程中甚至未提及频道创建相关内容。

应用加载时的频道界面截图:
频道界面空白截图

以下是我的代码:

@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ForumChannelsScreen(
    navController: NavController,
    userData: UserData?,
    context: Context
) {
    // GetStream related code

    // Set-up for OfflinePlugin for offlineStorage
    val offlinePluginFactory = StreamOfflinePluginFactory(
        config = Config(
            backgroundSyncEnabled = true,
            userPresence = true,
            persistenceEnabled = true,
            uploadAttachmentsNetworkType = UploadAttachmentsNetworkType.NOT_ROAMING,
        ),
        appContext = context,
    )

    // Set-up client for API calls and with the plugin for offline storage
    val client = ChatClient.Builder("secret", context)
        .withPlugin(offlinePluginFactory)
        .logLevel(ChatLogLevel.ALL) // would change to nothing if app were to be in production :)
        .build()

    // Authenticate and connect user
    val user = User(
        id = userData!!.userId,
        name = userData.username.toString(),
        image = userData.profilePictureUrl.toString()
    )
    var token = ""
    token = if (userData.userId == "secret") {
        "secret"
    } else {
        "secret"
    }
    client.connectUser(
        user = user,
        token = token
    ).enqueue()

    // Creating a channel
    val channelClient = client.channel(channelType = "messaging", channelId = "general")

    val extraData = mutableMapOf<String, Any>(
        "name" to "Awesome channel about food"
    )
    channelClient.create(memberIds = emptyList(), extraData = extraData).enqueue() { result ->
        if (result.isSuccess) {
            val channel: Channel = result.data()
        } else {
            Log.d("ChannelError", "Something went wrong creating a channel")
        }
    }


    val selectedIndex = rememberSaveable { mutableStateOf(0)}

    Scaffold(
        modifier = Modifier.background(MaterialTheme.colorScheme.background),
        bottomBar = {
            NavigationBar(
            ) {
                NavigationBarItem(
                    icon = { Icon(
                        painter = painterResource(R.drawable.discover_temp),
                        contentDescription = "Discover"
                    ) },
                    label = { Text(text = "Discover") },
                    onClick = {
                        selectedIndex.value = 0
                        navController.navigate(Screens.DiscoverScreen.route)
                    },
                    selected = (selectedIndex.value == 0),
                    colors = NavigationBarItemDefaults
                        .colors(
                            unselectedIconColor = Orange,
                            indicatorColor = MaterialTheme.colorScheme.surfaceColorAtElevation(LocalAbsoluteTonalElevation.current)
                        )
                )
                NavigationBarItem(
                    icon = { Icon(
                        painter = painterResource(R.drawable.favorite_unfocused),
                        contentDescription = "Favourites"
                    ) },
                    label = { Text(text = "Favourite") },
                    onClick = {
                        selectedIndex.value = 0
                        navController.navigate(Screens.FavouriteScreen.route)
                    },
                    selected = (selectedIndex.value == 0),
                    colors = NavigationBarItemDefaults
                        .colors(
                            unselectedIconColor = Orange,
                            indicatorColor = MaterialTheme.colorScheme.surfaceColorAtElevation(LocalAbsoluteTonalElevation.current)
                        )
                )
                NavigationBarItem(
                    icon = { Icon(
                        painter = painterResource(R.drawable.forum_unfocused),
                        contentDescription = "Forum"
                    ) },
                    label = { Text(text = "Forum") },
                    onClick = {
                        selectedIndex.value = 1
                        navController.navigate(Screens.ForumChannelsScreen.route)
                    },
                    selected = (selectedIndex.value == 1),
                    colors = NavigationBarItemDefaults
                        .colors(
                            unselectedIconColor = Orange,
                            indicatorColor = MaterialTheme.colorScheme.surfaceColorAtElevation(LocalAbsoluteTonalElevation.current) // sets the indicator color to the same as the color of
                            // the navbar by making sure its the same tone. Tones change on elevation,
                            // so I had to set it to the same elevation as the navbar itself
                        )
                )
            }
        }
    ) {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .background(MaterialTheme.colorScheme.background),
            verticalArrangement = Arrangement.SpaceBetween
        ) {
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp),
                horizontalArrangement = Arrangement.End
            ) {
                Text(
                    text = "Choose a channel",
                    style = MaterialTheme.typography.titleSmall,
                    modifier = Modifier.weight(1f),
                    textAlign = TextAlign.Center
                )
                if(userData?.profilePictureUrl != null) {
                    AsyncImage(
                        model = userData.profilePictureUrl,
                        contentDescription = "Profile Picture",
                        modifier = Modifier
                            .size(50.dp)
                            .clip(CircleShape)
                            .clickable { navController.navigate(Screens.ProfileScreen.route) },
                        contentScale = ContentScale.Crop,
                    )
                }
            }
            ChatTheme {
                ChannelsScreen(
                    title = "",
                    isShowingSearch = true,
                    onItemClick = { channel ->
                    },
                    onBackPressed = { navController.navigate(Screens.DiscoverScreen.route)}
                )
            }

        }
    }
}

核心问题排查与修复方案

1. 频道成员关系缺失

GetStream的ChannelsScreen仅展示当前用户为成员的频道:

  • 你代码中创建频道时传入memberIds = emptyList(),导致频道无成员,当前用户自然无法看到。
  • 控制台创建的频道,需手动将当前用户ID添加到频道成员列表中。

2. 客户端初始化与生命周期错误

  • 每次Composable重组都会重新创建ChatClient、重复连接用户、重复创建频道,导致状态混乱。需将ChatClient初始化放到Application类,确保全局单例。
  • connectUser是异步操作,ChannelsScreen可能在用户连接完成前就加载,导致未获取到频道数据。需用LaunchedEffect确保异步操作完成后再加载频道。

3. 重复创建频道的问题

多次调用channelClient.create()会触发错误(频道已存在时),需先检查频道是否存在,再决定是否创建。


修复后的代码示例

第一步:全局初始化ChatClient(Application类)

class MyApp : Application() {
    lateinit var chatClient: ChatClient

    override fun onCreate() {
        super.onCreate()
        val offlinePluginFactory = StreamOfflinePluginFactory(
            config = Config(
                backgroundSyncEnabled = true,
                userPresence = true,
                persistenceEnabled = true,
                uploadAttachmentsNetworkType = UploadAttachmentsNetworkType.NOT_ROAMING,
            ),
            appContext = this,
        )

        chatClient = ChatClient.Builder("你的API_KEY", this)
            .withPlugin(offlinePluginFactory)
            .logLevel(ChatLogLevel.ALL)
            .build()
    }
}

第二步:修改Composable逻辑

@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ForumChannelsScreen(
    navController: NavController,
    userData: UserData?,
    context: Context
) {
    val chatClient = (context.applicationContext as MyApp).chatClient
    val user = userData ?: return

    // 确保用户连接和频道操作仅执行一次
    LaunchedEffect(user.userId) {
        val token = if (user.userId == "你的测试用户ID") {
            "对应的测试Token"
        } else {
            "其他用户的Token"
        }

        // 等待用户连接完成
        val connectResult = chatClient.connectUser(
            user = User(
                id = user.userId,
                name = user.username.toString(),
                image = user.profilePictureUrl.toString()
            ),
            token = token
        ).await()

        if (connectResult.isSuccess) {
            // 检查频道是否存在,不存在则创建并添加当前用户为成员
            val channelClient = chatClient.channel("messaging", "general")
            val channelState = channelClient.watch().await()
            if (!channelState.isSuccess) {
                val createResult = channelClient.create(
                    memberIds = listOf(user.userId), // 添加当前用户为频道成员
                    extraData = mutableMapOf(
                        "name" to "Awesome channel about food"
                    )
                ).await()
                if (!createResult.isSuccess) {
                    Log.d("ChannelError", "创建频道失败: ${createResult.error().message}")
                }
            }
        } else {
            Log.d("ConnectError", "用户连接失败: ${connectResult.error().message}")
        }
    }

    val selectedIndex = rememberSaveable { mutableStateOf(1) }

    Scaffold(
        modifier = Modifier.background(MaterialTheme.colorScheme.background),
        bottomBar = {
            NavigationBar {
                NavigationBarItem(
                    icon = { Icon(painterResource(R.drawable.discover_temp), "Discover") },
                    label = { Text("Discover") },
                    onClick = {
                        selectedIndex.value = 0
                        navController.navigate(Screens.DiscoverScreen.route)
                    },
                    selected = selectedIndex.value == 0,
                    colors = NavigationBarItemDefaults.colors(
                        unselectedIconColor = Orange,
                        indicatorColor = MaterialTheme.colorScheme.surfaceColorAtElevation(LocalAbsoluteTonalElevation.current)
                    )
                )
                NavigationBarItem(
                    icon = { Icon(painterResource(R.drawable.favorite_unfocused), "Favourites") },
                    label = { Text("Favourite") },
                    onClick = {
                        selectedIndex.value = 0
                        navController.navigate(Screens.FavouriteScreen.route)
                    },
                    selected = selectedIndex.value == 0,
                    colors = NavigationBarItemDefaults.colors(
                        unselectedIconColor = Orange,
                        indicatorColor = MaterialTheme.colorScheme.surfaceColorAtElevation(LocalAbsoluteTonalElevation.current)
                    )
                )
                NavigationBarItem(
                    icon = { Icon(painterResource(R.drawable.forum_unfocused), "Forum") },
                    label = { Text("Forum") },
                    onClick = {
                        selectedIndex.value = 1
                        navController.navigate(Screens.ForumChannelsScreen.route)
                    },
                    selected = selectedIndex.value == 1,
                    colors = NavigationBarItemDefaults.colors(
                        unselectedIconColor = Orange,
                        indicatorColor = MaterialTheme.colorScheme.surfaceColorAtElevation(LocalAbsoluteTonalElevation.current)
                    )
                )
            }
        }
    ) { paddingValues ->
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(paddingValues)
                .background(MaterialTheme.colorScheme.background),
            verticalArrangement = Arrangement.SpaceBetween
        ) {
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp),
                horizontalArrangement = Arrangement.End
            ) {
                Text(
                    text = "Choose a channel",
                    style = MaterialTheme.typography.titleSmall,
                    modifier = Modifier.weight(1f),
                    textAlign = TextAlign.Center
                )
                user.profilePictureUrl?.let { url ->
                    AsyncImage(
                        model = url,
                        contentDescription = "Profile Picture",
                        modifier = Modifier
                            .size(50.dp)
                            .clip(CircleShape)
                            .clickable { navController.navigate(Screens.ProfileScreen.route) },
                        contentScale = ContentScale.Crop,
                    )
                }
            }
            ChatTheme {
                ChannelsScreen(
                    title = "",
                    isShowingSearch = true,
                    onItemClick = { /* 处理频道点击逻辑 */ },
                    onBackPressed = { navController.navigate(Screens.DiscoverScreen.route) }
                )
            }
        }
    }
}

额外检查点

  • 确认控制台中创建的频道已添加当前用户ID到成员列表。
  • 查看GetStream日志(已设置ChatLogLevel.ALL),排查连接、频道加载相关错误。
  • 确保ChatTheme与ChatClient实例上下文一致,避免状态不匹配。

内容的提问来源于stack exchange,提问作者Bartek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 13:14:57