Kuikly 实战:手把手撸一个跨平台 AI 聊天助手 (ChatDemo)

项目地址:GitHub

在这个 AI 大模型满天飞的时代,谁不想拥有一个属于自己的 AI 助手呢?今天,我们就以 ChatDemo 为例,看看如何用一套 Kotlin 代码,在 Android、iOS 和 HarmonyOS 三端上通过 Kuikly Compose 完美复刻 ChatGPT 的流式对话体验。

别被 AI 的高大上吓跑了,其实拆解下来,核心逻辑就像搭积木一样简单。准备好了吗?发车!

页面结构分析

打开 ChatDemo.kt,我们仿佛看到了一位身材曼妙的"美女"------哦不,是一段结构清晰的代码。整个聊天页面 (ChatScreen) 主要由三部分组成:

  1. 顶部导航栏 (NavBar):展示标题和返回按钮,雷打不动的"天灵盖"。

  2. 聊天内容区 (MessageList):显示对话流,这是核心的"躯干"。

  3. 底部输入区 (InputArea):输入框和发送按钮,负责交互的"四肢"。

这种结构在 Kuikly Compose 中实现起来异常清爽,简直就是"所见即所得":

kotlin 复制代码
// 伪代码结构演示

Box(modifier = Modifier.fillMaxSize()) {

    Column {

        NavBar() // 1. 天灵盖

        ChatList() // 2. 躯干

        InputArea() // 3. 四肢
    }
}

Kuikly Compose 写法

页面定义

不同于之前的 BasePager + DSL,Compose 风格的页面继承自 ComposeContainer,通过 setContent 开启 Compose 的世界:

kotlin 复制代码
@Page("ChatDemo")

internal class ChatDemo : ComposeContainer() {

    override fun willInit() {

        super.willInit()

        setContent {

            ChatScreen() // 进入 Compose 的声明式 UI 世界

        }
    }
}

简单粗暴,直接入题!

组件与布局

Kuikly Compose 完美复刻了 Jetpack Compose 的开发体验,让习惯了 Android 开发的同学们倍感亲切。

1. 列表组件 (LazyColumn)

聊天记录怎么展示?LazyColumn 也就是我们常说的 RecyclerView 的"升级版"。

kotlin 复制代码
LazyColumn(

    state = listState,

    modifier = Modifier.weight(1f)

) {

    itemsIndexed(chatList) { index, message ->

        // 根据 index 判断是用户还是 AI,渲染不同的气泡

        ChatMessageItem(message, isUser = (index % 2 == 0))

    }
}

看到没?没有 Adapter,没有 ViewHolder,就是一个简单的 Lambda,数据驱动 UI,清爽!

2. 输入框 (TextField)

底部的输入框使用了 TextField,这里有一个亮点------键盘避让。Kuikly 贴心地提供了 keyboardHeightChange 修饰符:

kotlin 复制代码
TextField(

    modifier = Modifier

    .keyboardHeightChange {

        // 键盘弹起时,自动调整高度,避免遮挡输入框

        keyboardHeight = it.height
    }

)

这行代码一加,键盘弹起时的页面抖动、遮挡问题统统不见,丝般顺滑。

样式与修饰符 (Modifier)

在 Kuikly Compose 中,attr {} 变成了更强大的 Modifier 链式调用。比如首页那个好看的渐变色卡片:

kotlin 复制代码
Box(
    modifier = Modifier

    .clip(RoundedCornerShape(16.dp)) // 裁个圆角

    .background(

        // 来个渐变色

        Brush.horizontalGradient(listOf(box.startColor, box.endColor))
    )
    .clickable { ... } // 加个点击事件
)

这就是声明式 UI 的魅力,你要什么效果,直接往上"叠"就行了。

核心黑科技:跨平台流式响应

界面画好了,怎么让 AI "动"起来?ChatDemo 的核心在于流式响应 (SSE),也就是那个酷炫的"打字机"效果。这里展示了 Kuikly 强大的跨平台能力。

Android & iOS (Ktor 协程)

这两兄弟关系好,直接用 Kotlin 协程配合 Ktor 网络库,处理起来行云流水:

kotlin 复制代码
// ChatDemo.kt

val channel = response.bodyAsChannel()

while (!channel.isClosedForRead) {

    val line = channel.readUTF8Line()

    if (line.startsWith("data:")) {
        // 收到数据,更新 UI
        withContext(Dispatchers.Main) {
            chatList[msgIndex] += delta // 界面自动刷新
        }
    }
}

HarmonyOS (Native Module 桥接)

鸿蒙这位新朋友,Kuikly 也有妙招。在第三方组件Ktor还未完善对鸿蒙的支持情况下,Kuikly提供了Module 桥接机制,99% 的代码用 KMP 共享,剩下 1% 搞不定的平台特性,通过 Module 轻松桥接原生能力。通过 OhosStreamRequestModule,我们能轻松调用鸿蒙原生的 ArkTS 能力:

kotlin 复制代码
// OhosStreamRequestModule.kt

fun request(...) {

    // 呼叫鸿蒙原生层:帮我发个请求!

    toNative(true, "request", params) { retEvent ->
        // 原生层收到 SSE 数据,回传给 Kotlin
        callbackFn.invoke(eventObj)
    }
}

这就是 Kuikly 的哲学:能通用的通用,不能通用的桥接。一套架构,搞定所有平台。

细节打磨:Markdown 渲染

AI 返回的代码块、加粗字体怎么显示?直接塞进 Text 肯定不行。Kuikly 提供了 Markdown 组件:

kotlin 复制代码
Markdown(
    state = markdownState,
    colors = markdownColor(text = Color.Black), // 定制颜色
    typography = markdownTypography() // 定制排版
)

它能实时解析流式传输过来的 Markdown 文本,渲染过程毫无闪烁,稳如老狗。

总结

通过 Review ChatDemo 的源码,我们不仅学会了如何画界面,更解锁了 AI 应用开发的核心技能:

  1. Compose 写法:用 Modifier 和声明式组件构建 UI,效率起飞。

  2. 状态管理:mutableState 让数据驱动界面更新,告别繁琐的 setText

  3. 跨平台架构:Ktor 处理标准平台,Module 桥接新兴平台(鸿蒙),游刃有余。

Kuikly 已经把"地基"打好了,剩下的就看你们如何在上面盖出万丈高楼了!今天的代码 Review 就到这里,还不快去 Clone 下来跑一跑?

相关推荐
程序员-珍3 小时前
报错下载android sdk失败
android·java
齊家治國平天下5 小时前
AAOS 电源管理深度解析:休眠/唤醒/功耗优化
android·车载系统·aaos·aosp·电源管理·休眠唤醒·carpowermanager
Xzaveir5 小时前
企业号码展示不可观测怎么办:状态机、拨测事件与异常回放
android·人工智能
2501_916007475 小时前
iOS和macOS应用程序性能分析和优化工具使用综合指南
android·macos·ios·小程序·uni-app·iphone·webview
黄林晴6 小时前
Nearby Connections 重大变更:不再自动开启 Wi-Fi / 蓝牙,适配指南
android
恋猫de小郭6 小时前
Flutter 全新真 3D 实现,用 flutter_scene 能开发一个「我的世界」
android·前端·flutter
HLC++19 小时前
Linux的进程间通信
android·linux·服务器
爱笑鱼1 天前
Binder(二):AIDL 生成的 Proxy、Stub 和 Parcel 到底在做什么?
android
爱笑鱼1 天前
Binder(一):一次方法调用,究竟怎样跨进程执行?
android
壮哥_icon1 天前
【Android 系统开发】使用 BAT 脚本高效自动化管理 /system/priv-app/ 系统应用(安装与卸载)
android·运维·自动化