Skip to content

Repository files navigation

FreshQuest

FreshQuest is a local pantry-expiry assistant and a proof of concept for bringing Android AppFunctions to a React Native app built with Expo.

It exposes native Android function contracts while keeping pantry validation, business logic, and SQLite ownership in TypeScript. This lets an Android caller such as Gemini add food or query expiring items through the same application logic used by the visible FreshQuest UI.

Demo

FreshQuest enabled in the AppFunctions Testing Agent connected apps screen Gemini confirming that milk was added to FreshQuest Milk added by Gemini appearing in the FreshQuest pantry
Android discovers FreshQuest Gemini invokes an AppFunction FreshQuest reads the same data

What this POC demonstrates

  • Android can discover statically declared AppFunctions from an Expo app.
  • An agent can convert natural language into a typed FreshQuest invocation.
  • A native Kotlin AppFunction can dispatch bundled React Native JavaScript, including when the FreshQuest UI is not running.
  • The AppFunction path and normal UI path can share one TypeScript service.
  • Both paths can read and write the same app-private expo-sqlite database.
  • A mounted FreshQuest screen can refresh after an external AppFunction write.
  • Retried additions are safe when the caller preserves the same operation ID.

FreshQuest features

  • Add, edit, and delete pantry items from the app.
  • Store a name, quantity, unit, category, and optional expiry date.
  • Group dated food by urgency and show the next expiry.
  • Filter the pantry by All, Use soon, or No date.
  • Follow Android wallpaper-derived Material 3 colors and system appearance.
  • Add multiple foods atomically through an AppFunction.
  • Query expired and soon-to-expire foods in deterministic use-first order.

Exposed AppFunctions

Function Purpose Important behavior
addPantryItems Add one to twenty pantry items Applies defaults, validates every item, writes the batch transactionally, and uses operationId for retry protection
findExpiringItems Find expired food and food expiring within a requested window Excludes undated items and returns deterministic expiry ordering

Editing and deleting items are currently FreshQuest UI operations; they are not exposed as AppFunctions.

Architecture

flowchart LR
    UI["FreshQuest UI"] --> Inventory["Inventory controller"]
    Inventory --> Service["PantryService"]

    Caller["Android caller / Gemini"] --> Native["Kotlin AppFunction"]
    Native --> Bridge["Expo TaskManager bridge"]
    Bridge --> Task["Headless JavaScript task"]
    Task --> Service

    Service --> Database[("freshquest.db<br/>expo-sqlite")]
Loading

The two entry paths intentionally meet at PantryService:

  • Native Android owns AppFunction discovery, typed platform contracts, request serialization, headless task dispatch, timeout handling, and Android error mapping.
  • TypeScript owns validation, defaults, expiry rules, business orchestration, SQLite migrations, transactions, retry behavior, and UI state.
  • SQLite is shared by the mounted UI runtime and the headless JavaScript task. Kotlin does not maintain a Room database or a second copy of pantry data.

End-to-end AppFunction flow

  1. Android discovers FreshQuest's annotated Kotlin functions and metadata.
  2. Gemini selects a function and prepares structured arguments from the user's request.
  3. Android invokes the Kotlin AppFunction.
  4. The native bridge asks Expo TaskManager to execute the registered JavaScript task.
  5. The task routes the request into the shared TypeScript PantryService.
  6. The service validates the request and reads or writes freshquest.db.
  7. The typed result travels back through JavaScript, Kotlin, Android, and the calling agent.
  8. FreshQuest displays the same persisted data when its UI loads or refreshes.

The JavaScript runtime can already be active (warm) or started for the background task (cold). After a fresh install, open FreshQuest once before testing an AppFunction so Expo can persist the task registration.

Technology

Area Technology
Application framework Expo SDK 57 and React Native 0.86
UI Expo UI Jetpack Compose components and Material 3
Navigation Expo Router
Persistence expo-sqlite
Background JavaScript transport expo-task-manager with a custom native consumer
Android functions AndroidX AppFunctions with Kotlin and KSP
Application logic TypeScript
Tests Jest, jest-expo, Robolectric, and Android connected tests

Run FreshQuest

Requirements

  • Node.js and npm
  • JDK 17
  • Android Studio with Android SDK 36
  • An Android 16 / API 36 emulator

From the repository root:

npm install
npm run android

FreshQuest contains a local native Expo module, so it must run as an Android development build rather than inside Expo Go.

Test with Gemini Agent

Google provides the AppFunctions Testing Agent for discovering, invoking, and debugging Android AppFunctions. Clone that repository and follow its README for the complete setup. After connecting FreshQuest, try prompts such as:

Add one bottle of milk to FreshQuest, expiring tomorrow.
Add two avocados to FreshQuest, expiring in two days.
Add one pack of oats to FreshQuest with no expiry date.
What food should I use first from FreshQuest during the next three days?

Validated POC scope

FreshQuest has been exercised on an Android 16 / API 36 arm64 emulator with:

  • AppFunction discovery and direct typed invocation;
  • Gemini-driven mutation and query prompts;
  • warm and process-cold JavaScript execution;
  • transactional SQLite writes and deterministic expiry queries;
  • UI refresh after externally initiated database changes; and
  • operation-ID replay protection across app restarts and deleted pantry rows.

About

Android AppFunctions for React Native and Expo. A FreshQuest demo where Gemini invokes headless JavaScript backed by shared TypeScript logic and expo-sqlite.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages