Generative AI in Flutter Build with Gemini

Axiftaj
6 min readMar 4, 2024

--

Introduction: In this tutorial, we’ll create a chat interface in Flutter that communicates with a Generative AI model using the google_generative_aipackage. We'll guide you through each step of the process, explaining the code along the way.

1. Generating API Key: Before we proceed, you’ll need an API key to use the Gemini AI model. Follow these steps to generate an API key:

  • Visit the Google AI Studio API Key page.
  • Sign in with your Google account if prompted.
  • If you haven’t already created an API key, click on the “Create Key” button.
  • Copy the generated API key.
  • Replace 'YOUR_API_KEY' with the copied API key in the _ChatWidgetStateclass where it's defined as static const _apiKey = 'YOUR_API_KEY';.

This step ensures that you have a valid API key to authenticate your requests to the Gemini AI model.

2. Setting up Dependencies: First, ensure you have the necessary dependencies listed in your pubspec.yaml file:

dependencies:
flutter:
sdk: flutter
google_generative_ai: ^0.2.0
flutter_markdown: ^0.6.19

3. Importing Packages: Import the required packages in your Dart file:

import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:google_generative_ai/google_generative_ai.dart';

4. Creating the Chat Screen: Create a stateful widget called ChatScreen. This widget will serve as the main entry point for our chat interface.

class ChatScreen extends StatefulWidget {
const ChatScreen({Key? key, required this.title});
  final String title;  @override
State<ChatScreen> createState() => _ChatScreenState();
}

5. Implementing the Chat Screen State: In the _ChatScreenState class, override the build method to define the UI of the chat screen. Use a Scaffold widget with an AppBar and a ChatWidget as the body.

class _ChatScreenState extends State<ChatScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: const ChatWidget(),
);
}
}

6. Creating the Chat Widget: Define a stateful widget called ChatWidget. This widget will contain the chat messages and the input field.

class ChatWidget extends StatefulWidget {
const ChatWidget({Key? key});
  @override
State<ChatWidget> createState() => _ChatWidgetState();
}

7. Implementing the Chat Widget State: Inside the _ChatWidgetState class, initialize the necessary variables and controllers in the initState method. Set up a GenerativeModel and start a chat session.

class _ChatWidgetState extends State<ChatWidget> {
late final GenerativeModel _model;
late final ChatSession _chat;
final ScrollController _scrollController = ScrollController();
final TextEditingController _textController = TextEditingController();
final FocusNode _textFieldFocus = FocusNode();
bool _loading = false;
  static const _apiKey = 'YOUR_API_KEY';  @override
void initState() {
super.initState();
_model = GenerativeModel(
model: 'gemini-pro',
apiKey: _apiKey,
);
_chat = _model.startChat();
}

8. Building the Chat UI: In the build method of _ChatWidgetState, define the UI for displaying chat messages and the input field. Use a ListView.builderto display the chat history and a TextField for user input.

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: _apiKey.isNotEmpty
? ListView.builder(
controller: _scrollController,
itemBuilder: (context, idx) {
// Display chat messages using MessageWidget
},
itemCount: _chat.history.length,
)
: ListView(
children: const [
Text('No API key found. Please provide an API Key.'),
],
),
),
// Input field for sending messages
],
),
);
}

9. Sending Messages:

To implement the _sendChatMessage method, we need to:

  • Get the message from the text controller.
  • Send the message to the Generative AI model using _chat.sendMessage.
  • Update the chat history with the response.
  • Scroll to the latest message.
  • Handle any errors that may occur during the process.

Here’s the code for implementing the _sendChatMessage method:

Future<void> _sendChatMessage(String message) async {
setState(() {
_loading = true;
});
  try {
var response = await _chat.sendMessage(Content.text(message));
var text = response.text;
if (text == null) {
_showError('No response from API.');
return;
} else {
setState(() {
_loading = false;
_scrollDown();
});
}
} catch (e) {
_showError(e.toString());
setState(() {
_loading = false;
});
}
}

10. Displaying Chat Messages:

To create the MessageWidget widget, we need to:

  • Determine the alignment of the message based on whether it is from the user or the AI model.
  • Format the message according to the desired UI design.

Here’s the code for creating the MessageWidget widget:

class MessageWidget extends StatelessWidget {
final String text;
final bool isFromUser;
  const MessageWidget({
Key? key,
required this.text,
required this.isFromUser,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: isFromUser ? MainAxisAlignment.end : MainAxisAlignment.start,
children: [
Flexible(
child: Container(
constraints: const BoxConstraints(maxWidth: 600),
decoration: BoxDecoration(
color: isFromUser ? Colors.blue : Colors.green,
borderRadius: BorderRadius.circular(18),
),
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20),
margin: const EdgeInsets.only(bottom: 8),
child: Text(
text,
style: TextStyle(color: Colors.white),
),
),
),
],
);
}
}

In the MessageWidget widget, the isFromUser parameter determines the alignment of the message. If isFromUser is true, the message is aligned to the right (indicating it's from the user), otherwise, it's aligned to the left (indicating it's from the AI model). The message is displayed inside a Container with rounded corners and a background color (blue for user messages and green for AI messages). The Text widget inside the Containerdisplays the message text in white color.

Conclusion: In this tutorial, we’ve covered the process of building a chat interface in Flutter that communicates with a Generative AI model. We explained each step, from setting up dependencies to implementing functionality. With this knowledge, you can create interactive chat interfaces powered by Generative AI in your Flutter applications.

Final Code:

import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:google_generative_ai/google_generative_ai.dart';
class ChatScreen extends StatefulWidget {
const ChatScreen({super.key, required this.title});
final String title; @override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: const ChatWidget(),
);
}
}
class ChatWidget extends StatefulWidget {
const ChatWidget({super.key});
@override
State<ChatWidget> createState() => _ChatWidgetState();
}
class _ChatWidgetState extends State<ChatWidget> {
late final GenerativeModel _model;
late final ChatSession _chat;
final ScrollController _scrollController = ScrollController();
final TextEditingController _textController = TextEditingController();
final FocusNode _textFieldFocus = FocusNode();
bool _loading = false;
static const _apiKey = 'AIzaSyCNPXTgr6aDQyy2GJgunSUdaLJ7gF1rwpg';
@override
void initState() {
super.initState();
_model = GenerativeModel(
model: 'gemini-pro',
apiKey: _apiKey,
);
_chat = _model.startChat();
}
void _scrollDown() {
WidgetsBinding.instance.addPostFrameCallback(
(_) => _scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(
milliseconds: 750,
),
curve: Curves.easeOutCirc,
),
);
}
@override
Widget build(BuildContext context) {
var textFieldDecoration = InputDecoration(
contentPadding: const EdgeInsets.all(15),
hintText: 'Enter a prompt...',
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
Radius.circular(14),
),
borderSide: BorderSide(
color: Theme.of(context).colorScheme.secondary,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: const BorderRadius.all(
Radius.circular(14),
),
borderSide: BorderSide(
color: Theme.of(context).colorScheme.secondary,
),
),
);
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: _apiKey.isNotEmpty
? ListView.builder(
controller: _scrollController,
itemBuilder: (context, idx) {
var content = _chat.history.toList()[idx];
var text = content.parts
.whereType<TextPart>()
.map<String>((e) => e.text)
.join('');
return MessageWidget(
text: text,
isFromUser: content.role == 'user',
);
},
itemCount: _chat.history.length,
)
: ListView(
children: const [
Text('No API key found. Please provide an API Key.'),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 25,
horizontal: 15,
),
child: Row(
children: [
Expanded(
child: TextField(
autofocus: true,
focusNode: _textFieldFocus,
decoration: textFieldDecoration,
controller: _textController,
onSubmitted: (String value) {
_sendChatMessage(value);
},
),
),
const SizedBox.square(
dimension: 15,
),
if (!_loading)
IconButton(
onPressed: () async {
_sendChatMessage(_textController.text);
},
icon: Icon(
Icons.send,
color: Theme.of(context).colorScheme.primary,
),
)
else
const CircularProgressIndicator(),
],
),
),
],
),
);
}
Future<void> _sendChatMessage(String message) async {
setState(() {
_loading = true;
});
try {
var response = await _chat.sendMessage(
Content.text(message),
);
var text = response.text;
if (text == null) {
_showError('No response from API.');
return;
} else {
setState(() {
_loading = false;
_scrollDown();
});
}
} catch (e) {
_showError(e.toString());
setState(() {
_loading = false;
});
} finally {
_textController.clear();
setState(() {
_loading = false;
});
_textFieldFocus.requestFocus();
}
}
void _showError(String message) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Something went wrong'),
content: SingleChildScrollView(
child: SelectableText(message),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('OK'),
)
],
);
},
);
}
}
class MessageWidget extends StatelessWidget {
final String text;
final bool isFromUser;
const MessageWidget({
super.key,
required this.text,
required this.isFromUser,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment:
isFromUser ? MainAxisAlignment.end : MainAxisAlignment.start,
children: [
Flexible(
child: Container(
constraints: const BoxConstraints(maxWidth: 600),
decoration: BoxDecoration(
color: isFromUser
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(18),
),
padding: const EdgeInsets.symmetric(
vertical: 15,
horizontal: 20,
),
margin: const EdgeInsets.only(bottom: 8),
child: MarkdownBody(
selectable: true,
data: text,
),
),
),
],
);
}
}

Demo Preview

--

--

Axiftaj
Axiftaj

Written by Axiftaj

Hello, this is Asif Taj a tech enthusiasts providing the quality services for the Android and iOS apps development, UI/UX Research and Designs, and Video ads.

No responses yet