Este guia explica como integrar a API AppFunctions ao seu app Android, implementar a lógica de uma função e verificar se a integração funciona corretamente.
Compatibilidade de versões
Essa implementação exige que o compileSdk do projeto seja definido como o nível 36 da API ou mais recente.
Não é necessário verificar se as AppFunctions são compatíveis. Isso é
processado automaticamente na biblioteca AppFunctions do Jetpack.
AppFunctionManager retorna uma instância se o recurso for compatível e
retorna nulo se não for.
Dependências
Adicione as dependências de biblioteca necessárias ao arquivo build.gradle.kts (ou
build.gradle) do módulo e configure o plug-in KSP no módulo do app de nível superior, conforme
mostrado:
# Add this to your app module at the top level. For multi module applications,
# you only need to specify this once.
ksp {
arg("appfunctions:aggregateAppFunctions", "true")
}
dependencies {
implementation("androidx.appfunctions:appfunctions:1.0.0-alpha09")
implementation("androidx.appfunctions:appfunctions-service:1.0.0-alpha09")
// If this project uses any Kotlin source, use Kotlin Symbol Processing (KSP)
// See Add the KSP plugin to your project
ksp("androidx.appfunctions:appfunctions-compiler:1.0.0-alpha09")
}
Implementar a lógica do AppFunctions
Para implementar uma AppFunction no seu app Android, crie uma classe que implemente a lógica específica das AppFunctions. Isso envolve a criação de classes de dados serializáveis para parâmetros e respostas e, em seguida, o fornecimento da lógica principal no método da função.
O código a seguir mostra um exemplo de implementação para criar uma tarefa no app TODO, incluindo a definição de parâmetros e tipos de resposta personalizados e a lógica da função principal usando um repositório.
package com.example.android.appfunctions
import androidx.appfunctions.AppFunctionSerializable
import androidx.appfunctions.AppFunctionContext
import androidx.appfunctions.AppFunctionElementNotFoundException
import androidx.appfunctions.AppFunctionInvalidArgumentException
import androidx.appfunctions.service.AppFunction
import javax.inject.Inject
...
// Developers can provide additional parameters in the constructor if needed.
// This requires a custom factory setup in the next step.
class TaskFunctions @Inject constructor(
private val taskRepository: TaskRepository
) {
/** The parameter to create the task. */
@AppFunctionSerializable(isDescribedByKDoc = true)
data class CreateTaskParams(
/** The title of the task. */
val title: String,
/** The content of the task. */
val content: String
)
/** The user-created task. */
@AppFunctionSerializable(isDescribedByKDoc = true)
data class Task(
/** The ID of the task. */
val id: String,
/** The title of the task. */
val title: String,
/** The content of the task. */
val content: String
)
/**
* Creates a task based on [createTaskParams].
*
* @param createTaskParams The parameter to describe how to create the task.
*/
@AppFunction(isDescribedByKDoc = true)
suspend fun createTask(
appFunctionContext: AppFunctionContext,
createTaskParams: CreateTaskParams,
): Task = withContext(Dispatchers.IO) {
// Developers can use predefined exceptions to let the agent know
// why it failed.
if (createTaskParams.title == null && createTaskParams.content == null) {
throw AppFunctionInvalidArgumentException("Title or content should be non-null")
}
val id = taskRepository.createTask(
createTaskParams.title,
createTaskParams.content)
return taskRepository
.getTask(id)
?.toTask()
?: throw AppFunctionElementNotFoundException("Task not found for ID = $id")
}
// Maps internal TaskEntity
private fun TaskEntity.toTask() = Task(id = id, title = title, content = description)
}
Pontos principais sobre o código
- Por padrão, uma implementação de AppFunction é executada na linha de execução da interface do Android.
Portanto, uma operação de longa duração precisa fazer o seguinte:
- Declare a AppFunction como uma função de suspensão.
- Mude para um dispatcher de corrotina adequado quando a operação puder bloquear a linha de execução.
- Quando
isDescribedByKDocé definido comotrue, a descrição da função ou a descrição serializável é codificada como parte doAppFunctionMetadatapara ajudar o agente a entender como usar a AppFunction do app.
Opcional: use o Hilt para fornecer uma fábrica AppFunction personalizada
Se a classe de implementação AppFunction exigir dependências no
construtor (como em TaskRepository no exemplo anterior), você precisará
fornecer uma fábrica personalizada para que o sistema saiba como instanciá-la. Essa etapa é opcional e só é necessária se a classe de função tiver parâmetros de construtor. Este exemplo mostra como criar um AppFunctionFactory personalizado e
configurá-lo na classe Application usando o Hilt para injeção
de dependência.
import android.app.Application
import androidx.appfunctions.service.AppFunctionConfiguration
import com.example.android.appfunctions.TaskFunctions
import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject
@HiltAndroidApp
class TodoApplication : Application(), AppFunctionConfiguration.Provider {
@Inject lateinit var taskFunctions: TaskFunctions
override fun onCreate() {
super.onCreate()
}
// This shows how AppFunctions works with Hilt.
override val appFunctionConfiguration: AppFunctionConfiguration
get() =
AppFunctionConfiguration.Builder()
.addEnclosingClassFactory(TaskFunctions::class.java) { taskFunctions }
.build()
}
Opcional: alternar a disponibilidade de AppFunction no momento da execução
Use a API AppFunctionManager para ativar ou desativar funções explicitamente ao
controlar o acesso às AppFunctions. O gating pode ser útil quando determinados recursos do app
não estão disponíveis para todos os usuários. Ao ativar ou desativar dinamicamente as
AppFunctions, o sistema de inteligência sabe exatamente quais recursos estão disponíveis
para o usuário a qualquer momento.
Para restringir com segurança as AppFunctions que exigem um estado de conta específico, siga um processo de duas etapas:
Etapa 1. Desativar a função por padrão
Para evitar que a função fique acessível antes que a flag de recurso seja
verificada, defina o parâmetro isEnabled da anotação @AppFunction como
false.
@AppFunction(isEnabled = false, isDescribedByKDoc = true)
suspend fun createTask(...) { ... }
Etapa 2: Ativar dinamicamente a função no ambiente de execução
Para cada classe AppFunction, o compilador gera uma classe correspondente
que contém constantes de ID de função (usando um sufixo Ids). É possível usar essas constantes de ID geradas com o método setAppFunctionEnabled de AppFunctionManagerCompat para mudar o estado ativado de uma função durante a execução.
import androidx.appfunctions.AppFunctionManager
// Assuming there is a hook API to observe user state or feature flags
suspend fun onFeatureEnabled() {
try {
AppFunctionManager.getInstance(context)
.setAppFunctionEnabled(
// Function ID is generated for developer to get
TaskFunctionsIds.CREATE_TASK_ID,
AppFunctionManagerCompat.APP_FUNCTION_STATE_ENABLED,
)
} catch (e: Exception) {
// Handle exception: AppFunctions indexation may not be fully completed
// upon initial app startup.
}
}
suspend fun onFeatureDisabled() {
AppFunctionManagerCompat.getInstance(context)
.setAppFunctionEnabled(
TaskFunctionsIds.CREATE_TASK_ID,
AppFunctionManagerCompat.APP_FUNCTION_STATE_DISABLED,
)
}
Considerações sobre os tipos de funcionalidades a serem disponibilizadas
A segurança é sempre fundamental. Ao escolher quais recursos do seu app disponibilizar como AppFunctions, é importante lembrar que os agentes do sistema podem processar consultas do usuário no servidor para aproveitar recursos avançados de LLM.
Para oferecer uma ótima experiência ao usuário e evitar a exposição de informações sensíveis, recomendamos seguir estas diretrizes:
- Funcionalidade que se beneficia da linguagem natural: disponibilize tarefas que sejam mais fáceis de expressar em uma conversa do que por navegação manual na interface do usuário.
- Acesso restrito: crie AppFunctions que só dão ao agente acesso a dados e ações necessários para atender à solicitação específica do usuário.
- Informações não sensíveis: compartilhe apenas dados que não sejam altamente pessoais ou confidenciais, ou dados que o usuário concorda explicitamente em compartilhar no contexto da ação.
- Confirmação inequívoca para qualquer ação destrutiva: tenha muito cuidado com funções que realizam ações destrutivas, como exclusão de dados. Embora o agente possa invocar essas ações, o app precisa incluir uma etapa de confirmação própria e usar uma linguagem clara e sem ambiguidades sobre as intenções. Também é útil adicionar mais de uma etapa de confirmação para garantir que o usuário esteja ciente do que está sendo solicitado.
Verificar a integração do AppFunction
Para verificar se você integrou corretamente o AppFunctions, use adb
shell cmd app_function.
Use adb shell cmd app_function list-app-functions | grep --after-context 10
$myPackageName para ver detalhes das AppFunctions que seu app oferece.
No Gemini no Android Studio ou em outros agentes da sua escolha, forneça um comando como o seguinte.
Execute `adb shell cmd app_function` to learn how the tool works, then act as a
chat agent aiming to invoke AppFunctions to fulfil user prompts for this app.
Rely on the AppFunction description as instructions.