Move beyond AI demos by building and deploying an AI agent to Cloud Run with a Flutter frontend. Using Go and the Agent Development Kit (ADK), learn how to define agents, wire up tools, and delegate to sub-agents. Integrate your agent with a Flutter client app to create a production-grade, agent-powered application ready for users on iOS, Android, and the web.
Resources:
Google ADK → https://goo.gle/3R8jCFz
Flutter → https://goo.gle/3R8jDt7
ADK Go → https://goo.gle/4uJKpXc
Watch the Flutter sessions from Google I/O 2026 → https://goo.gle/Flutter-at-IO26
Watch the cloud sessions from Google I/O 2026 → https://goo.gle/Cloud-at-IO2026
Subscribe to Google Cloud Tech → https://goo.gle/GoogleCloudTech
#GoogleIO
Event: Google I/O 2026
Speakers: Annie Wang, Marc Dougherty, Khanh Nguyen
Products Mentioned: Flutter, Cloud, Go
Оглавление (6 сегментов)
Segment 1 (00:00 - 05:00)
— Hey there. I'm Mark. — Hi, I'm Khan. And I'm Annie. Here's something interesting. When people try clothes on in a fitting room, they're way more likely to buy them. But online, you're kind of just guessing. Honestly, my shopping cart is always full because I never know how things will look on me. Or you end up with a stack of return boxes sitting by the front door. Okay, cutie. But don't worry. That's exactly why we're building this project today. In this workshop, you're stepping into the shoes of a developer at Thread Khan, a fictional yet fun retail brand that conveniently has an app that's been built with Flutter. And you will be building something really cool, a virtual fitting room plus a styling experience, all powered by backend agents. The idea is to help shoppers see how clothes actually look on them and get help creating fully styled outfits. But we're not just throwing agents at the problem. We're building them properly using ADK, Agent Development Kit, and the Gemini models. These agents will need to talk to our product catalog and edit user-provided images, which could eat a lot of memory quickly. We'll be coordinating multiple agents with ADK and depending on ADK's artifact storage and structured data support to feed clean, concise responses straight to our Flutter app to power a smooth and almost magical shopping experience. And not to mention fewer trips to the post office. Right? By the end of the workshop, you'll have a virtual fitting room that lets shoppers see how clothes look on them plus a virtual styling agent that will help these shoppers pick out great outfits for any occasion. Powered by Flutter, ADK Go, Cloud Run, and the Gemini family of models. If you're not familiar with the technologies that we're using in this workshop, no worries. The concepts that we'll talk about are transferable across many of the other frameworks and tools you'd use to build an agent-powered app experience. So, you just kick back, relax, and watch the workshop, or you can code along with us by heading over to the code lab available here. Okay, so we're building two user journeys today. First is the virtual fitting room. The virtual fitting room enables online shoppers to see how a particular article of clothing might look on them. Let's scroll through and find a product that we like. Ooh, I like this dash hot reload letterman jacket. We'll tap the virtual try-on button. Now, I'll pick this photo of myself that I conveniently have available on my phone. It'll take a moment to load. And now we get to see how that letterman jacket would look on me. I can also change my picked image. Since this one is a casual at-home photo, I'll pick another picture in a different setting. Next is the virtual styling flow. If I'm shopping for something specific, like a special occasion or destination, like Google I/O in Mountain View, California, I can hit submit. And I'm presented with a carousel of outfit options that I can scroll through. I can provide some feedback on the outfits, and our styling agent will adjust the outfit items accordingly. When I find an outfit that I like, I can add all the pieces for that outfit to my bag by hitting add to bag. And then continue on through to the checkout flow. Here's a high-level overview of what the user journey for these flows look like behind the scenes. First, to generate the virtual fitting room image, and second, the styling flow to recommend curated outfits. Now you've seen what the app can do. Let's pull back the curtain and look at how everything is wired together. At a high level, there two main pieces. First, a Flutter front end. This is a cross-platform app that runs on web, iOS, and Android. Second, a Go back end. It's a single binary running on Cloud Run, built entirely with ADK. These two talk to each other over plain HTTP. The back end exposed the ADK REST API, and the Flutter app calls it using the HTTP package. On the back end, we have four agents working together. In our Flutter app, we call agents directly. The fitting room agent for try-ons, the stylist agent for outfit suggestions. Both of these agents can delegate to a catalog agent to look at products. And the stylist agent has one extra power. It can directly use the image generation tool, just like fitting room agent. So, it doesn't need to go through another agent to generate outfit images. Both Flutter services, ADK fitting room service, and ADK styling service follow
Segment 2 (05:00 - 10:00)
the same three-step pattern. Step one, session. A session is how the back end tracks conversation history, shared state across multiple turns. The fitting room creates a new session every time. But the styling service is different. It reuses the same session ID. This is what allows the agent to remember your feedback and refine suggestions instead of starting over. Step two, run the agent. This is where the magic happens. Both services send a post request to the run endpoint. Inside the request, we include app name, session ID, a new message. That message contains a parts array. Each part can carry different types of data. Text for instructions or context, inline data for images encoded as base 64. So, for example, the fitting room sends both user photos and product images. The styling services send text like occasion and location plus an optional user photo. Step three, fetch the generated image. Instead of sending large images directly in the response, we use artifacts. When an image is generated, it gets saved to Google Cloud Storage. The agent returns only a reference name. Then the front end makes a separate get request to fetch the image. This keeps responses lightweight, fast, and predictable. The REST API sessions and artifact storage are few of the conveniences built into ADK. Next, let's go a little deeper and understanding how ADK actually works. Now, let's talk about ADK itself. ADK, the Agent Development Kit, is an open-source framework from Google for building, orchestrating, and deploying AI agents. You can think of it like a toolkit that can turn Gemini models into agents that can actually do things. Not just answer questions, but take actions, use tools, remember context, coordinate with other agents. And in our case, we're using ADK Go. So, everything runs in Go on our Cloud Run back end. So, you may wonder, why not just call the model? Well, you could call the Gemini API directly, but once your app need to look up products, generate images, suggest outfits, you need structure. And that's what ADK gives you. It handles tool calling, to invoke real functions, multi-agent coordinations for agents delegate works, sessions and memory for persistent context, and dev UI to inspect and debug. It is the difference between a raw model call and a production-ready system. And how does an agent work? At its core, every ADK agent follows a simple loop. It received a message. It think about what to do. It decide call a tool, delegate to another agent, or respond directly. Then it returns a response. And this loop can repeat multiple times within a single interaction. Agents don't just think in a moment. They remember. Each session has a shared state, a key-value store. All agents can read or write to it. And that's how coordination works. For example, the feeding room agent generates an image and stores the artifact name in state. The styling agent reads that same value and displays it. No direct communication needed. State acts like shared memory. The runner is what executes everything. It gives a message. It handles tool calls, delegations, state updates, streaming response. In main. go file, we build our agents and pass them into the ADK handler. This also gives you the ADK web UI, where you can chat with agents, inspect events, view artifacts, debug everything. When you're ready, the same binary deploys to Cloud Run. ADK also gives you callbacks. Those are hooks at key moments. For example, before two runs, after model response. In our implementation, we use a callback to automatically save user uploaded images as artifacts. So, they show up in the 80K web UI for easier debugging. Not all agents are the same. 80K gives you a few types. LM agent is standard.
Segment 3 (10:00 - 15:00)
Custom agent, if you want to end full control. Workflow agent orchestrate other agents. And most of the time, you will use LM agent. We use multiple agents working together. That's where workflow agents comes in. 80K gives you three flavors. Sequential agent runs sub agents one after another in order. Parallel agent runs sub agent at the same time. Loop agent runs a sub agent repeatedly until a condition is met. But most use case, including everything we're building today, you will reach to the LM agent. In our app, our styling agent coordinates with a styling room agent, which in turn coordinates with catalog agent. And that chain of delegation is what makes the whole experience feel seamless to the user, even though multiple agents are doing different jobs behind the scenes. Now that we know what 80K is and how it works, let's look at exactly how we implemented those agents for our virtual fitting room and its styling features. Let's take a look at the implementation of our agents. The running of the agents is done with main. go, where we construct our agents and pass them all to the 80K launcher. The launcher allows us to use the development web UI, which lets us see more of what's happening behind the scenes. You can also use the 80K as an API if you prefer. Each of our agents has their own directory, so they exist in their own go package. You may notice there's an instructions. md file in each agent directory. Because the agent instructions can be long and are often written with some markdown formatting, we write these prompts separately and use Go's embed package to make the contents available inside the code. The Go compiler inserts the contents of the file in the specified variable for us automatically, so there's no need for big multi-line strings in the middle of our code. Let's take a quick look at each of the agents we're building. First up, we have the catalog agent. This agent has access to our existing catalog of products. We do this by giving our agent a couple of tools. The list products tool lets our agent query our product database returning structured data. Our implementation reads a YAML file, but this tool could connect to an existing database or API. It's just a Go function. The get product image tool reads the product image data from a cloud storage bucket. Here's how we create the catalog agent. We provide some agent metadata like name, description, and instructions, as well as the necessary tools. Function tools like our list tool have a name, description, and of course, the function itself. The function signature determines the input and output structure of the LLM is expecting. Here, we're using list product args for input and list products result for output. We can try out the catalog agent directly with the ADK development UI. From the dev UI, we can ask questions about available products like "How much is the Flutter hat? " On its own, this agent could be integrated into our e-commerce site or app to provide chat assistance, but it becomes even more useful when we integrate it with our fitting room agent. The fitting room agent creates pictures of the user wearing some of our products. To make these images, we give our agent the fitting tool. This tool calls Gemini 2. 5 flash image asking it to add our products onto the user in the provided image. To do this, the fitting room agent needs access to our catalog agent and the get product image tool. Creating the fitting agent looks a lot like our catalog agent, just with a larger set of tools. The load artifacts tool is built in and allows us to load data from ADK's artifact storage. You can also see here that we're turning the catalog agent into a tool using agent tool dot new. We're also using a before model callback to save the user's picture to artifact storage. The agent can then load the picture from artifact storage when needed. Just like with the catalog agent, we can chat with the fitting room agent in the ADK development web UI. We'll upload my picture and ask the fitting agent to show me with the flutter hat on. And there it is. Our last agent is the styling agent. It provides product suggestions to the user based on a scenario. Like the fitting room agent, our styling agent needs
Segment 4 (15:00 - 20:00)
access to our catalog agent so it knows which products are available and appropriate for our event. The styling agent uses the same tool as the fitting room agent to show the user in a selection of products. As with the fitting room agent, the generated image is saved as an artifact and the name comes back in a structured response, so the front end can fetch the image directly. You may notice that our styling agent has the same tools as our fitting room agent, but the agents have different instructions. The styling agent instructions tell it to search for products that are right for the user's specified event or location, where the fitting room agent only uses the catalog to find product pictures. All of our agents can be served from the same binary. Once we're production ready, this binary can be deployed to Cloud Run, just like any other. Now that we've built and deployed our agents, Khan will walk us through integrating our agent functionality into our app. We can start running the app with Flutter run. Flutter is multi-platform, so this particular app can run on web, iOS, and Android right out of the box. If you want, you can set up your Android or iOS dev environments, but the fastest way to get started, since most folks already have a web browser, is to run the app in your browser, like Chrome. Making the app responsive is outside the scope of this workshop, so let's scale our window down to mobile dimensions. Right now, we have just the standard shopping app, but no worries, we'll get our virtual fitting room and styling features built out in no time. Before we write any new UI, let's level set on architecture. Flutter is declarative, meaning the UI is a function of the app state. Instead of imperatively changing the UI, we'll update our state models, and Flutter will automatically know how to rebuild the UI based on those state variables. To manage the state and decouple our data from the UI, we use providers. A provider encapsulates the app's data, things like the user's uploaded image, the AI-generated image, the literal loading state, the list of curated outfits, and so on. We construct these providers at the top of our app's widget tree, so we can access them from anywhere in the app. When a request completes and returns some data, it's then stored in the provider, which then notifies the listeners below it in the widgetry. In other words, any component in the UI that is subscribed to that provider is immediately and efficiently rebuilt to display the new data. Speaking of data, we have these ADK fitting room and styling services already built out that we'll use to fetch data from our ADK backend. The ADK fitting room service implements the fitting room service interface that has a generate try-on image method. It takes a user image and product image, then returns the generated try-on image. The ADK styling service implements the styling service interface with two methods: get styling suggestions, which takes a styling request and returns a list of outfits, and refine with feedback, which takes a string of feedback and returns a list of outfits. As Annie mentioned, these service classes are solely responsible for shuttling data back and forth between our client app and the ADK backend using HTTP requests. They manage building and sending requests, then receiving and parsing responses into data classes that our app can understand. If you look in our providers, we have methods defined that call these services whenever it needs to get new data. We're abstracting UI from the business logic and the business logic from its data source. If you zoom out and look at the front end app architecture, we're essentially following a mono view model architecture, otherwise known as MVVM. MVVM has clear separation of concerns, multiple different components with distinct responsibilities, well-defined interfaces, boundaries, and dependencies, making our app more testable, maintainable, and scalable. Okay. With the architecture out of the way, let's get into building the UI. We'll open the product screen and add a virtual fitting room button right next to the add to bag button. We'll just initialize our try-on provider with the selected product and push our next screen. The tried on screen acts as the core state machine for our virtual fitting room flow. Inside the screen, we use an animated switcher to route between three distinct phases. Asking for a photo, loading, and displaying the final fitting room image. If this syntax looks a little different, it's because we're using Dart's pattern matching syntax. It's one of my favorite Dart features. Now, let's build out the choose your image screen shown in that initial state. We already have some scaffolding, so we'll just add an upload image button. When pressed, it'll call process try on, which brings up an image picker. As soon as the user selects an image, the app sends off a request to generate the fitting room image. Let's hot reload our app and make sure that works. Perfect. Now, on the fitting room screen, we grab
Segment 5 (20:00 - 25:00)
the generated try on image from try on provider and display it in the center of the screen. If you're new to Flutter, that expanded widget is just telling the framework that we want that image to fill as much space as possible. Notice the change image button in the app bar. It's already built into our scaffolding. The button is conditionally shown if a user has already selected an image. When the user selects a new image, it immediately kicks off another request to process try on. At the bottom of the screen, there's already an add to bag button. We'll add our style me button right next to it. The button's on pressed callback is the show style me form function that has conveniently been predefined. Just for good measure, we'll also add a little bit of spacing in between the two buttons. Let's take a quick look at what the show style me form callback actually looks like under the hood. This function triggers a bottom modal sheet containing a form. The form asks for contextual inputs like location, occasion, and additional notes. There's also some form validation to make sure that either a location or occasion is present with the user's request. When the form is submitted, the bottom modal sheet returns a style request payload. Looking back at the show style me form function, you can see that it passes the style request to the styling provider so that our agent can process it. It then pushes the final style me screen so that the user can see their curated outfit results. We can hot reload the app to make sure that the style me request collects the right info and navigates us to the styling screen. Sweet. Finally, we made it to our last screen. Our agent responds with a list of outfits, each containing a generated outfit image, styling commentary, and a list of items that make up that particular outfit. We want to display these as a list of outfit cards. The scaffolding for this screen already provides the consumer and animated switcher logic that we need to listen to the styling provider and animate between states. Notice how the consumer specifically wraps our main layout. This means when the styling provider updates, only the widgets below it are rebuilt. It won't unnecessarily rebuild the static parts of the screen, like the app bar. Okay, let's write the logic that determines which state gets rendered to the screen. Jumping down to the animated switcher, we'll use a sequence of ternary operators to check the styling provider state dynamically, similar to the fitting room screen. First, we check if the provider is currently loading, and if so, return an app loading indicator. Then, we check if the list of outfits is empty, falling back to an app empty state. And finally, when there's a list of outfits, we throw them straight into our swipable outfit carousel. This animated switcher now triggers a fade and scale animation between the child widgets whenever the UI state changes. Let's trigger a hot reload and you'll see that the carousel populates with a couple of outfit cards. And here's a neat detail. There's an add to bag button that is already built into the card components, so we can throw the entire curated outfit into our bag with one tap. But, developing your personal style isn't a one-and-done interaction. It's iterative. Let's give our shoppers a way to give their feedback on the generated outfits. We'll add a feedback chat bar at the bottom of the screen as an overlay. For the callback, we'll pass the feedback to our styling provider so that it can ask our agent to update the list of outfits and update our state. Remember, because the animated switcher is a consumer of the styling provider, the cards are automatically rebuilt to reflect the new outfits. Last but not least, for the final polishing touch, we'll add an info button to the app bar. For our callback, we'll call our pre-built show styling brief details method. This method brings up a dialog containing the user's original styling request. Just in case our shopper forgets their style request because, well, they have a short memory like me. Let's hot restart our app and make sure that we've got the full try-on to styling flow working. We'll find an article of clothing that we like. Tap the button for the virtual fitting room. Pick our photo. Wait for it to generate our virtual fitting room image. Could take a moment. That looks good. Now, let's tap the style me button. Enter our style request. Submit. Wait for it a moment. It'll take a sec. Swipe through the outfits. Let's submit some feedback. I forget what our original styling request was. Let's check out the info dialog. Okay, yes, right. I like this outfit. So, let's throw it into our shopping bag and check out. Sweet, it all works. Of course, one of my favorite party tricks as a Flutter engineer is well, remember that this entire flow now runs on iOS, Android, and web without any extra work on our part. So, there you have it. You've built a virtual fitting room that lets shoppers see how clothes this look on them. Plus a virtual styling agent that will help them pick out great outfits for whatever
Segment 6 (25:00 - 25:00)
occasions they need. All powered by Flutter, 80K, Cloud Run, and the Gemini family of models. Hopefully, that means Thread Count will see more sales next quarter. We hope you enjoyed this workshop and feel more confident about building a full end-to-end agent-powered app experience. If you want to walk through and build out this app yourself, the code lab for this workshop is here and in the video description. If you build out this app, please let us know in the comments how it went and if you have any questions. Is there anything else that you would have added to the app? Thanks for joining us. Happy Google I/O. —