import 'package:google_generative_ai/google_generative_ai.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class FlutterAIChat extends ConsumerStatefulWidget {
const FlutterAIChat({super.key});
@override
ConsumerState<FlutterAIChat> createState() => _State();
}
class _State extends ConsumerState<FlutterAIChat> {
final List<Content> _history = [];
late final ChatSession _chat;
late final GenerativeModel _model;
@override
void initState() {
super.initState();
_model = GenerativeModel(
model: 'gemini-1.5-pro',
apiKey: const String.fromEnvironment('GEMINI_KEY'),
generationConfig: GenerationConfig(
temperature: 0.7,
topP: 0.95,
maxOutputTokens: 2048,
),
);
_chat = _model.startChat(history: _history);
}
Future<void> _send(String text) async {
final resp = await _chat.sendMessage(Content.text(text));
setState(() {
_history.add(Content.model([TextPart(resp.text ?? '')]));
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _history.length,
itemBuilder: (_, i) => MessageBubble(_history[i]),
),
),
AIInputBar(onSubmit: _send),
],
),
);
}
}
import 'package:google_generative_ai/google_generative_ai.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class FlutterAIChat extends ConsumerStatefulWidget {
const FlutterAIChat({super.key});
@override
ConsumerState<FlutterAIChat> createState() => _State();
}
class _State extends ConsumerState<FlutterAIChat> {
final List<Content> _history = [];
late final ChatSession _chat;
late final GenerativeModel _model;
@override
void initState() {
super.initState();
_model = GenerativeModel(
model: 'gemini-1.5-pro',
apiKey: const String.fromEnvironment('GEMINI_KEY'),
generationConfig: GenerationConfig(
temperature: 0.7,
topP: 0.95,
maxOutputTokens: 2048,
),
);
_chat = _model.startChat(history: _history);
}
Future<void> _send(String text) async {
final resp = await _chat.sendMessage(Content.text(text));
setState(() {
_history.add(Content.model([TextPart(resp.text ?? '')]));
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _history.length,
itemBuilder: (_, i) => MessageBubble(_history[i]),
),
),
AIInputBar(onSubmit: _send),
],
),
);
}
}
import 'package:google_generative_ai/google_generative_ai.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class FlutterAIChat extends ConsumerStatefulWidget {
const FlutterAIChat({super.key});
@override
ConsumerState<FlutterAIChat> createState() => _State();
}
class _State extends ConsumerState<FlutterAIChat> {
final List<Content> _history = [];
late final ChatSession _chat;
late final GenerativeModel _model;
@override
void initState() {
super.initState();
_model = GenerativeModel(
model: 'gemini-1.5-pro',
apiKey: const String.fromEnvironment('GEMINI_KEY'),
generationConfig: GenerationConfig(
temperature: 0.7,
topP: 0.95,
maxOutputTokens: 2048,
),
);
_chat = _model.startChat(history: _history);
}
Future<void> _send(String text) async {
final resp = await _chat.sendMessage(Content.text(text));
setState(() {
_history.add(Content.model([TextPart(resp.text ?? '')]));
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _history.length,
itemBuilder: (_, i) => MessageBubble(_history[i]),
),
),
AIInputBar(onSubmit: _send),
],
),
);
}
}import 'package:google_mlkit_object_detection/google_mlkit_object_detection.dart';
import 'package:camera/camera.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'vision_ai.g.dart';
@riverpod
class VisionAINotifier extends _$VisionAINotifier {
late final ObjectDetector _detector;
@override
AsyncValue<List<DetectedObject>> build() {
_detector = ObjectDetector(
options: ObjectDetectorOptions(
mode: DetectionMode.stream,
classifyObjects: true,
multipleObjects: true,
),
);
ref.onDispose(_detector.close);
return const AsyncValue.data([]);
}
Future<void> processFrame(CameraImage frame) async {
final image = InputImage.fromBytes(
bytes: frame.planes.first.bytes,
metadata: InputImageMetadata(
size: Size(frame.width.toDouble(), frame.height.toDouble()),
rotation: InputImageRotation.rotation0deg,
format: InputImageFormat.bgra8888,
bytesPerRow: frame.planes.first.bytesPerRow,
),
);
final objects = await _detector.processImage(image);
state = AsyncValue.data(objects);
}
}
class AROverlayPainter extends CustomPainter {
final List<DetectedObject> objects;
AROverlayPainter(this.objects);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.cyanAccent.withOpacity(0.8)
..strokeWidth = 2.0
..style = PaintingStyle.stroke;
for (final obj in objects) {
canvas.drawRRect(
RRect.fromRectAndRadius(obj.boundingBox, const Radius.circular(8)),
paint,
);
for (final label in obj.labels) {
_drawLabel(canvas, label.text, obj.boundingBox.topLeft);
}
}
}
@override
bool shouldRepaint(AROverlayPainter old) => old.objects != objects;
}
import 'package:google_mlkit_object_detection/google_mlkit_object_detection.dart';
import 'package:camera/camera.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'vision_ai.g.dart';
@riverpod
class VisionAINotifier extends _$VisionAINotifier {
late final ObjectDetector _detector;
@override
AsyncValue<List<DetectedObject>> build() {
_detector = ObjectDetector(
options: ObjectDetectorOptions(
mode: DetectionMode.stream,
classifyObjects: true,
multipleObjects: true,
),
);
ref.onDispose(_detector.close);
return const AsyncValue.data([]);
}
Future<void> processFrame(CameraImage frame) async {
final image = InputImage.fromBytes(
bytes: frame.planes.first.bytes,
metadata: InputImageMetadata(
size: Size(frame.width.toDouble(), frame.height.toDouble()),
rotation: InputImageRotation.rotation0deg,
format: InputImageFormat.bgra8888,
bytesPerRow: frame.planes.first.bytesPerRow,
),
);
final objects = await _detector.processImage(image);
state = AsyncValue.data(objects);
}
}
class AROverlayPainter extends CustomPainter {
final List<DetectedObject> objects;
AROverlayPainter(this.objects);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.cyanAccent.withOpacity(0.8)
..strokeWidth = 2.0
..style = PaintingStyle.stroke;
for (final obj in objects) {
canvas.drawRRect(
RRect.fromRectAndRadius(obj.boundingBox, const Radius.circular(8)),
paint,
);
for (final label in obj.labels) {
_drawLabel(canvas, label.text, obj.boundingBox.topLeft);
}
}
}
@override
bool shouldRepaint(AROverlayPainter old) => old.objects != objects;
}
import 'package:google_mlkit_object_detection/google_mlkit_object_detection.dart';
import 'package:camera/camera.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'vision_ai.g.dart';
@riverpod
class VisionAINotifier extends _$VisionAINotifier {
late final ObjectDetector _detector;
@override
AsyncValue<List<DetectedObject>> build() {
_detector = ObjectDetector(
options: ObjectDetectorOptions(
mode: DetectionMode.stream,
classifyObjects: true,
multipleObjects: true,
),
);
ref.onDispose(_detector.close);
return const AsyncValue.data([]);
}
Future<void> processFrame(CameraImage frame) async {
final image = InputImage.fromBytes(
bytes: frame.planes.first.bytes,
metadata: InputImageMetadata(
size: Size(frame.width.toDouble(), frame.height.toDouble()),
rotation: InputImageRotation.rotation0deg,
format: InputImageFormat.bgra8888,
bytesPerRow: frame.planes.first.bytesPerRow,
),
);
final objects = await _detector.processImage(image);
state = AsyncValue.data(objects);
}
}
class AROverlayPainter extends CustomPainter {
final List<DetectedObject> objects;
AROverlayPainter(this.objects);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.cyanAccent.withOpacity(0.8)
..strokeWidth = 2.0
..style = PaintingStyle.stroke;
for (final obj in objects) {
canvas.drawRRect(
RRect.fromRectAndRadius(obj.boundingBox, const Radius.circular(8)),
paint,
);
for (final label in obj.labels) {
_drawLabel(canvas, label.text, obj.boundingBox.topLeft);
}
}
}
@override
bool shouldRepaint(AROverlayPainter old) => old.objects != objects;
}import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:uuid/uuid.dart';
part 'ai_state.freezed.dart';
part 'ai_state.g.dart';
@freezed
class AIResponse with _$AIResponse {
const factory AIResponse({
required String id,
required String text,
required DateTime createdAt,
@Default([]) List<String> citations,
@Default(0.0) double confidence,
@Default(AIModel.geminiPro) AIModel model,
}) = _AIResponse;
factory AIResponse.fromJson(Map<String, dynamic> j) =>
_$AIResponseFromJson(j);
}
@riverpod
class AIStateNotifier extends _$AIStateNotifier {
@override
AsyncValue<AIResponse?> build() => const AsyncValue.data(null);
Future<void> generate({
required String prompt,
AIModel model = AIModel.geminiPro,
}) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
final gemini = ref.read(geminiServiceProvider);
final result = await gemini.generateContent(
contents: [Content.text(prompt)],
config: GenerationConfig(
temperature: 0.8,
responseMimeType: 'application/json',
),
);
return AIResponse(
id: const Uuid().v4(),
text: result.text ?? '',
createdAt: DateTime.now(),
model: model,
confidence: _computeConfidence(result),
);
});
}
double _computeConfidence(GenerateContentResponse r) =>
r.candidates.first.safetyRatings
.map((s) => s.probability.index / 5.0)
.reduce((a, b) => (a + b) / 2);
}
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:uuid/uuid.dart';
part 'ai_state.freezed.dart';
part 'ai_state.g.dart';
@freezed
class AIResponse with _$AIResponse {
const factory AIResponse({
required String id,
required String text,
required DateTime createdAt,
@Default([]) List<String> citations,
@Default(0.0) double confidence,
@Default(AIModel.geminiPro) AIModel model,
}) = _AIResponse;
factory AIResponse.fromJson(Map<String, dynamic> j) =>
_$AIResponseFromJson(j);
}
@riverpod
class AIStateNotifier extends _$AIStateNotifier {
@override
AsyncValue<AIResponse?> build() => const AsyncValue.data(null);
Future<void> generate({
required String prompt,
AIModel model = AIModel.geminiPro,
}) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
final gemini = ref.read(geminiServiceProvider);
final result = await gemini.generateContent(
contents: [Content.text(prompt)],
config: GenerationConfig(
temperature: 0.8,
responseMimeType: 'application/json',
),
);
return AIResponse(
id: const Uuid().v4(),
text: result.text ?? '',
createdAt: DateTime.now(),
model: model,
confidence: _computeConfidence(result),
);
});
}
double _computeConfidence(GenerateContentResponse r) =>
r.candidates.first.safetyRatings
.map((s) => s.probability.index / 5.0)
.reduce((a, b) => (a + b) / 2);
}
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:uuid/uuid.dart';
part 'ai_state.freezed.dart';
part 'ai_state.g.dart';
@freezed
class AIResponse with _$AIResponse {
const factory AIResponse({
required String id,
required String text,
required DateTime createdAt,
@Default([]) List<String> citations,
@Default(0.0) double confidence,
@Default(AIModel.geminiPro) AIModel model,
}) = _AIResponse;
factory AIResponse.fromJson(Map<String, dynamic> j) =>
_$AIResponseFromJson(j);
}
@riverpod
class AIStateNotifier extends _$AIStateNotifier {
@override
AsyncValue<AIResponse?> build() => const AsyncValue.data(null);
Future<void> generate({
required String prompt,
AIModel model = AIModel.geminiPro,
}) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
final gemini = ref.read(geminiServiceProvider);
final result = await gemini.generateContent(
contents: [Content.text(prompt)],
config: GenerationConfig(
temperature: 0.8,
responseMimeType: 'application/json',
),
);
return AIResponse(
id: const Uuid().v4(),
text: result.text ?? '',
createdAt: DateTime.now(),
model: model,
confidence: _computeConfidence(result),
);
});
}
double _computeConfidence(GenerateContentResponse r) =>
r.candidates.first.safetyRatings
.map((s) => s.probability.index / 5.0)
.reduce((a, b) => (a + b) / 2);
}BecomingA FlutterAI Engineer
Join 600+ developers at the 5th edition of the largest Flutter Conference in Africa.
5 Years, 1 Community:Continuous Growth and Impact
From a WhatsApp group for Flutter devs in Lagos to Africa's biggest Flutter conference — this is our story.
What started as a WhatsApp group for Flutter devs in Nigeria quickly turned into something none of us expected...
The story continues...
The community keeps shipping. The next chapter is yours.
— The Event
Friday & Saturday
November 6th & 7th, 2026
Countdown
Zone Tech Park
Off Gbagada Express, Gbagada, Lagos
What's on the schedule
A line up of sessions that will literally equip you to become A Flutter AI engineer.
Using Trigonometry, Randomness & Math in Advanced Flutter Animations
Olumide Olubosede
Keynote: David Adegoke
David Adegoke
The Invisible Features: Building Trust & Security into Flutter Apps
Oladapo Olatubosun
One Language, Full Stack: Agentive Flutter UI with genui_catalog + Serverpod
Omar Farouk
Building Your First AI-Powered Solution
Mary Olasupo
Designing Flutter Apps for Distributed Systems
Nikki Eke
Your Flutter App Is Only Half the Product: AI Backends That Scale
Ifeoluwa Obayemi
Release with Confidence: AI-Assisted Releases with CodeRabbit & Codemagic
Ibrahim Sakariyah
The One-Man StartUp: Building Systems to Grow an Idea with AI
Favour Mustapha
Design Systems in the Age of AI Agents
Oluwaseun Odunlade
My Windows App Can Think, Hear and See
Daniel Oluremi
Beyond Beautiful UIs: Building the Next Generation of Flutter Applications with Emerging Tech
Victor Mba
Routes as Values: Navigation as Typed State
Samuel Abada
Offline-First Flutter: Building Apps That Keep Working Without the Internet
Christian Emeka
Building Interactive CLI Apps with Dart
Lucky Ebere
The Aristocracy of Technical Debt
Olaniyan Wisdom
FlutterBytes Speakers so far…
Engineers, founders, and Flutter enthusiasts who've taken the stage across all editions.








Daniel Oluremi
Mobile/Desktop & AI Engineer
Sumeru (Edutech)
Wheel spins in 5s
What happens at FlutterBytes
doesn't end at FlutterBytes
The impact of each edition always outlasts the event day, and the evidence abounds.
2025


2024


2023


2022


FlutterBytes sponsors
FlutterBytes Conference has been sponsored by some of the top companies in Africa and the world — companies that invest in developers because they know it's the best bet.
Partner with us
Become a sponsor
Reach 600+ Flutter engineers, founders and tech leaders at Africa's premier mobile conference.
The Flutter Bytes
The people with the highest commits to the FlutterBytes Conferences
No matter what, AI can't replace or replicate the efforts of the organising team












Are you ready for the next level?
Join 600+ Flutter Devs this October. Don't miss out on a rare opportunity for incredible growth.














