Author Archives:

Prefactoring: Clear the Way for Your New Feature

This article was adapted from a Google Tech on the Toilet (TotT) episode. You can download a printer-friendly version of this TotT episode and post it in your office.

By Rahul Singal

“First make the change easy, then make the easy change.” - paraphrased from Kent Beck 

You're working on a new feature, but the existing code wasn't written with future changes in mind. Trying to force the feature in directly gets complicated fast. One change leads to another, and before you know it you're already a few files deep fixing things you never planned to touch.

Prefactoring (short for "preparatory refactoring") is the practice of reworking existing code to make it more suitable for an upcoming change before you actually implement the new functionality. Instead of cleaning up code as an afterthought or trying to force a new feature into an incompatible structure, you restructure the codebase first.

Prefactoring helps you: 

  • Easily implement new features: Restructuring the codebase first ensures your new feature fits naturally into the code.

  • Speed up reviews: It's easier to review the refactoring and the feature in separate changes.

  • Avoid bugs: Isolating cleanups from functional logic can help prevent bugs.

  • Roll back safely: If you need to roll back, it is much easier to revert small, focused changes.

Here is a simplified example of a prefactoring change:

Change 1 (Prefactoring)

Extract display name helper to remove duplication.

Change 2 (Feature)

Add middle name support.

+ def get_display_name(user):

+    return f"{user.first_name}”  {user.last_name}


# Profile page

-    display_name = f"{user.first_name} {user.last_name}"

+    display_name = get_display_name(user)

# Email template

- greeting = f"Hi {user.first_name} {user.last_name},"

+ greeting = f"Hi {get_display_name(user)},"

    

def get_display_name(user):

-  return f"{user.first_name} {user.last_name}”

+  return f"{user.first_name} {user.middle_name} {user.last_name}"




You can prefactor a change that is already in review too! If your reviewer suggests a related cleanup during review, you can also extract it into a new base change to keep your current change focused on the feature. Note that not every cleanup needs to be prefactoring: you can do the cleanup in a follow up change if the cleanup doesn’t block your feature, or even in the same change if the cleanup is small enough.



Redesigned Google Classroom homepage with tailored views based on user’s role

Soon, Google Classroom will introduce a redesigned homepage globally across all editions to help teachers, students, and administrators easily find relevant content, resources, and tools tailored to their specific roles. The updated interface transforms the homepage into a dynamic, centralized hub that more easily surfaces existing information and tools that were previously located in different areas of Classroom. Users can still access classes in the side navigation panel and a dedicated classes module on the homepage.

The new experience, which will begin rolling out on July 27, 2026, is personalized based on a user's role and available features:

  • For teachers, a new dashboard gives actionable insights, highlights student classwork interactions, tracks assignment completion, and surfaces a feature spotlight to help discover instructional tools and resources.

  • For students, a dedicated ‘Enrolled’ view reminds learners of coursework that is due soon and helps them manage their deadlines.

  • For school leaders and IT administrators, the homepage provides a centralized view to monitor high-level performance analytics, access shortcuts for backend administrative settings, and discover relevant tools to support educators and staff.


Users who have multiple roles (such as a teacher taking a professional development class) can easily change their view dashboard by clicking into another role (for example, Teaching, Enrolled, or Admin). When a user loads the homepage, it returns to the previous role view.

To help users control their view and focus on what matters most to them, all new homepage modules are collapsible.



Please note that not all features and views will be available to all users. Eligibility is determined by the user’s role, feature access, account type, and settings.

Getting started

  • Admins: There is no admin control for the new Classroom homepage. Gemini and Gemini Notebook features will only appear if the user is in an OU with Gemini in Classroom, Gemini app, and/or Gemini Notebook enabled. Visit the Help Center to learn about managing access to Gemini in Classroom, Gemini app, Gemini Notebook, and the option to turn these services on or off for users in the Admin console.
  • End users: There is no end user setting for the new Classroom homepage. Visit the Help Center to learn more about the new Classroom homepage.

Rollout pace

Availability

  • Available to all Google Workspace customers, Workspace Individual subscribers, and users with personal Google accounts

Resources

A centralized hub for meeting resources on the new Google Meet homepage

Finding the right notes or attachments for a meeting shouldn't feel like a scavenger hunt. We’re introducing a revamped Google Meet homepage on the web to help you stay organized and prepared throughout your entire meeting workflow.

The new homepage provides a centralized view of your meeting agenda and essential artifacts, allowing you to:

  • Prepare effectively: Quickly access meeting descriptions and calendar attachments for upcoming calls. 
  • Follow through easily: Find meeting notes, recordings, and transcripts from past meetings without digging through your inbox or Drive.
  • Manage your time: Use the new week and month navigators to look ahead at your schedule or revisit previous discussions.

Getting started

  • Admins: There is no admin control for this feature. The new homepage experience will be available to all users with access to Google Meet on the web.
  • End users: The new homepage experience will be available to all Meet users on the web. Visit meet.google.com to get started.

Rollout pace

Availability

  • Available to all Google Workspace customers and Workspace Individual subscribers

Resources

Celebrating 5 years of Jetpack Compose

Posted by Rebecca Franks, Developer Relations Engineer, Nick Butcher, Product Manager, Loryn Hairston, Product Marketing Manager, Android


Today, we officially celebrate five years since the release of Jetpack Compose 1.0. From version 1.0, announced on July 28th, 2021, to our latest 1.11 release, we’ve seen the APIs evolve significantly over the years, and we’re taking a moment to celebrate.

When we officially announced the 1.0 release, we promised a simpler, faster, and more intuitive way to build native interfaces on Android. Looking back, it's safe to say that Compose didn’t just deliver on that promise, but also completely changed the Android ecosystem, with more than 68% of the top 1,000 apps using it in production today.

History

Over the last five years, Compose has grown steadily. In the early days, we explored showing you how to build layouts with the basic Box, Row, and Column. Today, we’ve expanded Compose to work not just on mobile devices, but to other form factors such as Compose for TV, WearOS, Glance for Widgets, and even display glasses with Jetpack Compose Glimmer.



We recorded an Android Developers Backstage episode with Clara Bayarri, Engineering Lead for Jetpack, and two former leads of the team, Romain Guy and Chet Haase, along with Tor Norbye, Senior Engineering Director. In this episode, they discuss the history of Compose and the early days of development.




Compose highlights over the years

Looking back

The beginnings of Compose were very different from what you know today. Two projects were happening in parallel inside the Android team.

At the time, the Views toolkit team was thinking of unbundling the UI Toolkit into a library to help with development speed, and make it easier for developers to adopt and control updates. Meanwhile, a team was working on a novel idea to build declarative layouts by embedding XML inside Kotlin, which looked something like this:



Those two efforts merged to produce what you know today - a fully declarative UI Toolkit that utilizes the power of a compiler plugin, runtime, and Kotlin:

@Composable
fun Newsfeed(stories: List<Story>) {
    LazyColumn {
        items(stories) { story ->
            Card {
                val author = story.author
                Image(painterResource(author.profilePhoto),
                    contentDescription = author.name)
                Text(author.name)
                Text(story.content)
                if (story.hasCommentsEnabled()) {
                    for(comment in story.comments) {
                        Text(comment.mainContent)
                    }
                }
            }
        }
    }
}

And you, the community, helped us very early on! Before 2021, Compose had a pre-alpha phase, which helped ensure Compose was fit to solve the problems of our developers.

One of our favorite memories is the Android Dev Challenge. We challenged the community to build four different tasks with Compose, filling our feeds with Puppy apps, clocks, and weather apps, and giving us a ton of direct feedback that helped shape the 1.0 release.

Compose has continued to evolve, from launching with a set of Material 2 components to now supporting Material 3 Expressive.

Material 2 in Compose

Material 3 Expressive in Compose

Looking ahead

As of today, Compose 1.11 is the latest version with 1.12 coming soon, offering so much more than 1.0, 5 years ago. This year, we introduced more adaptive APIs, such as FlexBox, Grid, MediaQuery, and Styles. These APIs let you advance to the next level of premium, adaptive UI development with Compose.

At Google I/O 2026, we announced that we are now Compose-first, meaning that all future UI development will happen only in Compose, while the Views toolkit enters maintenance mode. Material Design is also shifting focus entirely to Compose, signaling an end to the findViewById era.

Community is at the heart of Compose 

Over the years, you’ve inspired us with creative examples of how you’ve used Compose, and we’d love to highlight a few more examples of where we’ve seen exciting work. Jetbrains has been a great partner for Google with Compose, expanding Compose to work across platforms with Compose Multiplatform and enabling desktop, iOS, and web developers to also enjoy the benefits of Compose.

We’ve really enjoyed following our most beloved newsletters from JetpackCompose.app’s Dispatch, AndroidWeekly, to jetc - helping Android Developers stay up-to-date with the latest in the world of Compose and Android.

Another standout contributor is sinasamaki. They’ve created many delightful experiences using Compose, such as this fun ribbon modifier and the glitchy effect:


Saket Narayan has also always been an inspiration when it comes to creating useful tools for Compose, such as telephoto, a library featuring support for pan and zoom gestures and automatic sub-sampling of large images, or the latest library, Touch Robot, which allows you to easily test interaction animations:

paparazzi.gif(end = 3_000) {
  DebitCard(
    Modifier.testTag("card")
  )

  val touchRobot = rememberTouchRobot()
  LaunchedEffect(Unit) {
    touchRobot.onNode(hasTestTag("card")).performGesture {
      draw(
        path = createAndroidHeadPath(),
        duration = 3.seconds,
      )
    }
  }
}

/** A path drawing the Android head. */
fun createAndroidHeadPath(bounds: Rect): Path = TODO()

Jake Wharton, who has used Compose in innovative ways (like molecule, and even building UI with Compose for the terminal with mosaic). Chris Banes, who has built many Compose libraries over the years, with our most recent favourite - Haze for background blurring, and many of the Android Google Developer Experts like Akshay Chordiya, Huyen Tue Dao, and Katie Barnett, who’ve contributed to the success of Compose. But this is not about selecting individuals - there have been so many great contributors to the Compose codebase, and many of you continue to inspire us with your fun examples, libraries, and in-depth talks. Without the community, Jetpack Compose wouldn’t be as successful as it is today.

Cheers to the next 5 years, and more! 

Jetpack Compose has grown from an experimental idea into the standard for Android UI Development. Thank you to the entire Toolkit team at Google, and to the incredible global developer community that wrote libraries, filed bugs, and pushed the boundaries of what declarative UI can do.

This week, we’ll be celebrating with some in-person birthday parties across the globe, and a live “Birthday party” on the Android Developers YouTube channel on July 30th at 13:00 UTC. During this time, we’ll hang out and discuss Compose and answer your questions!

Cheers to the next 5 years, and happy composing!

Build intelligent Android apps: Introduction to Jetpacker

Posted by Jolanda Verhoef, Senior Developer Relations Engineer, Android Developer Relations


Building GenAI features in your app usually means navigating through various models, APIs and architecture choices: 

  • Execution location: Where does your model run? On device, in the cloud, or both?
  • Complexity: How complex is your setup? Are you doing a single inference call or do you need a more agentic flow?
  • In-app or Android System: Should your feature be built into your Android app or does it fit better as an Android system integration?

In this blog post series we'll navigate these choices with you. We will take you along on a journey, starting with a basic mobile app and transforming it into a personalized, intelligent, and agentic experience.

Jetpacker: a demo travel app

Jetpacker is a technical showcase app that our team built from the ground up for this year's Google I/O (built using Antigravity). At its core, Jetpacker helps users plan, explore, and enjoy their next big adventure. It shows an overview of your trips, the itinerary of each trip, and details of each event on that trip. Of course following all best practices of Android development, including a beautifully expressive Material UI design.

And best of all? It's fully open source!

Today we are publishing a series of technical blog posts diving deep into each of these features. We’ll provide detailed implementation steps, code snippets, and architectural insights to help you build your own intelligent Android applications.

On-device intelligence

On-device features in Jetpacker: Summarizing trip itineraries, managing expenses, and voice notes

Using an on-device model comes with no additional cloud inference costs, means you don't have to worry about internet connectivity, and lets users be confident that private information will be processed locally, on the device, without any of their data being sent to the cloud.

In Jetpacker, we chose on-device inference for three of our features:

  • The trip overview feature transforms a messy, multi-day itinerary into a concise, actionable summary. It leverages Gemini Nano through the ML Kit GenAI APIs to process data locally on the device. We consider this a nice-to-have feature where we don't want to incur extra cloud costs, making on-device inference the right choice.
  • The expense tracker automatically extracts structured data from receipt images to help users track their travel spending. It uses the multimodal capabilities of Gemini Nano 4 through the ML Kit GenAI APIs. We choose an on-device solution so that any privacy-sensitive information on the receipt images never leaves the user's device.
  • The audio diary records, transcribes, and categorizes voice notes into relevant trip activities. It is powered by the ML Kit Speech Recognition and GenAI Prompt APIs. We chose an on-device solution for privacy and connectivity reasons.

Cloud & hybrid inference













Cloud and hybrid features in Jetpacker: Museum assistant with web grounding, hybrid restaurant review drafting, and hotel support chat featuring custom-routed live translation.

Sometimes your use-case requires AI models with greater world knowledge or a much larger context window and with greater ability in handling complex tasks. In that case, we can switch from running an on-device model to using a cloud model instead.

Or, if you want to get the best of both worlds, you can use hybrid inference to dynamically choose either a cloud or on-device model at runtime. This allows us to lower costs by moving inference to the device when it is available, but at the same time support all Android devices running the app.

In Jetpacker, we implemented several features using cloud or hybrid inference:

  • The place Q&A feature answers user questions about specific locations by grounding responses in real-world data. It uses Firebase AI Logic integrated with Google Maps and web context. Using a cloud model is necessary here for its greater world knowledge.
  • The review drafting feature helps users compose detailed reviews for the places they have visited. It leverages both on-device and cloud models through Firebase AI Logic's new Hybrid inference API. This is a feature we wanted to make available to all app users, so we're using a cloud model as a fallback when an on-device model is unavailable.
  • The automatic chat translation dynamically translates chat messages in real time to facilitate seamless communication, demonstrating custom hybrid inference logic. Again, we want this feature to be available to all app users, but at the same time have some specific considerations on when to choose on-device versus cloud.

System integration

While not a feature you see in the app itself, the Android system integration opens up the app's core capabilities directly to the Android operating system. It uses the AppFunctions API to integrate with system-level intelligence.

In-app agentic workflows (coming soon!)

The booking assistant shows several in-progress flight bookings, asking the user for input before making a final booking.

Agenticness introduces a higher level of autonomy, enabling models to act as agents. Instead of a single inference call, an agent works towards a specific goal via an orchestration loop that allows it to reason, use tools, and adapt its path. Depending on your requirements, these intelligent agents can run either in the cloud, directly on-device, or in a hybrid setup.

For Jetpacker we added a booking assistant that automates end-to-end booking workflows directly within the application to streamline reservations. It is built using A2UI and ADK running in the cloud. The Android app functions as a front-end to the multi-agentic system running in the cloud.

Learn more

Check out the other parts of this blog post series:

Part 1 (this post!): Introduction of the app and a high-level overview.
Part 2: On-device intelligence. Deep-dive into ML Kit’s GenAI APIs and Gemini Nano to build privacy-first features like itinerary summarization, receipt parsing, and local audio processing.
Part 3: Hybrid and cloud reasoning. Explore how to use Firebase AI Logic to ground LLM answers in real-world data like Google Maps and web context.
Part 4: System integration. Integrating with the Android intelligence system using AppFunctions.
Part 5 (coming soon): In-app agentic workflows. Extend the app with an end-to-end booking assistant powered by A2UI and ADK.

Interested in more on Android Development? Follow Android Developers on YouTube or LinkedIn!

Build intelligent Android apps: Integrate into Android’s intelligence system using AppFunctions

Posted by Ben Weiss, Senior Developer Relations Engineer, Android Developer Relations



Welcome back to the blog post series "Build intelligent Android apps" where we take a basic Android app and transform it into a personalized, intelligent, and agentic experience. In our previous post, we explored how to leverage Firebase AI Logic to build cloud-hosted and hybrid AI features.

Traditional mobile UIs excel at focused, hands-on tasks, and the Android intelligence system is introducing complementary features to make complex, multi-step actions even easier. By supplementing traditional user interfaces, AppFunctions provide a powerful new entry point: A privileged agent on the device can access app features in the background. This can be particularly helpful when users are driving, walking or otherwise multitasking.

In this article, we'll show you how we designed and integrated these capabilities into our travel planning app, JetPacker, using Android AppFunctions. We'll explore the rationale behind our feature choices, discuss the specialized tooling we used to accelerate development, and dive into the code that makes it all work.

Designing AI-ready features: making choices that matter for your users

To select which features to provide to the intelligence system, we looked for tasks where a voice or text command is objectively faster than tapping through screens. In this side-by-side screen recording you can see this contrast perfectly: on the left, a user tapping through multiple screens to log an expense; on the right, the same task completed instantly in the background via a privileged agent.



Our first choice was expense tracking. Logging a coffee expense during a trip usually takes quite a few taps—unlocking the phone, opening the app, finding the active trip, navigating to the expenses tab, tapping the add button, taking a picture of the receipt, and checking the result. By providing the addExpense and getExpenses features as AppFunctions, the system agent handles the heavy lifting. When the user says, "Add a five-dollar coffee expense to my Paris trip," the agent automatically searches for the correct trip ID in the background and inserts the expense, skipping the manual UI flow entirely.

We also prioritized itinerary management. Finding what activity is next on a busy trip itinerary usually requires scrolling through a dense timeline view. By providing getItinerary and addItineraryEvent to the system, the user can simply ask, "What am I doing next in Paris?" and get an immediate answer.



Finally, we focused on hands-free note capturing. Typing out reminders or notes while walking down a busy street is difficult and unsafe. Exposing a voice note capability allows the user to say, "The flight was amazing, I saw a beautiful sunset and managed to sleep well," and the privileged agent automatically transcribes and saves it directly into the travel database  using the addVoiceNote AppFunction.

Android MCP powered by AppFunctions

This entire experience is built on Android MCP. Under this design, the app acts as a local MCP server. Rather than remote APIs, you provide your app features directly to the on-device intelligence system.

Android AppFunctions is the API that brings this concept to life. It reads annotated Kotlin functions and compiles them into type-safe, sandboxed tool definitions that the privileged agent can discover and invoke locally on the device.







Diagram highlighting our apps, the android platform, and system agents coordinate AppFunctions.

Under the Android MCP model, your app acts as a local MCP server that exposes structured tools, while the Android platform serves as the central tool registry. On the MCP client side, agent apps are registered with the intelligence system after being granted system-privileged permissions to access the registry.

When a user interacts with a registered agent, its LLM determines if the request can be handled by an AppFunction, queries the platform's metadata, and executes the appropriate registered functions in the background. This local MCP client-server design gives you full control: you choose exactly which features are accessible to the agent, keeping the rest of your app's data private.

How we accelerated development with Android skills

To streamline the integration process, we leveraged the AppFunctions development skill. The AppFunctions development skill is a complete development companion. It guided us through the entire lifecycle: mapping Kotlin data classes to serialize parameters, generating the necessary Service entry points, refining our KDoc documentation to ensure the LLM understands parameter boundaries, and setting up automated testing using ADB.

Providing app features to the intelligence system

Enough with the theory, let's dive into the implementation.

Configuration and dependency setup

We begin by adding the AppFunctions dependencies. One for the API and one for the Kotlin Symbol Processing compiler.

implementation("androidx.appfunctions:appfunctions:1.0.0-alpha10")
ksp("androidx.appfunctions:appfunctions-compiler:1.0.0-alpha10")

Modeling custom data types

Any custom object exchanged with the agent must be annotated with @AppFunctionSerializable. In our TripSerializable.kt file, we define our trip data model:

@AppFunctionSerializable(isDescribedByKDoc = true)
data class TripSerializable(
    /** The trip's unique identifier. */
    val id: String,
    /** The trip's title. */
    val title: String,
    /** The trip's destination location. */
    val location: String,
    /** The trip's start date in milliseconds. */
    val startDate: Long,
    /** The trip's end date in milliseconds. */
    val endDate: Long,
    /** A list of participants. */
    val participants: List<String>,
)

Providing features using the @AppFunction annotation

Next, the skill wrote the Kotlin functions that perform the database queries and annotate them with @AppFunction. We can view this in searchTrip:

/**
 * Looks for trips based on optional filters like id, title (name), location, and dates.
 *
 * @param id The unique identifier of the trip.
 * @param title The title or name of the trip.
 * @param location The destination location.
 * @param startDate The minimum start date in milliseconds.
 * @param endDate The maximum end date in milliseconds.
 * @return A list of trips matching the filters.
 */
@AppFunction(isDescribedByKDoc = true)
suspend fun searchTrip(
    id: String? = null,
    title: String? = null,
    location: String? = null,
    startDate: Long? = null,
    endDate: Long? = null
): List<TripSerializable> {
    return withContext(Dispatchers.IO) {
    // implementation
}

Since AppFunctions run on the UI thread by default, we use withContext(Dispatchers.IO) to switch to a background dispatcher. Additionally, we refine our KDoc to use clear, imperative verbs and specify parameter constraints. This documentation compiles directly into the tool's schema, which the privileged agent uses to resolve parameters and handle runtime errors.

The service entry point and Hilt integration

To register these features with the intelligence system, we create an abstract base class that extends AppFunctionService. We annotate it with @AppFunctionServiceEntryPoint:

@RequiresApi(36)
@AndroidEntryPoint
@AppFunctionServiceEntryPoint(
    serviceName = "JetPackerAppFunctionService",
    appFunctionXmlFileName = "jetpacker_app_function_service"
)
abstract class BaseJetPackerAppFunctionService : AppFunctionService() {
    @Inject internal lateinit var tripDao: TripDao
    // DAOs and database references are injected here...
}

During compilation, KSP generates the final concrete service subclass, JetPackerAppFunctionService, as declared with the serviceName parameter. We also register app_metadata.xml in the app's manifest. This file provides global operational rules for JetPacker's declared AppFunctions.

Testing and verifying your AppFunctions

Once implemented, you should verify that your AppFunctions are registered and working correctly.

Running devices or emulators with Android 17 or newer, you can use ADB commands from your terminal to list and invoke your functions. Running adb shell cmd app_function list-app-functions displays all registered functions for your package. You can then execute a specific function and test its database integration by running adb shell cmd app_function execute-app-function while passing a raw JSON parameters string.

Instead of these ADB commands, you can also use the AppFunctions Testing Agent to inspect your configuration, list and execute AppFunctions, and even see how your AppFunctions behave in a real conversational flow.

Wrapping it up

When thinking about app features that can be contributed to the intelligence system using AppFunctions requires a slight shift in how we think about code and documentation. AppFunctions enable you to use this new interaction model for apps, which allows using an agent to access app features..

First, the AppFunctions development skill is an essential lifecycle tool, helping you discover features, implement and refine AppFunctions for your apps. Second, KDoc comments are a compiled API asset; clear parameter descriptions directly impact the execution accuracy of the system agent. Finally, Android MCP provides local-first execution allowing apps to safely collaborate with AI agents.

Contributing app features through AppFunctions makes your application ready for the intelligence system. Let us know how you are adapting your apps for the agentic era!

Learn more

Check out the other parts of this blog post series:
Part 1: Introduction of the app and a high-level overview.
Part 2: On-device intelligence. Deep-dive into ML Kit’s GenAI APIs and Gemini Nano to build privacy-first features like itinerary summarization, receipt parsing, and local audio processing.
Part 3: Hybrid and cloud reasoning. Explore how to use Firebase AI Logic to ground LLM answers in real-world data like Google Maps and web context.
Part 4 (this post!): System integration. Integrating with the Android intelligence system using AppFunctions.
Part 5 (coming soon): In-app agentic workflows. Extend the app with an end-to-end booking assistant powered by A2UI and ADK.

Interested in more on Android Development? Follow Android Developers on YouTube or LinkedIn!

All code snippets in this blog post follow the following copyright notice:

Copyright 2026 Google LLC.
SPDX-License-Identifier: Apache-2.0