@Retention(CLASS) @Target({METHOD})

public @interface AppIntent

Exposes a capability of your application to the system: Siri, Spotlight, the Shortcuts app, an Android launcher shortcut, a widget button, or a language model.

@AppIntent(value = "log_workout", title = "Log a workout",
        description = "Records a completed workout",
        phrases = {"Log a workout in ${applicationName}"},
        headless = true, timeoutSeconds = 5)
public static IntentResult logWorkout(
        @IntentParam(value = "kind", title = "What kind of workout?",
                     options = {"run", "ride", "swim"}) String kind,
        @IntentParam(value = "minutes", title = "How many minutes?") int minutes) {
    WorkoutStore.append(kind, minutes);
    return IntentResult.spoken("Logged a " + minutes + " minute " + kind + ".");
}

The handler must be public static

This is not a style preference. The build generates a direct static call to your method, and a direct call is the only form that survives the iOS translator’s dead-code elimination and Android’s obfuscation – a reflective lookup would be stripped on iOS and renamed on Android, in both cases silently. static also makes the contract visible: a handler can be asked to run in a process that exists only to answer it, where no instance of yours has been constructed and nothing is on screen.

The method returns com.codename1.intents.IntentResult, or void when it has nothing to report. It may take com.codename1.intents.IntentContext as its first parameter to see the deadline, the source and the cancellation flag. Every other parameter must carry IntentParam.

At build time

The Codename One Maven plugin scans the project’s compiled bytecode, validates every @AppIntent, and generates both the reflection-free dispatch table and the native declarations each platform compiles into the app. A malformed declaration fails the build rather than going quiet on a device.

Phrases

Apple enforces three rules on a spoken phrase, all of them as build failures that produce no App Intents metadata at all. The build checks them here instead, so the message names your declaration rather than arriving as an opaque failure from appintentsmetadataprocessor:

  • Every phrase must contain ${applicationName}.
  • A phrase may reference at most one parameter. Write one phrase per parameter rather than combining them.
  • A phrase parameter must be an IntentEntity type. A primitive cannot appear in a phrase, which is not much of a loss: leave it out and the platform still asks for it, using the title on its IntentParam.

An intent that declares phrases must also be discoverable, since a phrase is only reachable through an App Shortcut.

Phrases are ignored on platforms with no voice invocation, which today means Android.

Methods

public abstract String value()The stable id, matching [a-z][a-z0-9_]{2,63}.
public abstract String title()The human-readable name shown in the Shortcuts app.
public abstract String description() default ""A longer explanation shown alongside the title.
public abstract String[] phrases() default {}Spoken phrases that invoke this intent.
public abstract boolean headless() default falseTrue when this intent may run without bringing the app to the foreground.
public abstract boolean discoverable() default trueTrue when the platform may offer this intent before the user has ever run it.
public abstract boolean destructive() default falseTrue when the platform should confirm with the user before running this.
public abstract String opensRoute() default ""A route template to open instead of answering in place, for example /orders/{orderId}, where each {name} names one of this intent’s parameters.
public abstract Exposure[] exposure() default {Exposure.ASSISTANT}Which consumers this intent is offered to.
public abstract int timeoutSeconds() default 20How long the handler may run before the framework reports a failure.

Method details

value

public abstract String value()

The stable id, matching [a-z][a-z0-9_]{2,63}. Required.

It is stable in the strong sense: the system stores it in donated shortcuts and the user’s own Shortcuts workflows, so renaming one breaks what people already built.

title

public abstract String title()
The human-readable name shown in the Shortcuts app. Required.

description

public abstract String description() default ""
A longer explanation shown alongside the title.

phrases

public abstract String[] phrases() default {}
Spoken phrases that invoke this intent. Each must contain ${applicationName}.

headless

public abstract boolean headless() default false

True when this intent may run without bringing the app to the foreground.

A headless handler must not touch Form, Dialog, or anything else needing a window; see the com.codename1.intents package documentation for the full contract.

Leaving it false brings the app forward, which gives the handler a window to act on – not permission to touch one from the handler’s own thread. No handler runs on the event dispatch thread, so a foreground handler updates the user interface through Display#callSerially, as any background thread does.

discoverable

public abstract boolean discoverable() default true
True when the platform may offer this intent before the user has ever run it. False restricts it to appearing after a donation.

destructive

public abstract boolean destructive() default false

True when the platform should confirm with the user before running this. Set it on anything that deletes, sends, or spends.

It also closes the paths that cannot confirm. A destructive intent is not published as an Android launcher shortcut, is refused when an unauthenticated caller asks for it, and is not donated – each of those runs on a single tap with nothing in between. The capability stays fully available through the assistant and the Shortcuts app, which confirm first; what goes away is the one-tap route to it.

opensRoute

public abstract String opensRoute() default ""

A route template to open instead of answering in place, for example /orders/{orderId}, where each {name} names one of this intent’s parameters.

The URL is resolved through the same Route table that handles deep links, so an intent and a link to the same screen cannot drift apart. A non-empty value is also what tells the platform to open the app.

exposure

public abstract Exposure[] exposure() default {Exposure.ASSISTANT}
Which consumers this intent is offered to. Defaults to the platform only; add Exposure.MODEL to also offer it to a language model through com.codename1.intents.Intents#asTools().

timeoutSeconds

public abstract int timeoutSeconds() default 20

How long the handler may run before the framework reports a failure.

The platform usually allows around twenty seconds. Do not use them: a spoken interaction that takes ten seconds has already failed as an interaction. Aim under two, and return IntentResult.opens(...) for anything genuinely slower.