AppFunctions allow an Android app to expose functionality that can then be used by AI agents and assistants running on the device. With the recent announcement of Android MCP (which is powered by the AppFunctions API and allows Gemini to make use of functionality in your app) I thought it was a good time to look at adding AppFunctions support to the Confetti conference app. This article will outline the changes made to do that (included in this PR).
✨ This is huge 👀: We're launching Android MCP, powered by the AppFunctions API!
— Mishaal Rahman (@MishaalRahman) October 28, 2026
This lets your app integrate with @GeminiApp on Android so your users can ask questions that pull info from your app or take actions in it.
To support this, we're also launching a preview of the… pic.twitter.com/FARaS5NuAC
The idea is to allow an agent to answer questions about the sessions in the conference that’s currently selected in Confetti and also to bookmark/unbookmark sessions on behalf of the user. We’ll expose 4 functions to do that: getConference, listSessions, addBookmark and removeBookmark.
Dependencies
We’re using 1.0.0-beta01 of the AppFunctions library. We need the following dependencies (along with the KSP plugin which is used to generate code and metadata from our annotated functions). Note also that AppFunctions requires compileSdk 37 and targetSdk 36 or later.
libs.versions.toml
[versions]
...
androidx-appfunctions = "1.0.0-beta01"
[libraries]
...
androidx-appfunctions = { module = "androidx.appfunctions:appfunctions", version.ref = "androidx-appfunctions" }
androidx-appfunctions-compiler = { module = "androidx.appfunctions:appfunctions-compiler", version.ref = "androidx-appfunctions" }build.gradle.kts
implementation(libs.androidx.appfunctions)
ksp(libs.androidx.appfunctions.compiler)Implementation
The functions are declared in an abstract class extending AppFunctionService and annotated with @AppFunctionServiceEntryPoint. KSP then generates the concrete service (ConfettiAppFunctionService in our case) along with the XML schema that describes the functions to the system. Each function is annotated with @AppFunctionDeclaration. Confetti uses Koin so we just make the class a KoinComponent and inject the existing repositories as we would elsewhere (the functions themselves just call in to existing repository code).
The following shows how those pieces fit together.

@RequiresApi(36)
@AppFunctionServiceEntryPoint(
serviceName = "ConfettiAppFunctionService",
appFunctionXmlFileName = "confetti_app_function_service",
)
abstract class BaseConfettiAppFunctionService : AppFunctionService(), KoinComponent {
private val repository: ConfettiRepository by inject()
private val appSettings: AppSettings by inject()
private val authentication: Authentication by inject()
/**
* Lists every session in the currently selected conference.
*
* @return All sessions with their ID, title, description and start/end times.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun listSessions(): List<SessionResponse> = withContext(Dispatchers.IO) {
val conference = requireConference()
val user = authentication.currentUser.value
val response = repository.sessions(conference, user?.uid, user, FetchPolicy.CacheFirst)
response.data?.sessions?.nodes?.map { it.sessionDetails.toResponse() } ?: emptyList()
}
/**
* Bookmarks a session in the currently selected conference. Requires the user to be
* signed in to Confetti.
*
* @param sessionId ID of the session to bookmark, as returned in [SessionResponse.id].
* @return true if the bookmark was saved.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun addBookmark(sessionId: String): Boolean = withContext(Dispatchers.IO) {
val conference = requireConference()
val user = requireSignedInUser()
repository.addBookmark(conference, user.uid, user, sessionId)
}
// getConference, removeBookmark and helper functions omitted
...
}Note that isDescribedByKDoc = true means the KDoc is what gets passed to the LLM….so it’s worth spending a bit of time on those descriptions. The same applies to @AppFunctionSerializable classes (where the KDoc needs to be inline on each property). Session times are returned as LocalDateTime which is one of the types the library natively supports.
/** A session (talk or workshop) in a conference. */
@AppFunctionSerializable(isDescribedByKDoc = true)
data class SessionResponse(
/** Unique ID of the session, used to add or remove a bookmark. */
val id: String,
/** Title of the session. */
val title: String,
/** Abstract describing the session content, if provided. */
val description: String?,
/** Start time in the conference's local time zone. */
val startsAt: LocalDateTime,
/** End time in the conference's local time zone. */
val endsAt: LocalDateTime,
)We also need to register the generated service in AndroidManifest.xml…
AndroidManifest.xml
<service
android:name="dev.johnoreilly.confetti.functions.ConfettiAppFunctionService"
android:permission="android.permission.BIND_APP_FUNCTION_SERVICE"
android:exported="true"
tools:targetApi="36">
<property
android:name="android.app.appfunctions.schema"
android:value="app_functions_schema.xsd" />
<property
android:name="android.app.appfunctions.v2"
android:value="confetti_app_function_service.xml" />
<intent-filter>
<action android:name="android.app.appfunctions.AppFunctionService" />
</intent-filter>
</service>
<property
android:name="android.app.appfunctions.app_metadata"
android:resource="@xml/app_metadata" />…along with an app_metadata.xml file which provides an overall description of the app (including, for example, how functions relate to each other).
app_metadata.xml
<AppFunctionAppMetadata xmlns:appfn="http://schemas.android.com/apk/androidx.appfunctions"
appfn:instructions="Confetti is a conference schedule app: it lists the sessions of the conference the user has selected and manages the user's session bookmarks.
Operational patterns:
- 'listSessions' returns the session IDs that 'addBookmark' and 'removeBookmark' take.
- All functions act on the currently selected conference, which 'getConference' returns.
Constraints:
- A conference must already be selected in the app; switching conference is not supported.
- Adding or removing bookmarks requires the user to be signed in to Confetti." />Testing
The quickest way to test is from the command line using adb.
adb shell cmd app_function list-app-functions --package dev.johnoreilly.confetti
adb shell "cmd app_function execute-app-function --package dev.johnoreilly.confetti \
--function 'dev.johnoreilly.confetti.functions.BaseConfettiAppFunctionService#getConference' \
--parameters '{}' --brief-yaml"I also used the AppFunction Testing Agent app from the android/appfunctions repo. The Debug tab there shows the functions it has discovered for an app and allows you to run them directly.

And the Agent Demo tab lets you interact with those functions using Gemini. In the following, with droidcon London 2026 selected in Confetti, I asked which sessions were about agents. Gemini called getConference and listSessions and then grouped the relevant sessions by day/time.

I then asked it to bookmark one of those sessions (with Gemini working out the session id itself from the earlier listSessions result). That bookmark then showed up as expected in the Confetti Bookmarks tab.

Next step will be trying this out with Gemini using Android MCP once I’m able to. I’m also interested in looking at the experimental support for returning A2UI content from AppFunctions (something used for example in the ChatApp sample in that same android/appfunctions repo).
The code shown here is part of the androidApp module in the Confetti repository.