버튼은 이름만 부른다 — 눌렀을 때 호스트가 하는 일

5 분
목표화면 강좌 4편. onTap 에는 코드가 없고 도구 이름만 있다. 그 이름이 실제로 호스트까지 갔는지는 그림으로 확인할 수 없어서, 하니스가 눌러 보고 받아 적는다.

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.take

fired 는 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.png

43, 그 아래 2 waiting, 버튼 둘, 그 아래 회색으로 called 43.

직접 돌려보기

cd content/sample/course-ui
bash verify.sh
cat captures/run.log

가져갈 것

  1. onTap: { type: 'tool', tool: ... } — 화면은 이름까지만 안다
  2. onToolCall 은 그대로 넘긴다 — 이름으로 분기하지 않는다
  3. 눌러 보는 검사 — 캡처가 못 보는 것이 있으면 로그로 남긴다

다음 편

마지막. 화면 하나가 서로 다른 모양이 되는 것 — 그리고 개수를 모르는 목록.

샘플 실행하기

makemind-academy/course_ui/
git clone https://github.com/makemind-academy/course_ui
cd course_ui
bash verify.sh
GitHub에서 열기 →