오랜만에 포스팅이긴한데 공부하느라 바빴음..ㅎ
AI챗봇 실습을 하면서 사용한 주요 기능을 정리해보려고함

1. 📦 실시간 메시지 갱신 (Isar + StreamBuilder)
StreamBuilder<List<MessageModel>>(
stream: GetIt.I<Isar>().messageModels.where().watch(fireImmediately: true),
builder: (context, snapshot) {
final messages = snapshot.data ?? [];
// 위젯 트리 빌드 완료 후 맨 아래로 자동 스크롤
WidgetsBinding.instance.addPostFrameCallback((_) async => scrollToBottom());
return buildMessageList(messages); // 메시지 리스트 렌더링
},
)
- Isar DB에서 실시간으로 데이터를 감지 (`watch`)
- 새로운 메시지가 저장되면 자동으로 UI가 업데이트되며 아래로 스크롤됨
- 빌드 완료 후 최신 메시지로 자동 스크롤
2. 🤖 Gemini API 연동 및 스트리밍 응답 처리
final model = GenerativeModel(
model: "gemini-1.5-flash",
apiKey: "YOUR_API_KEY", // Gemini API Key 입력
systemInstruction: Content.system(
"너는 GPT-4급 지능을 가진 스마트한 AI야. 사용자의 질문에 성의 있고 창의적으로 답변해.",
),
);
model.generateContentStream(promptContext).listen(
(event) async {
// 응답 텍스트가 있으면 누적
if (event.text != null) {
message += event.text!;
}
// 현재까지 받은 텍스트를 메시지 객체로 만듦
final MessageModel model = MessageModel(
isMine: false,
message: message,
date: DateTime.now(),
);
// 기존 메시지 덮어쓰기용 ID 설정
if (currentModelMessageId != null) {
model.id = currentModelMessageId!;
}
// DB에 저장
currentModelMessageId =
await isar.writeTxn<int>(() => isar.messageModels.put(model));
},
onDone: () => setState(() => isRunning = false), // 응답 완료 → 로딩 종료
onError: (e) async {
// 에러 발생 시 유저 메시지 삭제
await isar.writeTxn(() async {
return isar.messageModels.delete(currentUserMessageId!);
});
// 에러 상태 설정
setState(() {
error = e.toString();
isRunning = false;
});
},
);
- 제미나이 API를 가져옴
- 제미나이한테 `"너는 GPT-4급 지능을 가진 스마트한 AI야. 사용자의 질문에 성의 있고 창의적으로 답변해.`라고 정해둠
-> 실제로 답변의 차이가 있었음
3. 🧠 대화 맥락 구성 (최근 메시지 → 프롬프트화)
final contextMessages = await isar.messageModels.where().limit(20).findAll();
final List<Content> promptContext = contextMessages.map((e) => Content(
e.isMine ? "user" : "model", // 역할 분리
[TextPart(e.message)], // 메시지를 TextPart로 구성
)).toList();
- 최근 메세지를 기반으로 프롬프트를 구성
- 제미나이가 대화 흐름을 이해하고 자연스럽게 이어가도록 함
4. 📝 메시지 저장 (사용자 입력, Gemini 응답)
// 사용자 메시지 저장
currentUserMessageId = await isar.writeTxn(() async {
return await isar.messageModels.put(
MessageModel(
isMine: true,
message: currentPrompt,
point: myMesageCount + 1, // 포인트 시스템 등에서 활용 가능
date: DateTime.now(),
),
);
});
- 사용자의 입력 메시지를 Isar에 저장
- Gemini 응답도 동일한 방식으로 저장됨
5. 🧩 입력창 컴포넌트화 (ChatTextField)
ChatTextField(
error: error, // 입력 유효성 오류 메시지
loading: isRunning, // 로딩 상태 표시
onSend: handleSendMessage, // 전송 시 호출 함수
controller: controller, // 텍스트 입력 컨트롤러
),
- 단순한 `TextField`가 아닌 커스텀 위젯으로 구성
- 상태에 따라 로딩 중, 에러 메시지 등도 같이 보여줌
7. 📅 날짜 구분선 (DateDivider) 자동 표시
final shouldDrawDateDivider =
prevMessage == null || getStringDate(prevMessage.date) != getStringDate(message.date);
if (shouldDrawDateDivider)
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: DateDivider(date: message.date),
);
- 이전 메시지와 날짜가 다르면 날짜 구분선 삽입
- 날짜 단위로 대화 흐름을 시각적으로 구분
✅ 전체 기술 스택 요약
| 기능 | 기술/패키지 | 설명 |
| 로컬 DB | `isar`, `get_it` | 메시지 저장 및 실시간 스트림 처리 |
| AI 응답 | `google_generative_ai` | Gemini API로 대화형 응답 구현 |
| UI | `StreamBuilder`, `ListView`, `Custom Widget` | 실시간 채팅 UI |
| 상태 관리 | `StatefulWidget`, `setState` | 로딩/에러 상태 처리 |
| UX 개선 | 자동 스크롤, 날짜 구분선 | 대화 흐름을 직관적으로 표현 |
https://github.com/Leedoseo/Flutter_WidgetStudy/tree/main/ai_talk
Flutter_WidgetStudy/ai_talk at main · Leedoseo/Flutter_WidgetStudy
Contribute to Leedoseo/Flutter_WidgetStudy development by creating an account on GitHub.
github.com
'Flutter > Flutter Study' 카테고리의 다른 글
| [Flutter] Drawer사용법 (1) | 2025.06.24 |
|---|---|
| [Flutter] Rest API (0) | 2025.05.14 |
| [Flutter] 위젯 생명주기 (0) | 2025.04.22 |
| [Flutter] 웹뷰 사용해서 앱에서 웹연결 실습 간단 정리 (0) | 2025.04.17 |
| [Flutter] Basic Widget 기본 개념 (실습) (0) | 2025.04.15 |