<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Serverpod</title>
    <description>The latest articles on DEV Community by Serverpod (serverpod).</description>
    <link>https://dev.to/serverpod</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F13615%2F18579d89-a0fd-41da-8c57-23560db5b0c8.png</url>
      <title>DEV Community: Serverpod</title>
      <link>https://dev.to/serverpod</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/serverpod"/>
    <language>en</language>
    <item>
      <title>Type-safe everything - Eliminate manual API glue code</title>
      <dc:creator>Jamiu Okanlawon</dc:creator>
      <pubDate>Thu, 01 Oct 2026 09:28:14 +0000</pubDate>
      <link>https://dev.to/serverpod/type-safe-everything-eliminate-manual-api-glue-code-mhn</link>
      <guid>https://dev.to/serverpod/type-safe-everything-eliminate-manual-api-glue-code-mhn</guid>
      <description>&lt;p&gt;Every Flutter app that talks to a backend needs some code you never really wanted to write. That includes an endpoint path, an HTTP request, JSON parsing, and a client model that must match the data defined on the server. None of it is the feature, but all of it has to stay correct.&lt;/p&gt;

&lt;p&gt;When you maintain the code that connects to the server by hand, it has to change with the API. Add three fields to a response, and you'll need to update the client model and parsing code before Flutter can use them. Each edit is small, but the server response, client model, and parser all have to stay in sync.&lt;/p&gt;

&lt;p&gt;Serverpod, an open-source backend written in Dart for Flutter, takes a different approach. You define each model once, and Serverpod generates the model classes and serialization code for both your server and Flutter app. It also generates type-safe client methods from your endpoints. Those generated types let the Dart analyzer catch type errors before you run the app.&lt;/p&gt;

&lt;p&gt;In this article, we'll build a ride fare estimator, call it from Flutter, and then modify its API. We'll see which parts of that change we need to write, which parts Serverpod generates, and how the analyzer catches a mismatch.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Serverpod connects the server and Flutter
&lt;/h2&gt;

&lt;p&gt;For our fare estimator, the app sends a pickup location and a destination. The server calculates the price and returns it with a currency code for Flutter to display.&lt;/p&gt;

&lt;p&gt;A model describes the data your app sends or receives. An endpoint contains the Dart methods that your app can call on the server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj9axz9j0nnuy4wfr98n5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj9axz9j0nnuy4wfr98n5.jpg" alt="type-safe-serverpod-illustration" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The serialization code turns Dart objects into a format that can be sent between the app and server. It turns received data back into Dart objects. Flutter calls the endpoint through a generated client method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before we start
&lt;/h2&gt;

&lt;p&gt;If you haven't installed Serverpod yet, follow the &lt;a href="https://docs.serverpod.dev/installation" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt;. Then create a project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;serverpod create tripquote
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the recommended defaults during setup.&lt;/p&gt;

&lt;p&gt;The project contains three packages. Here are the parts we'll use, including the fare files we'll add next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tripquote/
├── tripquote_server/lib/src/fare/   # Models and endpoint we'll add
│   ├── location.spy.yaml
│   ├── fare_estimate.spy.yaml
│   └── fare_endpoint.dart
├── tripquote_client/               # Generated client code
└── tripquote_flutter/lib/main.dart # Flutter code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the &lt;code&gt;tripquote&lt;/code&gt; directory, then start the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;tripquote
serverpod start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep it running while you follow the tutorial. Serverpod watches your models and endpoints and regenerates the connecting code when they change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the fare estimator
&lt;/h2&gt;

&lt;p&gt;The fare estimator needs a pickup location and a destination. Start by defining a &lt;code&gt;Location&lt;/code&gt; &lt;a href="https://docs.serverpod.dev/concepts/data-and-the-database/models" rel="noopener noreferrer"&gt;model&lt;/a&gt; for both.&lt;/p&gt;

&lt;p&gt;Model definitions go in &lt;code&gt;.spy.yaml&lt;/code&gt; files. Create &lt;code&gt;tripquote_server/lib/src/fare/location.spy.yaml&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;### A geographic point.&lt;/span&gt;
&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Location&lt;/span&gt;
&lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;### Degrees north of the equator.&lt;/span&gt;
  &lt;span class="na"&gt;latitude&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;double&lt;/span&gt;
  &lt;span class="c1"&gt;### Degrees east of the prime meridian.&lt;/span&gt;
  &lt;span class="na"&gt;longitude&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;double&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, define the estimate that the server will return. Create &lt;code&gt;tripquote_server/lib/src/fare/fare_estimate.spy.yaml&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;### What a ride is expected to cost.&lt;/span&gt;
&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;FareEstimate&lt;/span&gt;
&lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;### The estimated fare.&lt;/span&gt;
  &lt;span class="na"&gt;fare&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;double&lt;/span&gt;
  &lt;span class="c1"&gt;### ISO currency code, such as USD.&lt;/span&gt;
  &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these models in place, let’s create an &lt;a href="https://docs.serverpod.dev/concepts/endpoints-and-apis" rel="noopener noreferrer"&gt;endpoint&lt;/a&gt; to calculate the estimate. We'll use a base fare of USD 2.50 plus USD 1.20 per kilometer.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;tripquote_server/lib/src/fare/fare_endpoint.dart&lt;/code&gt; with the following code. The helper functions calculate the distance and round the fare to two decimal places:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:math'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:serverpod/serverpod.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'../generated/protocol.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;/// Estimates what a ride will cost.&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FareEndpoint&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;Endpoint&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;/// Returns an estimate for a ride from [pickup] to [destination].&lt;/span&gt;
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FareEstimate&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;estimateFare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="n"&gt;pickup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;baseFare&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;pricePerKm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;distanceKm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_distanceKm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pickup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;fare&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baseFare&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;distanceKm&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pricePerKm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;FareEstimate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;fare:&lt;/span&gt; &lt;span class="n"&gt;_round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fare&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nl"&gt;currency:&lt;/span&gt; &lt;span class="s"&gt;'USD'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;/// The great-circle distance between two points, in kilometers.&lt;/span&gt;
&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;_distanceKm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;earthRadiusKm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;6371.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;latitudeDelta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_toRadians&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;latitude&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;longitudeDelta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_toRadians&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latitudeDelta&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_toRadians&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
          &lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_toRadians&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
          &lt;span class="n"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;longitudeDelta&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;earthRadiusKm&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;asin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;_toRadians&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;degrees&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;degrees&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;_round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;round&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://docs.serverpod.dev/concepts/endpoints-and-apis/sessions" rel="noopener noreferrer"&gt;Session&lt;/a&gt; parameter gives the endpoint access to server features such as logging and the database. It stays on the server, so Flutter only needs to send the two locations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Call the endpoint from Flutter
&lt;/h2&gt;

&lt;p&gt;When you save the endpoint from the previous section, Serverpod generates &lt;code&gt;client.fare.estimateFare&lt;/code&gt;. It accepts two &lt;code&gt;Location&lt;/code&gt; objects and returns a &lt;code&gt;Future&amp;lt;FareEstimate&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The template already sets up &lt;code&gt;client&lt;/code&gt; in &lt;code&gt;tripquote_flutter/lib/client.dart&lt;/code&gt;. Keep that file unchanged.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;tripquote_flutter/lib/screens/fare_screen.dart&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:tripquote_client/tripquote_client.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'../client.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FareScreen&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FareScreen&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FareScreen&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_FareScreenState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_FareScreenState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FareScreen&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;late&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FareEstimate&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_estimate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;initState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;_estimate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fare&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;estimateFare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;latitude:&lt;/span&gt; &lt;span class="mf"&gt;6.5244&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;longitude:&lt;/span&gt; &lt;span class="mf"&gt;3.3792&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;latitude:&lt;/span&gt; &lt;span class="mf"&gt;6.4281&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;longitude:&lt;/span&gt; &lt;span class="mf"&gt;3.4219&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;FutureBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FareEstimate&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;future:&lt;/span&gt; &lt;span class="n"&gt;_estimate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;builder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hasError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Could not load the fare.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;

          &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;estimate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;estimate&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CircularProgressIndicator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;

          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fareLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;estimate&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;fareLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FareEstimate&lt;/span&gt; &lt;span class="n"&gt;estimate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${estimate.currency}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;${estimate.fare}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;tripquote_flutter/lib/main.dart&lt;/code&gt;, replace the greeting-screen import with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'screens/fare_screen.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;MyHomePage&lt;/code&gt;, replace &lt;code&gt;body: const GreetingsScreen(),&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FareScreen&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;MyApp&lt;/code&gt;, change the home-page title:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="nl"&gt;home:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MyHomePage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="s"&gt;'Fare estimate'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the rest of &lt;code&gt;main.dart&lt;/code&gt;, including &lt;code&gt;await initializeClient()&lt;/code&gt;, unchanged.&lt;/p&gt;

&lt;p&gt;The call to &lt;code&gt;client.fare.estimateFare&lt;/code&gt; sends the pickup and destination to the server. The returned &lt;code&gt;FareEstimate&lt;/code&gt; has typed fields, so our &lt;code&gt;fareLabel&lt;/code&gt; function can read &lt;code&gt;currency&lt;/code&gt; and &lt;code&gt;fare&lt;/code&gt; directly.&lt;/p&gt;

&lt;p&gt;Keep the server running and hot restart the Flutter app by pressing &lt;strong&gt;R&lt;/strong&gt; in the &lt;code&gt;serverpod start&lt;/code&gt; terminal. A successful request for these locations displays:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USD 16.54
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't write an HTTP request, specify an endpoint path, or maintain a matching model in Flutter. Serverpod generates the code needed to transfer the data between the app and server.&lt;/p&gt;

&lt;p&gt;The endpoint's documentation also appears in the generated client. Hover over &lt;code&gt;estimateFare&lt;/code&gt; in your editor to read the description from the server code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add more data to the response
&lt;/h2&gt;

&lt;p&gt;Suppose the app now needs to show a price range and tell the rider when surge pricing applies. It should still show a single fare when surge pricing does not apply.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try replacing the fare
&lt;/h3&gt;

&lt;p&gt;A first approach is to replace &lt;code&gt;fare&lt;/code&gt; with two range fields. Replace the contents of &lt;code&gt;tripquote_server/lib/src/fare/fare_estimate.spy.yaml&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;### What a ride is expected to cost.&lt;/span&gt;
&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;FareEstimate&lt;/span&gt;
&lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;fareLow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;double&lt;/span&gt;
  &lt;span class="na"&gt;fareHigh&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;double&lt;/span&gt;
  &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the model, leaving the endpoint and Flutter code unchanged. Wait for &lt;code&gt;serverpod start&lt;/code&gt; to process the change.&lt;/p&gt;

&lt;p&gt;The Dart analyzer now reports errors in the server endpoint and the Flutter app:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foqrbl4yvxhg9npidoap2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foqrbl4yvxhg9npidoap2.jpg" alt="type-safe-analyzer-error" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now update the endpoint to match the model. For this example, set the low price 10% below the estimate and the high price 15% above it. In &lt;code&gt;tripquote_server/lib/src/fare/fare_endpoint.dart&lt;/code&gt;, keep the distance and fare calculations inside &lt;code&gt;estimateFare&lt;/code&gt;. Replace only its return statement with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;FareEstimate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;fareLow:&lt;/span&gt; &lt;span class="n"&gt;_round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fare&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;fareHigh:&lt;/span&gt; &lt;span class="n"&gt;_round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fare&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;currency:&lt;/span&gt; &lt;span class="s"&gt;'USD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save both files and wait for &lt;code&gt;serverpod start&lt;/code&gt; to finish regenerating. Leave the Flutter code unchanged.&lt;/p&gt;

&lt;p&gt;The Dart analyzer now flags &lt;code&gt;estimate.fare&lt;/code&gt; in &lt;code&gt;fareLabel&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The getter 'fare' isn't defined for the type 'FareEstimate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated &lt;code&gt;FareEstimate&lt;/code&gt; no longer has &lt;code&gt;fare&lt;/code&gt;, but our Flutter code still reads that field. The analyzer reports the mismatch before we run the updated app.&lt;/p&gt;

&lt;p&gt;The first attempt removed a value the app still needs for ordinary pricing. We'll keep &lt;code&gt;fare&lt;/code&gt; and add the extra information alongside it instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep the fare and add optional details
&lt;/h3&gt;

&lt;p&gt;Replace the contents of &lt;code&gt;tripquote_server/lib/src/fare/fare_estimate.spy.yaml&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;### What a ride is expected to cost.&lt;/span&gt;
&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;FareEstimate&lt;/span&gt;
&lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;### The estimated fare, adjusted when surge applies.&lt;/span&gt;
  &lt;span class="na"&gt;fare&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;double&lt;/span&gt;
  &lt;span class="c1"&gt;### ISO currency code, such as USD.&lt;/span&gt;
  &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
  &lt;span class="c1"&gt;### The low end of the fare range.&lt;/span&gt;
  &lt;span class="na"&gt;fareLow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;double?&lt;/span&gt;
  &lt;span class="c1"&gt;### The high end of the fare range.&lt;/span&gt;
  &lt;span class="na"&gt;fareHigh&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;double?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new fields have values only when surge pricing applies. The &lt;code&gt;?&lt;/code&gt; allows them to be &lt;code&gt;null&lt;/code&gt; otherwise. The response always includes &lt;code&gt;fare&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We'll use &lt;code&gt;priceSurge&lt;/code&gt; to turn surge pricing on or off in this example. When it is &lt;code&gt;true&lt;/code&gt;, the server doubles the fare before calculating the range.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;tripquote_server/lib/src/fare/fare_endpoint.dart&lt;/code&gt;, replace the &lt;code&gt;FareEndpoint&lt;/code&gt; class with the code below. Include the &lt;code&gt;priceSurge&lt;/code&gt; line above the class, and keep the imports and helper functions unchanged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;priceSurge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;/// Estimates what a ride will cost.&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FareEndpoint&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;Endpoint&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;/// Returns an estimate for a ride from [pickup] to [destination].&lt;/span&gt;
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FareEstimate&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;estimateFare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="n"&gt;pickup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;baseFare&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;pricePerKm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;distanceKm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_distanceKm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pickup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;fare&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baseFare&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;distanceKm&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pricePerKm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;priceSurge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;FareEstimate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;fare:&lt;/span&gt; &lt;span class="n"&gt;_round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fare&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nl"&gt;currency:&lt;/span&gt; &lt;span class="s"&gt;'USD'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;surgeMultiplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;surgeFare&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fare&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;surgeMultiplier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;FareEstimate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;fare:&lt;/span&gt; &lt;span class="n"&gt;_round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;surgeFare&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;currency:&lt;/span&gt; &lt;span class="s"&gt;'USD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;fareLow:&lt;/span&gt; &lt;span class="n"&gt;_round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;surgeFare&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;fareHigh:&lt;/span&gt; &lt;span class="n"&gt;_round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;surgeFare&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the model and endpoint files. After regeneration, &lt;code&gt;estimate.fare&lt;/code&gt; is valid again. The two new fields are also available in Flutter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6x4kxt1wfbpnlrbpucx6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6x4kxt1wfbpnlrbpucx6.jpg" alt="type-safe-auto-fields" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Display the new fields in Flutter
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;tripquote_flutter/lib/main.dart&lt;/code&gt;, replace only &lt;code&gt;fareLabel&lt;/code&gt;. Keep the client setup, request, and widget unchanged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;fareLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FareEstimate&lt;/span&gt; &lt;span class="n"&gt;estimate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;estimate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fareLow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;estimate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fareHigh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${estimate.currency}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;$low&lt;/span&gt;&lt;span class="s"&gt; to &lt;/span&gt;&lt;span class="si"&gt;$high&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Surge pricing'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${estimate.currency}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;${estimate.fare}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When both range values are present, the display shows the range and indicates that surge pricing applies. Otherwise, it shows the single fare.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;priceSurge&lt;/code&gt; set to &lt;code&gt;false&lt;/code&gt;, restart the server and Flutter app. The screen shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USD 16.54
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set &lt;code&gt;priceSurge&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;, then restart both again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USD 29.77 to 38.05
Surge pricing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restarting the server applies the changed flag. Restarting Flutter makes a new request.&lt;/p&gt;

&lt;p&gt;The request stayed the same. We updated the model definition, pricing logic, and Flutter display. Serverpod generated the corresponding model classes and conversion code on both sides, along with the code that connects the endpoint to Flutter.&lt;/p&gt;

&lt;p&gt;You write the contract. Serverpod generates the plumbing.&lt;/p&gt;

&lt;p&gt;The same process works with more than the numbers and strings used in the example above. Enums, lists, maps, nested models, byte data, and UUIDs are a few of the other supported types. The &lt;a href="https://docs.serverpod.dev/concepts/data-and-the-database/models" rel="noopener noreferrer"&gt;model documentation&lt;/a&gt; covers the full set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The code that connects your Flutter app to its backend has to be right. Maintaining it still takes time away from the feature you're building, even with help from a coding agent. Serverpod reduces that maintenance, while the Dart analyzer helps catch type errors before you run the app.&lt;/p&gt;

&lt;p&gt;To apply the same approach to another feature, &lt;a href="https://serverpod.dev/blog/flutter-google-apis-server-side" rel="noopener noreferrer"&gt;learn how to call Google APIs from a Serverpod backend&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>backend</category>
      <category>serverpod</category>
    </item>
    <item>
      <title>Accessing Google APIs server-side from a Flutter app</title>
      <dc:creator>Jamiu Okanlawon</dc:creator>
      <pubDate>Thu, 13 Aug 2026 16:51:14 +0000</pubDate>
      <link>https://dev.to/serverpod/accessing-google-apis-server-side-from-a-flutter-app-3ip2</link>
      <guid>https://dev.to/serverpod/accessing-google-apis-server-side-from-a-flutter-app-3ip2</guid>
      <description>&lt;p&gt;Your Flutter app needs data that lives in a user’s Google account. Their calendar, their contacts, their files. Google’s APIs will hand it over, but not to just anyone: the user has to consent in your app, and your backend needs an access token that lets it act on their behalf.&lt;/p&gt;

&lt;p&gt;That second part is where most Flutter developers get stuck, because it means OAuth credentials, token verification, authenticated sessions, and a backend to hold them.&lt;/p&gt;

&lt;p&gt;With Serverpod, that backend comes with Google sign-in built in. When a user signs in, your server gets their verified identity and receives an access token. Reading their calendar is an HTTP call away.&lt;/p&gt;

&lt;p&gt;In this article, we’ll wire it end-to-end: Google sign-in for a Flutter app, and a backend that reads the user’s upcoming calendar events the moment they sign in. We will demonstrate it with the calendar, but the pattern is the same for any Google API that the user can grant you access to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the flow
&lt;/h2&gt;

&lt;p&gt;Before configuring anything, it’s worth understanding the whole journey.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F28h6a5gbfzayr3pgeue2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F28h6a5gbfzayr3pgeue2.png" alt=" " width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a user signs in, your app asks Google for two things: proof of who the user is, and permission to read their calendar. Google shows the consent screen, and what comes back reaches your Serverpod backend.&lt;/p&gt;

&lt;p&gt;The server verifies that the identity token was issued by Google, creates an authenticated session, and receives the access token that carries the user’s consent. That token is what lets your backend call Google’s APIs for this user.&lt;/p&gt;

&lt;p&gt;The user grants access in your app. API access occurs on your server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before we start
&lt;/h2&gt;

&lt;p&gt;We’ll begin with a new Serverpod project created using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;serverpod create my_project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During project creation, keep the recommended defaults and make sure Authentication remains enabled when prompted.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F00mylv1xt81e5x484pq1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F00mylv1xt81e5x484pq1.png" alt=" " width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The command gives you a Flutter app (&lt;code&gt;my_project_flutter&lt;/code&gt;), the backend it talks to (&lt;code&gt;my_project_server&lt;/code&gt;), and a generated client package that connects the two, so your app calls the server using plain Dart methods.&lt;/p&gt;

&lt;p&gt;With the project in place, we’re ready to connect it to Google.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Google
&lt;/h2&gt;

&lt;p&gt;Every Google integration begins in Google Cloud, regardless of which backend you’re using.&lt;/p&gt;

&lt;p&gt;You’ll need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a Google Cloud project.&lt;/li&gt;
&lt;li&gt;Configure the Google Auth Platform.&lt;/li&gt;
&lt;li&gt;Enable the required APIs: the People API, which Serverpod uses for basic profile data, and the Calendar API, which is used in this article.&lt;/li&gt;
&lt;li&gt;Under Data access, add the &lt;code&gt;userinfo.email&lt;/code&gt;, &lt;code&gt;userinfo.profile&lt;/code&gt;, and &lt;code&gt;calendar.readonly&lt;/code&gt; scopes.&lt;/li&gt;
&lt;li&gt;Create a Web OAuth client for your backend, and any platform-specific clients your app needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The Calendar scope is sensitive, so Google may ask you to verify your app before you publish it. While it’s in testing mode, any account you add as a test user can sign in.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The complete walkthrough, including platform-specific configuration for Android, iOS, and the web, is covered in the Serverpod &lt;a href="https://docs.serverpod.dev/next/concepts/authentication/providers/google/setup#get-your-google-credentials" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once those credentials are ready, it’s time to connect them to Serverpod.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting Google to Serverpod
&lt;/h2&gt;

&lt;p&gt;The first step in connecting your backend to Google is making your Google credentials available to Serverpod.&lt;/p&gt;

&lt;p&gt;Copy your Web OAuth credentials into &lt;code&gt;config/passwords.yaml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;development&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;googleClientSecret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;"web": {&lt;/span&gt;
        &lt;span class="s"&gt;"client_id": "your-client-id.apps.googleusercontent.com",&lt;/span&gt;
        &lt;span class="s"&gt;"client_secret": "your-client-secret",&lt;/span&gt;
        &lt;span class="s"&gt;"redirect_uris": []&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Serverpod reads these credentials at startup and uses them to verify Google identity tokens.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The credential block is a YAML block scalar, so an indentation error silently produces malformed JSON and surfaces later as an authentication failure rather than an error at startup.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The complete credential structure, along with production configuration options, is covered in the &lt;a href="https://docs.serverpod.dev/next/concepts/authentication/providers/google/setup#store-your-credentials" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Register the Google provider
&lt;/h2&gt;

&lt;p&gt;Next, register Google as one of your application’s identity providers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initializeAuthServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;tokenManagerBuilders:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;JwtConfigFromPasswords&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nl"&gt;identityProviderBuilders:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;GoogleIdpConfigFromPasswords&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;GoogleIdpConfigFromPasswords()&lt;/code&gt; reads the credentials from &lt;code&gt;passwords.yaml&lt;/code&gt; and configures Google as an available authentication provider.&lt;/p&gt;

&lt;p&gt;The final server-side step is exposing that provider through an endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:serverpod_auth_idp_server/providers/google.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GoogleIdpEndpoint&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;GoogleIdpBaseEndpoint&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s the entire endpoint.&lt;/p&gt;

&lt;p&gt;Serverpod already provides the implementation. Your application exposes it, making the Google authentication flow available to your Flutter client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the calendar from your server
&lt;/h2&gt;

&lt;p&gt;When a user signs in, Serverpod passes your code the access token via a callback in the provider config. Whatever you do with it runs on your server, with the user’s consent.&lt;/p&gt;

&lt;p&gt;Add the &lt;code&gt;http&lt;/code&gt; package to your server, then extend the provider registration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dart pub add http    &lt;span class="c"&gt;# from my_project_server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// my_project_server/lib/server.dart&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:convert'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:http/http.dart'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initializeAuthServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;// ... tokenManagerBuilders stay as they are ...&lt;/span&gt;
  &lt;span class="nl"&gt;identityProviderBuilders:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;GoogleIdpConfigFromPasswords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;getExtraGoogleInfoCallback:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="n"&gt;accountDetails&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="n"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="kt"&gt;Uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;https&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'www.googleapis.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'/calendar/v3/calendars/primary/events'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="s"&gt;'maxResults'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'5'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="s"&gt;'singleEvents'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'true'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="s"&gt;'orderBy'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'startTime'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="s"&gt;'timeMin'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toUtc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toIso8601String&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="nl"&gt;headers:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'Authorization'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Bearer &lt;/span&gt;&lt;span class="si"&gt;$accessToken&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Calendar request failed: &lt;/span&gt;&lt;span class="si"&gt;${response.statusCode}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nl"&gt;level:&lt;/span&gt; &lt;span class="n"&gt;LogLevel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s"&gt;'items'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
          &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Upcoming: &lt;/span&gt;&lt;span class="si"&gt;${event['summary'] ?? '(no title)'}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Calendar read skipped: &lt;/span&gt;&lt;span class="si"&gt;$e&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;level:&lt;/span&gt; &lt;span class="n"&gt;LogLevel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s a real Google API call, made by your backend, for the user who just signed in. This example logs their next five events. Yours might store them instead.&lt;/p&gt;

&lt;p&gt;There are two things to know about this callback.&lt;/p&gt;

&lt;p&gt;First, it runs as part of the sign-in flow. If the calendar request fails and the error goes uncaught, the entire sign-in fails too. Treat the calendar read as optional: log the error and let sign-in continue.&lt;/p&gt;

&lt;p&gt;Second, the access token passed to the callback is short-lived. It works well for reading calendar data immediately during sign-in. If you need to sync calendar data later, such as in a scheduled or background job, you’ll need to store the credentials and handle token refresh separately.&lt;/p&gt;

&lt;p&gt;The same token works against any Google API covered by the scopes the user granted. The &lt;a href="https://docs.serverpod.dev/next/concepts/authentication/providers/google/customizations#accessing-google-apis" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; covers how to request additional scopes and use the token in your own code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the application
&lt;/h2&gt;

&lt;p&gt;With the backend configured, it’s time to start the project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;serverpod start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s the only command it takes.&lt;/p&gt;

&lt;p&gt;Besides launching your backend, Flutter application, and development services, &lt;code&gt;serverpod start&lt;/code&gt; automatically generates any required code before starting the project.&lt;/p&gt;

&lt;p&gt;If your authentication setup introduces new database tables, there’s no need to stop the server. While &lt;code&gt;serverpod start&lt;/code&gt; is running, press &lt;strong&gt;M&lt;/strong&gt; to create and apply the migration. Once it has been applied, development continues without interrupting your workflow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr6ztdabzff7j91qn413s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr6ztdabzff7j91qn413s.png" alt=" " width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the backend running, the remaining setup happens in Flutter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting Flutter
&lt;/h2&gt;

&lt;p&gt;The template already initializes authentication when the app starts. Adding Google is one more call in &lt;code&gt;main.dart&lt;/code&gt;, right after &lt;code&gt;client.auth.initialize()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initializeGoogleSignIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;serverClientId:&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;web-client-id&amp;gt;.apps.googleusercontent.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;serverClientId&lt;/code&gt; is the Web application client ID from earlier, not one of the platform-specific ones. That’s deliberate: the sign-in happens in your app, but the tokens are for your server, and this value tells Google which server that is. On the web, the same value is used as &lt;code&gt;clientId&lt;/code&gt;, along with a redirect URI. The &lt;a href="https://docs.serverpod.dev/next/concepts/authentication/providers/google/setup#initialize-the-google-sign-in-service" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; includes the exact call for each platform.&lt;/p&gt;

&lt;p&gt;Then ask for the calendar scope when the user signs in. The sign-in screen that ships with the template uses &lt;code&gt;SignInWidget&lt;/code&gt;, which accepts a customized Google button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;SignInWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;client:&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;googleSignInWidget:&lt;/span&gt; &lt;span class="n"&gt;GoogleSignInWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;client:&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;scopes:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;GoogleAuthController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultScopes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;'https://www.googleapis.com/auth/calendar.readonly'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scopes here are what Google puts on the consent screen and what your server’s access token is allowed to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying it out
&lt;/h2&gt;

&lt;p&gt;Run the app and tap the Google button.&lt;/p&gt;

&lt;p&gt;The consent screen now asks for calendar access alongside the usual profile permissions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkw135jjfsfj9nbkxi1lr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkw135jjfsfj9nbkxi1lr.png" alt=" " width="799" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Approve it, and watch your server’s logs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvo84g6o298fvkjlpj9gn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvo84g6o298fvkjlpj9gn.png" alt=" " width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your backend just called Google on the user’s behalf. Sign-in, verification, session, and a working API call, and the only authentication code you wrote was a callback.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pattern you’ll reuse
&lt;/h2&gt;

&lt;p&gt;The calendar was just an example. Change the scopes you request, and the endpoint you call, and the same flow reads Google Drive files, Contacts, or YouTube data. Add another identity provider, and the same four steps repeat: configure the provider, register it with Serverpod, expose its endpoint, and initialize it in Flutter.&lt;/p&gt;

&lt;p&gt;The provider-specific configuration changes, but the authentication architecture stays the same. That consistency is one of the advantages of Serverpod’s authentication system. As your application grows, you don’t have to rethink your authentication flow. You just plug in another provider, or another API, and keep building.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Accessing Google APIs from a Flutter app has a reputation for being complicated, and most of that reputation stems from the requirements Google imposes, no matter what backend you use: credentials, consent, and verification. Once those are in place, Serverpod’s side is a config entry, an empty endpoint class, and a callback that receives a ready-to-use access token.&lt;/p&gt;

&lt;p&gt;From there, Serverpod handles verifying identity tokens, creating authenticated sessions, and maintaining consistent authentication across your application. What you do with the APIs is up to you.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>backend</category>
      <category>google</category>
    </item>
    <item>
      <title>Serverpod 4 - The first agentic coding engine that hot-reloads the full stack</title>
      <dc:creator>Viktor Lidholt</dc:creator>
      <pubDate>Tue, 09 Jun 2026 14:14:47 +0000</pubDate>
      <link>https://dev.to/serverpod/serverpod-4-the-first-agentic-coding-engine-that-hot-reloads-the-full-stack-499l</link>
      <guid>https://dev.to/serverpod/serverpod-4-the-first-agentic-coding-engine-that-hot-reloads-the-full-stack-499l</guid>
      <description>&lt;p&gt;Today, we’re thrilled to share a tech preview of Serverpod 4. The first agentic coding engine that hot-reloads the full stack and can run locally on your computer. If you are a developer, this will forever change the way you think about building apps. We are finally closing the loop between your app's output, the backend, and your AI agent.&lt;/p&gt;

&lt;p&gt;👉  &lt;strong&gt;&lt;a href="https://serverpod.dev/discord" rel="noopener noreferrer"&gt;Join us on Discord&lt;/a&gt; to be among the first ones to try it out.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/iize2vmgQZs"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Here is what makes Serverpod 4 radically different: Sub-second stateful hot reload across the server, database, web, and app. Everything is wired up to your favorite AI agent through a tried and tested MCP server and skills. We’ve added an embedded Postgres database; there's no need for complex setup or Docker containers. Everything just works.&lt;/p&gt;

&lt;p&gt;You can literally see your app evolving as the agent works. No recompilation. No restart. No wait.&lt;/p&gt;

&lt;p&gt;Serverpod 4 runs locally on your computer, and with Flutter for the frontend, your app runs at native speed on any platform. And did we say that it’s all free and open source?&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the package?
&lt;/h2&gt;

&lt;p&gt;Servepod is an open-source, scalable backend written in Dart for the Flutter community. It provides end-to-end type safety all the way from the database to the client app, catching many errors before the app is even started. You can add a method on the server and call it from your app as if it were a local method.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2439b2olet33eb1k5dlh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2439b2olet33eb1k5dlh.jpg" alt="serverpod-4-preview-screen" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;New in Serverpod 4 is a complete full-stack runtime for agentic coding. One of Flutter’s core strengths has always been its stateful hot reload. We are now bringing this to the server and to the database. The &lt;code&gt;serverpod start&lt;/code&gt; command will manage your full stack, including the Flutter app. Whenever you make a change to your code, database schema, or frontend, Serverpod will pick up the changes and instantly hot-reload them.&lt;/p&gt;

&lt;p&gt;Your AI agent can interact with the running server through a new MCP server. It can create and apply database migrations, read logs, and even drive the app, taking and reviewing screenshots. The bundled rules and skills turn the AI agent into a senior Serverpod developer, and even lower-tier models understand how to write reliable, testable code.&lt;/p&gt;

&lt;p&gt;We are still working hard to iron out the last couple of issues, but we are getting very close to a final release. We cannot wait to see what you will build with Serverpod.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>flutter</category>
      <category>dart</category>
      <category>vibecoding</category>
    </item>
  </channel>
</rss>
