Skip to content

Expose createTodo via Android AppFunctions (Gemini-ready capture)#13

Open
colonelpanic8 wants to merge 1 commit into
masterfrom
appfunctions-create-todo
Open

Expose createTodo via Android AppFunctions (Gemini-ready capture)#13
colonelpanic8 wants to merge 1 commit into
masterfrom
appfunctions-create-todo

Conversation

@colonelpanic8

Copy link
Copy Markdown
Owner

Summary

Adds Android AppFunctions (androidx.appfunctions Jetpack, Android 16+) support to the app, exposing a single @AppFunctioncreateTodo(title, notes?) — that captures a todo into Mova by POSTing to the user's org-agenda-api /capture endpoint. On-device agents (eventually Gemini) discover the function via the OS index and read its KDoc as the tool description.

This is the forward-looking half of making on-the-go capture hands-free: App Actions' modern replacement. Gemini invocation of third-party AppFunctions is currently in Google's private preview / EAP, so this ships the mechanism now so it lights up when the preview opens.

What it does

  • createTodo(appFunctionContext, title, notes?) — trims/validates the title, reads the user's stored server URL + credentials, and POSTs {"template":"default","values":{"Title":…, "Body":…}} with HTTP Basic auth (connect 8s / read 12s, matching the codebase).
  • Credentials are reused, not re-persisted: it reads the same mova_widget_prefs SharedPreferences (mova_api_url/mova_username/mova_password) that the RN app writes at login via SharedStorageModule and that QuickCaptureActivity already reads. Verified the file name and keys match the existing native code.
  • Agent-friendly failures: blank title → AppFunctionInvalidArgumentException; not-signed-in / network / server error → AppFunctionAppUnknownException with messages written for the model to read back.

Build / toolchain

  • androidx.appfunctions:appfunctions{,-service,-compiler} = 1.0.0-alpha08, plus the KSP plugin (2.1.20-2.0.1, matching RN 0.81's pinned Kotlin 2.1.20).
  • alpha08 is a deliberate pin — it's the newest release that supports the current compileSdk 36 / AGP 8.11 toolchain. alpha09 needs compileSdk 37 and alpha10 needs AGP 9.1 (and switches to an @AppFunctionServiceEntryPoint service model). Bump all three artifacts + revisit the API shape once Expo/RN moves to AGP 9 / compileSdk 37. No compileSdk/AGP bump required by this PR.
  • :app:compileDebugKotlin (incl. kspDebugKotlin) builds green in the nix develop --impure .#android shell; KSP generated app_functions_v2.xml with MovaAppFunctions#createTodo (title required, notes optional, CreateTodoResult schema, KDoc descriptions).

Not yet verified / follow-ups

  • No on-device E2E — no Android 16 device was available. To finish: install the debug build on an API 36 device, log into Mova once, then adb shell cmd app_function list-app-functions | grep com.colonelpanic.mova and invoke via Google's sample agent app.
  • The notes"Body" value assumes the default capture template has a Body field; it's only sent when non-blank and degrades to title-only otherwise. Worth confirming against the live template (QuickCaptureActivity discovers field names dynamically via /capture-templates; this function hardcodes Title like the wear CaptureClient).
  • expo prebuild --clean would drop these android/ edits (same caveat as the existing QuickCapture/widget native code).

🤖 Generated with Claude Code

Add androidx.appfunctions (Jetpack) support so on-device assistants/agents
(eventually Gemini) can capture a todo into Mova by voice. Implements a single
@appfunction, createTodo(appFunctionContext, title, notes?), that POSTs to the
user's org-agenda-api server at {apiUrl}/capture with
{"template":"default","values":{"Title":...}} over HTTP Basic auth, matching the
existing widget/QuickCapture capture path.

Credentials: reuses the native-readable copy the RN app already writes at login.
context/AuthContext.tsx -> widgets/storage.ts -> SharedStorageModule persists the
server URL + username + password into the "mova_widget_prefs" SharedPreferences
(keys mova_api_url/mova_username/mova_password), which QuickCaptureActivity already
reads natively. The AppFunction reads the same prefs via AppFunctionContext.context,
so no new persistence layer is introduced. No credentials -> AppFunctionAppUnknown
Exception with a "log in first" message; network/server error -> AppFunctionApp
UnknownException; blank title -> AppFunctionInvalidArgumentException. Timeouts are
bounded (connect 8s, read 12s) to match the codebase style.

Wiring:
- MainApplication implements AppFunctionConfiguration.Provider and registers a
  factory for the stateless MovaAppFunctions enclosing class.
- appfunctions-service supplies the platform AppFunctionService via its own merged
  manifest, so no manual <service> declaration is needed.
- The KSP appfunctions-compiler generates the app_functions_v2.xml descriptor that
  the OS indexes; verified createTodo (title required, notes optional) and its KDoc
  descriptions appear in the generated descriptor.

Version pinning / risk:
- Pinned to androidx.appfunctions 1.0.0-alpha08, the last release compatible with
  the current Expo SDK 54 / React Native 0.81 toolchain (compileSdk 36, AGP 8.11,
  Kotlin 2.1.20). alpha09 requires compileSdk 37 and alpha10 requires AGP 9.1;
  neither is available under Expo's pinned toolchain, so no compileSdk/AGP bump was
  made. Bump all three appfunctions artifacts (and revisit the AppFunctionContext
  vs entry-point API shape) once Expo moves to AGP 9 / compileSdk 37.
- KSP is added to the app module: plugin classpath pinned to 2.1.20-2.0.1 in
  android/build.gradle (matches RN's Kotlin 2.1.20); bump alongside any Kotlin bump.
- These native edits (gradle, MainApplication) live in the committed android/ project
  like the existing QuickCapture/widget code; a future `expo prebuild --clean` would
  need them reapplied, same as the existing custom native code.

Verified: `:app:compileDebugKotlin` (incl. kspDebugKotlin) succeeds in the
nix .#android dev shell. No device was available for
`adb shell cmd app_function list-app-functions`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant