Last updated: October 26, 2026
After completing this codelab, students will be able to:
campus_notify or the Week 7 refactor). This codelab adds one AI feature to an existing project.AI features enable mobile apps to communicate with cloud AI models to provide intelligent services to users:
+----------------+ HTTPS/wss +----------------+
| Flutter UI | <-----------------> | AI API Layer |
| (Chat, OCR, | (Dio/http) | (LLM, OCR, |
| STT, Image) | | STT, Vision) |
+----------------+ +----------------+
| |
| API Key (secure storage) | Response
v v
+----------------+ Optional +----------------+
| Local Cache | <-----------------> | Backend |
| (Hive/SQLite) | (batching) | (auth, log) |
+----------------+ +----------------+
flutter_secure_storage to store tokens/keys| Component | Example | Purpose |
|---|---|---|
| System prompt | "You are a friendly campus assistant, answer in Indonesian, max 2 sentences" | Set persona and boundaries |
| User prompt | "Class schedule on Monday?" | User input |
| Context | Schedule data, user profile, location | Make response relevant |
| Limits | Do not share other users' private data | Privacy and security |
Tips: Keep prompts short and specific, translate output if needed, and validate output before showing to user.
Add to pubspec.yaml:
dependencies:
flutter:
sdk: flutter
http: ^1.2.0
flutter_secure_storage: ^9.0.0
riverpod: ^2.5.0
Create lib/ai/ai_service.dart:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class AiService {
final _storage = const FlutterSecureStorage();
final _apiUrl = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent';
Future<String> ask(String question) async {
final apiKey = await _storage.read(key: 'ai_api_key');
if (apiKey == null || apiKey.isEmpty) {
throw Exception('API key not available. Please set it in settings.');
}
final response = await http.post(
Uri.parse('$_apiUrl?key=$apiKey'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'contents': [{
'parts': [{'text': question}]
}]
}),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return data['candidates'][0]['content']['parts'][0]['text'];
} else {
throw Exception('Failed to call AI API: ${response.statusCode}');
}
}
Future<void> saveApiKey(String key) async {
await _storage.write(key: 'ai_api_key', value: key);
}
}
Create lib/features/chat_screen.dart with streaming response and loading indicator.
image_pickerrecord or just_audiofinal result = await aiService.recognizeText(imageFile);
final text = await aiService.speechToText(audioFile);
| Risk | Mitigation |
|---|---|
| User data leakage | Never send sensitive data to AI API |
| Cost spike | Limit requests per user/day, use local cache |
| API error / down | Show friendly message, retry option, offline mode |
| Irrelevant response | Apply validation and fallback to local data |
try {
final response = await aiService.ask(question);
} catch (e) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('AI Service Busy'),
content: const Text('Try again later or use offline mode.'),
),
);
}
docs/ai-log.md.flutter analyze + flutter test in README.10-week-10-ai-feature-integration/ with lib/, docs/, README.md, screenshots/.