버튼은 이름만 부른다 — 눌렀을 때 호스트가 하는 일
5 분3편에서 값이 화면으로 들어왔다. 반대 방향이 남았다.
onTap 에 코드가 없다
{ "type": "button", "label": "Call next", "onTap": { "type": "tool", "tool": "queue.next" } },
{ "type": "button", "label": "Take a ticket", "onTap": { "type": "tool", "tool": "queue.take" } }onTap 은 { type, tool } 이다. "이 버튼을 누르면 queue.next 라는 도구를 부른다"까지가 전부고, 그 도구가 무엇을 하는지는 안 적혀 있다.
적을 수도 없다. 이 파일은 서버 강좌에서 만든 서버가 보낸 것이고, 실행은 그 서버에서 일어난다.
받는 쪽
rt.buildUI(
context: c,
// 이름이 여기로 온다. 호스트는 이 이름이 무엇인지 모른다.
onToolCall: (tool, params) async => fired.add(tool),
)1편에서 빈 함수로 뒀던 자리다. 실제 앱이라면 이 자리에 클라이언트 강좌 3편의 한 줄이 들어간다.
onToolCall: (tool, params) async {
// 이름은 화면에서 왔다. 그대로 넘긴다.
final r = await client.callTool(tool, params);
final state = jsonDecode((r.content.first as TextContent).text)
as Map<String, dynamic>;
state.forEach(rt.stateManager.set);
},tool 을 그대로 넘긴다. if (tool == 'queue.next') 같은 분기가 없다 — 넣는 순간 서버가 도구를 하나 더 만들 때마다 호스트를 고쳐야 한다.
그림으로는 확인이 안 된다
여기서 캡처가 처음으로 부족해진다. step4.png 에는 버튼 두 개가 보인다. 그런데 그 버튼이 눌렀을 때 무엇을 보내는지는 안 보인다.
onTap 을 통째로 지워도 그림은 똑같다.
그래서 하니스가 누른다.
fired.clear();
await tester.tap(find.text('Call next'));
await tester.pump(const Duration(milliseconds: 40));
await tester.tap(find.text('Take a ticket'));
await tester.pump(const Duration(milliseconds: 40));
say('pressed -> the host was asked for: ${fired.join(", ")}');pressed -> the host was asked for: queue.next, queue.takefired 는 onToolCall 이 받은 이름을 순서대로 담은 리스트다. 이 줄이 run.log 에 남고, 검사가 그걸 읽는다.
# 호스트가 도구 이름을 알고 있으면 실패
grep -rq 'queue\.next\|queue\.take' $SRC && \
die "step4: the host knows the tool names"
# 눌렀을 때 화면이 요청한 이름이 안 왔으면 실패
grep -q 'the host was asked for: queue.next, queue.take' $LOG || \
die "step4: pressing did not send the tool names the screen asked for"두 줄이 같이 있어야 뜻이 생긴다. 위만 있으면 "호스트에 이름이 없다"까지고, 아래만 있으면 이름이 호스트에 박혀 있어도 통과한다. 둘을 겹쳐야 화면이 준 이름이 그대로 갔다가 된다.
onTap 을 지우고 돌려 보면 아래 줄이 걸린다.
[5/8] step4 — the button names a tool and stops there
step4: pressing did not send the tool names the screen asked for실패는 화면에서 처리하지 않는다
queue.next 가 거절당하면 어떻게 되는가. 서버 강좌 3편의 카운터는 대기가 없으면 부르기를 거절한다.
거절도 결과다. 서버가 isError 와 이유를 돌려주고, 그게 다른 값들과 같이 상태로 들어온다.
{ "type": "text", "text": "{{notice}}", "style": { "fontSize": 15, "color": "#9ca3af" } }step4.json 아래쪽의 이 한 줄이 그 자리다. 화면 정의에 오류 처리 문법이 따로 없다 — 이유도 그냥 상태의 한 값이다.
렌더
step4 896 B, 12 lines, type "page" -> step4.png43, 그 아래 2 waiting, 버튼 둘, 그 아래 회색으로 called 43.
직접 돌려보기
cd content/sample/course-ui
bash verify.sh
cat captures/run.log가져갈 것
onTap: { type: 'tool', tool: ... }— 화면은 이름까지만 안다onToolCall은 그대로 넘긴다 — 이름으로 분기하지 않는다- 눌러 보는 검사 — 캡처가 못 보는 것이 있으면 로그로 남긴다
다음 편
마지막. 화면 하나가 서로 다른 모양이 되는 것 — 그리고 개수를 모르는 목록.
샘플 실행하기
makemind-academy/course_ui/git clone https://github.com/makemind-academy/course_ui cd course_ui bash verify.sh