flutter_agent_kit 0.0.1
flutter_agent_kit: ^0.0.1 copied to clipboard
Flutter bindings for Google's Agent Development Kit (ADK) for Kotlin — build on-device and cloud AI agents with tool calling, from Dart.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_agent_kit/flutter_agent_kit.dart';
void main() {
runApp(const ExampleApp());
}
/// Demo app for `flutter_agent_kit`: a chat screen, one Dart-defined tool
/// (`roll_dice`), and a switch for whether the conversation survives an app
/// restart. See docs/v1-implementation-plan.md for what this is and isn't
/// meant to show.
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter_agent_kit example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const ChatPage(),
);
}
}
/// Native errors (e.g. from a misconfigured Firebase project) carry a full
/// stack trace after the first line — only the first line is useful in a
/// chat bubble.
String _firstLine(Object error) => error.toString().split('\n').first;
class _ChatMessage {
_ChatMessage.user(this.text) : isUser = true, isPending = false;
_ChatMessage.assistant() : isUser = false, text = '', isPending = true;
final bool isUser;
String text;
bool isPending;
}
class ChatPage extends StatefulWidget {
const ChatPage({super.key});
@override
State<ChatPage> createState() => _ChatPageState();
}
class _ChatPageState extends State<ChatPage> {
final _textController = TextEditingController();
final _messages = <_ChatMessage>[];
late Agent _agent;
bool _rememberChat = false;
bool _sending = false;
@override
void initState() {
super.initState();
_agent = _buildAgent();
}
Agent _buildAgent() {
return Agent(
instructions: 'You are a helpful assistant. Use the roll_dice tool when asked to roll a die.',
model: const AgentModelConfig.cloud(),
session: _rememberChat
? const AgentSessionConfig.persistent()
: const AgentSessionConfig.memory(),
tools: [
AgentTool(
name: 'roll_dice',
description: 'Rolls a standard six-sided die and returns the result.',
parametersSchema: const {'type': 'object', 'properties': {}},
execute: (args) async => {'result': Random().nextInt(6) + 1},
),
],
);
}
Future<void> _toggleRememberChat(bool value) async {
final oldAgent = _agent;
setState(() {
_rememberChat = value;
_messages.clear();
_agent = _buildAgent();
});
await oldAgent.dispose();
}
Future<void> _send() async {
final text = _textController.text.trim();
if (text.isEmpty || _sending) return;
_textController.clear();
final assistantMessage = _ChatMessage.assistant();
setState(() {
_messages.add(_ChatMessage.user(text));
_messages.add(assistantMessage);
_sending = true;
});
try {
await for (final event in _agent.send(text)) {
setState(() {
switch (event) {
case AgentTextDelta(:final text):
assistantMessage.text += text;
case AgentFinalResponse(:final text):
assistantMessage
..text = text
..isPending = false;
case AgentToolCall(:final toolName):
assistantMessage.text = 'Using $toolName…';
case AgentError(:final message):
assistantMessage
..text = 'Error: ${_firstLine(message)}'
..isPending = false;
}
});
}
} catch (error) {
setState(() {
assistantMessage
..text = 'Error: ${_firstLine(error)}'
..isPending = false;
});
} finally {
setState(() => _sending = false);
}
}
@override
void dispose() {
_agent.dispose();
_textController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('flutter_agent_kit example')),
body: Column(
children: [
SwitchListTile(
title: const Text('Remember this conversation after restart'),
value: _rememberChat,
onChanged: _sending ? null : _toggleRememberChat,
),
const Divider(height: 1),
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(12),
itemCount: _messages.length,
itemBuilder: (context, index) {
final message = _messages[index];
return Align(
alignment: message.isUser
? Alignment.centerRight
: Alignment.centerLeft,
child: Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.75,
),
margin: const EdgeInsets.symmetric(vertical: 4),
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 10,
),
decoration: BoxDecoration(
color: message.isUser
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context)
.colorScheme
.surfaceContainerHighest,
borderRadius: BorderRadius.circular(14),
),
child: Text(
message.text.isEmpty && message.isPending
? '…'
: message.text,
),
),
);
},
),
),
SafeArea(
minimum: const EdgeInsets.all(8),
child: Row(
children: [
Expanded(
child: TextField(
controller: _textController,
decoration: const InputDecoration(
hintText: 'Message',
border: OutlineInputBorder(),
),
onSubmitted: (_) => _send(),
),
),
const SizedBox(width: 8),
IconButton.filled(
onPressed: _sending ? null : _send,
icon: const Icon(Icons.send),
),
],
),
),
],
),
);
}
}