联网 App 第 2 课:班级留言板,一个标签存列表、先读后写、定时刷新(附 .aia 源码)

第 2 课 班级留言板:填昵称写留言,全班的留言按时间倒序排在下面,每 10 秒自动刷新。

下载 MessageBoard.aia 返回联网 App 专题

这一课学什么

  • 一个标签存一个列表:所有留言都放在标签 board 里,每条留言是一个字典(昵称、内容、时间)。
  • 先读后写:发送时不能直接用手机上显示的旧列表,要先从服务器取最新的,追加后再整体存回。
  • 定时刷新:用计时器每 10 秒取一次,别人发的留言自己就出来了。
班级留言板界面预览

图为由最终 .aia 执行真实积木、连真实服务端生成的布局预览,不是真机截图。

准备

服务端照常运行(和第 1 课同一个就行)。下载 MessageBoard.aia 导入,填服务地址。

自己搭的话:顶部的「服务地址」卡片和积木与第 1 课完全一样,可以把第 1 课的积木拖进背包,在这个项目里取出来。

第一步:界面(这一课新增)

组件 名字 要设置的属性
文本输入框 文本输入框_昵称 提示:你的昵称
文本输入框 文本输入框_留言 允许多行、高度 90
按钮 按钮_发送、按钮_刷新  
标签 标签_留言标题、标签_刷新状态  
列表显示框 列表显示框_留言 布局:主文字 + 副文字(布局 1)
计时器 计时器1 计时间隔 10000,先不启用

第二步:数据长什么样

服务器上标签 board 的值是一个列表,每一项是一条留言:

[
  {"name": "小红", "text": "周五的物理作业是第几页?", "time": "01-15 16:00"},
  {"name": "阿杰", "text": "第 52 页,第 3 到 6 题",   "time": "01-15 16:03"}
]

网络微数据库可以直接存列表和字典,取回来还是列表和字典,不用自己拼文本。用浏览器打开服务地址,能看到它存成的 JSON 文本。

第三步:发送——先读后写

global 待发送 = ""
global 正在请求 = false

procedure 刷新() {
  if not 正在请求 {
    正在请求 = true
    网络微数据库1.GetValue("board")
  }
}

when 按钮_发送.Click() {
  var 昵称 = trim(文本输入框_昵称.Text)
  var 内容 = trim(文本输入框_留言.Text)
  if 昵称 == "" {
    底部提示条1.Show("先填昵称,大家才知道是谁说的")
  } else if 内容 == "" {
    底部提示条1.Show("留言不能为空")
  } else if textLength(内容) > 200 {
    底部提示条1.Show(join("最多 200 字,现在 ", textLength(内容), " 字"))
  } else if 正在请求 {
    底部提示条1.Show("正在和服务器通信,稍等一下再点发送")
  } else {
    微数据库1.StoreValue("nickname", 昵称)
    待发送 = {"name": 昵称, "text": 内容, "time": 计时器1.FormatDateTime(计时器1.Now(), "MM-dd HH:mm")}
    按钮_发送.Enabled = false
    // 不能直接用手机上显示的旧列表:别人可能刚发了新留言
    刷新()
  }
}

when 网络微数据库1.GotValue(tagFromWebDB, valueFromWebDB) {
  正在请求 = false
  var 列表 = choose(islist(valueFromWebDB), valueFromWebDB, [])
  if isdict(待发送) {
    listAdd(列表, 待发送)
    while length(列表) > 100 {
      listRemove(列表, 1)
    }
    待发送 = ""
    网络微数据库1.StoreValue("board", 列表)
  }
  // 最后调用第四步的「显示」过程,把列表画出来
}

when 网络微数据库1.ValueStored() {
  按钮_发送.Enabled = true
  文本输入框_留言.Text = ""
  底部提示条1.Show("已发送")
}

为什么要绕一圈先读?设想你打开 App 时列表里有 3 条,看了一会儿,这期间小红发了第 4 条。如果你直接把手机上的 3 条加上自己的存回去,服务器上就只剩 4 条:小红那条被你覆盖了。先取最新的列表再追加,就能把小红的留言保留下来。

几个细节:

  • 「已获得值时」既处理刷新,也处理发送:用全局变量 待发送 是不是字典来区分这次读取是为了什么。
  • 服务器上还没有 board 时拿到的是空文本,用「是否为列表」判断,不是列表就当空列表。
  • 只保留最近 100 条:列表越大,每次存取越慢。
  • 正在请求 防止刷新和发送同时进行,两个请求的结果会搅在一起。

中途验证:发一条留言,电脑浏览器刷新服务地址页面,board 的值里能看到这条留言的昵称、内容、时间。

第四步:显示与自动刷新

when 计时器1.Timer() {
  if 微数据库1.GetValue("serviceUrl", "") != "" {
    网络微数据库1.GetValue("board")
  }
}

procedure 显示(列表) {
  var 项 = []
  for i = length(列表) to 1 step -1 {
    var 条 = select(列表, i)
    listAdd(项, 列表显示框_留言.CreateElement(join(dictget(条, "name", "?"), " · ", dictget(条, "time", "")), dictget(条, "text", ""), ""))
  }
  列表显示框_留言.Elements = 项
  标签_留言标题.Text = join("③ 大家的留言(", length(列表), " 条)")
}
  • 从最后一条倒着取,最新的就排在最上面。
  • 「创建列表元素」生成带主文字和副文字的一行,交给列表显示框的「元素」。
  • .aia 里的计时器事件调用的是第三步的「刷新」过程(带 正在请求 检查),这里为了让这一段单独能看懂写成了直接请求。

中途验证:用另一台手机发一条留言,10 秒内这台手机上自己出现。

断网怎么办

global 待发送 = ""
global 正在请求 = false

when 网络微数据库1.WebServiceError(message) {
  正在请求 = false
  if isdict(待发送) {
    待发送 = ""
    按钮_发送.Enabled = true
    底部提示条1.Show("发送失败,留言还在输入框里,稍后再点发送")
  }
  标签_刷新状态.Text = join("连不上服务器:", message)
}

发送失败时不清空输入框,用户写了半天的话不会丢,网络恢复后再点一次发送就行。

动手试一个局限

用两台手机,同一瞬间点发送(可以两个人数「1、2、3」一起点)。偶尔会发现只出现了一条:两台手机都取到了同样的旧列表,各自追加后写回,后写回的把先写回的覆盖了。

这是网络微数据库本身的局限:它只能整个标签一起读写,没有「往列表末尾追加一条」这种一步完成的操作。专题的验证里专门让两台手机的请求交错执行,确认确实会丢一条。

  • 班级规模、偶尔发言也可能冲突,不能把人数少当作不会丢数据的保证;
  • 人多、频繁写时,可以让每个人存自己的标签(如 board_小明),读的时候再合起来,但同一标签仍有覆盖风险;需要可靠的并发追加时,改用支持原子操作或事务的 CloudBase 等后端,见专题首页最后一节「学完以后」。

跑起来看看

  • 发一条留言:输入框清空、提示「已发送」,列表最上面是自己的留言。
  • 别的手机发了留言:最多 10 秒出现在列表里。
  • 关掉服务端再发:提示发送失败,留言还在输入框里;重新开服务端后点发送成功。
  • 关掉 App 再打开:昵称还在,自动刷新出最新留言。

常见问题

现象 原因与办法
发送后别人的留言没了 发送时直接存了手机上的旧列表,没有先读。照第三步改
列表里显示一串 {"name"…} 把字典直接放进了列表显示框,要用「创建列表元素」取出昵称和内容
列表顺序反了 循环从 1 到长度是最旧的在上面;改成从长度到 1、步长 -1
一直不自动刷新 计时器没启用:.aia 在屏幕初始化时把「启用计时」设为真
偶尔少一条留言 两人同一瞬间发送,见「动手试一个局限」
留言多了越来越慢 列表太大。本课只保留最近 100 条,可以改成 50

动手改一改

  1. 点一条留言,用对话框显示完整内容(长留言在列表里会被截断)。
  2. 自己的留言用不同颜色或在昵称后加「(我)」。

下一课

第 3 课 点击挑战排行榜:每个人只保留最好成绩,没破纪录就不写服务器,再把成绩从高到低排出名次。

用到的组件

网络微数据库 · 微数据库 · 计时器 · 列表显示框 · 底部提示条

验证范围

最终 .aia 已通过正式编辑器载入与保存重开(0 错误)、后台导入和 Android APK 编译;连真实服务端执行积木检查 25 项(先读后写保留别人的留言、同一瞬间发送会丢一条、自动刷新、只保留 100 条、断网保留输入、重启恢复等),见验证记录。真机运行与 AI 伴侣联调尚未测试。

文档反馈