Skip to content

Commit 7d9c9a7

Browse files
authored
chore: Create version 2.9.0 (#307)
1 parent ac862ac commit 7d9c9a7

File tree

93 files changed

+11437
-4
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+11437
-4
lines changed

docs/05-tutorials/02-tutorials/03-ai-and-rag.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
Are you curious about how best to include AI in your Flutter app? In this talk, we will explore how you can use Serverpod to stream content generated by Gemini to your Flutter app in real-time. We will also dive into calling the Gemini APIs from Dart and linking them to your custom knowledge database using retrieval-augmented generation (RAG) and a vector database. We build a practical example where you can chat with the Flutter and Serverpod documentation, pulling in data from official docs, issues, and discussions on GitHub.
44

5-
<div style={{ position : 'relative', paddingBottom : '56.25%', height : '0' }}><iframe style={{ position : 'absolute', top : '0', left : '0', width : '100%', height : '100%' }} width="560" height="315" src="https://www.youtube-nocookie.com/embed/NJ9H1kmwhuE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>
5+
<div style={{ position : 'relative', paddingBottom : '56.25%', height : '0' }}><iframe style={{ position : 'absolute', top : '0', left : '0', width : '100%', height : '100%' }} width="560" height="315" src="https://www.youtube-nocookie.com/embed/NJ9H1kmwhuE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>

docusaurus.config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,9 @@ const config = {
170170
to: '/concepts/authentication/setup',
171171
},
172172
{
173-
// Moved in version 1.1.1 and 2.1.0
174-
from: ['/tutorials', '/tutorials/videos'],
175-
to: '/tutorials/first-app',
173+
// Moved in version 1.1.1, 2.1.0 and 2.9.0
174+
from: ['/tutorials', '/tutorials/videos', '/tutorials/first-app'],
175+
to: '/tutorials/tutorials/fundamentals',
176176
},
177177
{
178178
// Moved in version 1.2.0
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
---
2+
sidebar_label: 1. Creating endpoint methods
3+
---
4+
5+
# Creating endpoint methods
6+
7+
With Serverpod, calling an endpoint method in your server is as simple as calling a local method in your app. Let's create your first custom endpoint method and call it from the Flutter app. In this example, you will create a method that generates recipes from ingredients you may have in your fridge. Your server will talk with Google's Gemini API to make this magic happen. You will then call your endpoint method from the Flutter app and display the recipe.
8+
9+
:::info
10+
On the server, you can do things you don't want to do in the app, like calling an API secured by a secret key or accessing a database. The server can also do things that are impossible in the app, like sending push notifications or emails.
11+
:::
12+
13+
## Create a new project
14+
15+
Use the `serverpod create` command to create a new project. This command will generate a new project with a server, a client, and a Flutter app.
16+
17+
```bash
18+
serverpod create magic_recipe
19+
```
20+
21+
:::tip
22+
Always open the root directory of the project in your IDE. This will make it easier to navigate between the server and app packages. It will also prevent your analyzer from going out of sync when you generate code.
23+
:::
24+
25+
### Add the Gemini API to your project
26+
27+
To generate our recipes, we will use Google's free Gemini API. To use it, you must create an API key on [this page](https://aistudio.google.com/app/apikey). It's free, but you have to sign in with your Google account. Add your key to the `config/passwords.yaml` file in your project's server package. Git ignores this file, so you can safely add your API key here.
28+
29+
```yaml
30+
# config/passwords.yaml
31+
# This file is not included in the git repository. You can safely add your API key here.
32+
# The API key is used to authenticate with the Gemini API.
33+
development:
34+
gemini: '--- Your Gemini Api Key ---'
35+
```
36+
37+
Next, we add Google's Gemini package as a dependency to our server.
38+
39+
```bash
40+
$ cd magic_recipe/magic_recipe_server
41+
$ dart pub add google_generative_ai
42+
```
43+
44+
## Create a new endpoint
45+
46+
Create a new file in `magic_recipe_server/lib/src/recipes/` called `recipe_endpoint.dart`. This is where you will define your endpoint and its methods. With Serverpod, you can choose any directory structure you want to use. E.g., you could also use `src/endpoints/` if you want to go layer first or `src/features/recipes/` if you have many features.
47+
48+
<!--SNIPSTART 01-getting-started-endpoint-->
49+
```dart
50+
import 'dart:async';
51+
52+
import 'package:google_generative_ai/google_generative_ai.dart';
53+
import 'package:serverpod/serverpod.dart';
54+
55+
/// This is the endpoint that will be used to generate a recipe using the
56+
/// Google Gemini API. It extends the Endpoint class and implements the
57+
/// generateRecipe method.
58+
class RecipeEndpoint extends Endpoint {
59+
/// Pass in a string containing the ingredients and get a recipe back.
60+
Future<String> generateRecipe(Session session, String ingredients) async {
61+
// Serverpod automatically loads your passwords.yaml file and makes the passwords available
62+
// in the session.passwords map.
63+
final geminiApiKey = session.passwords['gemini'];
64+
if (geminiApiKey == null) {
65+
throw Exception('Gemini API key not found');
66+
}
67+
final gemini = GenerativeModel(
68+
model: 'gemini-1.5-flash-latest',
69+
apiKey: geminiApiKey,
70+
);
71+
72+
// A prompt to generate a recipe, the user will provide a free text input with the ingredients
73+
final prompt =
74+
'Generate a recipe using the following ingredients: $ingredients, always put the title '
75+
'of the recipe in the first line, and then the instructions. The recipe should be easy '
76+
'to follow and include all necessary steps. Please provide a detailed recipe.';
77+
78+
final response = await gemini.generateContent([Content.text(prompt)]);
79+
80+
final responseText = response.text;
81+
82+
// Check if the response is empty or null
83+
if (responseText == null || responseText.isEmpty) {
84+
throw Exception('No response from Gemini API');
85+
}
86+
87+
return responseText;
88+
}
89+
}
90+
```
91+
<!--SNIPEND-->
92+
93+
:::info
94+
For methods to be generated, they need to return a typed `Future`, where the type should be `void` `bool`, `int`, `double`, `String`, `UuidValue`, `Duration`, `DateTime`, `ByteData`, `Uri`, `BigInt`, or [serializable models](../06-concepts/02-models.md). The first parameter should be a `Session` object. You can pass any serializable types as parameters, and even use `List`, `Map`, or `Set` as long as they are typed.
95+
:::
96+
97+
Now, you need to generate the code for your new endpoint. You do this by running `serverpod generate` in the server directory of your project:
98+
99+
```bash
100+
$ cd magic_recipe/magic_recipe_server
101+
$ serverpod generate
102+
```
103+
104+
`serverpod generate` will create bindings for the endpoint and register them in the server's `generated/protocol.dart` file. It will also generate the required client code so that you can call your new `generateRecipe` method from your app.
105+
106+
:::note
107+
When writing server-side code, in most cases, you want it to be "stateless". This means you want to avoid using global or static variables. Instead, think of each endpoint method as a function that does stuff in a sub-second timeframe and returns data or a status message to your client. If you want to run more complex computations, you can schedule a [future call](../06-concepts/14-scheduling.md), but you usually shouldn't keep the connection open for longer durations. The `Session` object contains all the information you need to access the database and other features of Serverpod. It is similar to the `BuildContext` in Flutter.
108+
:::
109+
110+
## Call the endpoint from the client
111+
112+
Now that you have created the endpoint, you can call it from the Flutter app. Do this in the `magic_recipe_flutter/lib/main.dart` file. Modify the `_callHello` method to call your new endpoint method and rename it to `_callGenerateRecipe`. It should look like this; feel free to just copy and paste:
113+
114+
<!--SNIPSTART 01-getting-started-flutter-->
115+
```dart
116+
class MyHomePageState extends State<MyHomePage> {
117+
/// Holds the last result or null if no result exists yet.
118+
String? _resultMessage;
119+
120+
/// Holds the last error message that we've received from the server or null if no
121+
/// error exists yet.
122+
String? _errorMessage;
123+
124+
final _textEditingController = TextEditingController();
125+
126+
bool _loading = false;
127+
128+
void _callGenerateRecipe() async {
129+
try {
130+
setState(() {
131+
_errorMessage = null;
132+
_resultMessage = null;
133+
_loading = true;
134+
});
135+
final result =
136+
await client.recipe.generateRecipe(_textEditingController.text);
137+
setState(() {
138+
_errorMessage = null;
139+
_resultMessage = result;
140+
_loading = false;
141+
});
142+
} catch (e) {
143+
setState(() {
144+
_errorMessage = '$e';
145+
_resultMessage = null;
146+
_loading = false;
147+
});
148+
}
149+
}
150+
151+
@override
152+
Widget build(BuildContext context) {
153+
return Scaffold(
154+
appBar: AppBar(
155+
title: Text(widget.title),
156+
),
157+
body: Padding(
158+
padding: const EdgeInsets.all(16),
159+
child: Column(
160+
children: [
161+
Padding(
162+
padding: const EdgeInsets.only(bottom: 16.0),
163+
child: TextField(
164+
controller: _textEditingController,
165+
decoration: const InputDecoration(
166+
hintText: 'Enter your ingredients',
167+
),
168+
),
169+
),
170+
Padding(
171+
padding: const EdgeInsets.only(bottom: 16.0),
172+
child: ElevatedButton(
173+
onPressed: _loading ? null : _callGenerateRecipe,
174+
child: _loading
175+
? const Text('Loading...')
176+
: const Text('Send to Server'),
177+
),
178+
),
179+
Expanded(
180+
child: SingleChildScrollView(
181+
child: ResultDisplay(
182+
resultMessage: _resultMessage,
183+
errorMessage: _errorMessage,
184+
),
185+
),
186+
),
187+
],
188+
),
189+
),
190+
);
191+
}
192+
}
193+
```
194+
<!--SNIPEND-->
195+
196+
## Run the app
197+
198+
:::tip
199+
Before you start your server, ensure no other Serverpod server is running. Also, ensure that Docker containers from other Serverpod projects aren't running to avoid port conflicts. You can see and stop containers in the Docker Desktop app.
200+
:::
201+
202+
Let's try our new recipe app! First, start the server:
203+
204+
```bash
205+
$ cd magic_recipe/magic_recipe_server
206+
$ docker compose up -d
207+
$ dart bin/main.dart --apply-migrations
208+
```
209+
210+
Now, you can start the Flutter app:
211+
212+
```bash
213+
$ cd magic_recipe/magic_recipe_flutter
214+
$ flutter run -d chrome
215+
```
216+
217+
This will start the Flutter app in your browser:
218+
219+
![Example Flutter App](/img/getting-started/endpoint-chrome-result.png)
220+
221+
Try out the app by clicking the button to get a new recipe. The app will call the endpoint on the server and display the result in the app.
222+
223+
## Next steps
224+
225+
For now, you are just returning a `String` to the client. In the next section, you will create a custom data model to return structured data. Serverpod makes it easy by handling all the serialization and deserialization for you.

0 commit comments

Comments
 (0)