Resposta rápida: use App Shortcuts no Android para levar o usuário diretamente a tarefas frequentes sem obrigá-lo a abrir a home do app e repetir a navegação. Declare atalhos estáticos em XML para ações universais e estáveis, publique atalhos dinâmicos com ShortcutManagerCompat para destinos personalizados, permita atalhos fixados quando a pessoa quiser colocar uma ação na tela inicial e marque como long-lived apenas conversas ou entidades que precisam participar de superfícies do sistema. Cada atalho deve abrir um destino válido, ter rótulo curto, ícone claro e comportamento seguro mesmo após logout, atualização ou restauração do processo.
Um toque longo no ícone pode virar “Nova tarefa”, “Escanear produto”, “Abrir pedido recente” ou “Conversar com Ana”. Essa economia de poucos segundos é valiosa em apps usados várias vezes por dia — logística, vendas, mensagens, finanças, saúde, delivery e ferramentas internas. O recurso, porém, não é um menu secreto para despejar funcionalidades. Bons atalhos representam tarefas, não departamentos do aplicativo.
Este guia mostra quando usar cada tipo, como configurar shortcuts.xml, publicar atalhos com Kotlin, integrar com Compose e deep links, atualizar destinos, medir uso e evitar os erros que aparecem em produção.
Antes de fixar números de limites ou detalhes de API, consulte a documentação oficial de App Shortcuts. Launchers e versões do Android podem apresentar o recurso de formas diferentes; projete para degradação segura.
Qual tipo de App Shortcut escolher?
A decisão principal é simples:
| Tipo | Quem cria | Melhor uso | Pode mudar com frequência? |
|---|---|---|---|
| Estático | Desenvolvedor, em XML | Ação igual para todos, como “Nova tarefa” | Não; muda com atualização do app |
| Dinâmico | App, em runtime | Conversa, projeto, loja ou pedido relevante | Sim |
| Fixado | Usuário, após solicitação do app | Ação que merece ícone próprio na tela inicial | O usuário controla a presença |
| Long-lived | App, como propriedade do atalho dinâmico | Conversas e entidades usadas por superfícies do sistema | Sim, com identidade estável |
Em geral, comece com dois ou três atalhos realmente úteis. A ordem e a quantidade visível dependem do launcher, então a experiência principal nunca pode depender de todos aparecerem.
Regra de produto
Um atalho é bom quando completa esta frase:
“A pessoa toca aqui para fazer algo específico agora.”
“Pedidos” é apenas uma seção. “Acompanhar pedido atual” é uma tarefa. “Clientes” é uma seção. “Cadastrar cliente” é uma tarefa. Essa diferença melhora descoberta, acessibilidade e mensuração.
Atalhos estáticos com shortcuts.xml
Atalhos estáticos funcionam bem para ações que não dependem de conta, histórico ou personalização. Crie o arquivo res/xml/shortcuts.xml:
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:shortcutId="nova_tarefa"
android:enabled="true"
android:icon="@drawable/ic_shortcut_add"
android:shortcutShortLabel="Nova tarefa"
android:shortcutLongLabel="Criar uma nova tarefa">
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="br.dev.exemplo"
android:targetClass="br.dev.exemplo.MainActivity"
android:data="https://exemplo.com/tarefas/nova" />
</shortcut>
</shortcuts>
Categorias de shortcut não são tags de SEO nem agrupamentos visuais livres: elas comunicam semântica ao sistema. Use a categoria de conversa somente quando o destino representar uma conversa real.
Depois, referencie o XML na Activity principal do AndroidManifest.xml:
<activity
android:name=".MainActivity"
android:exported="true">
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
<!-- filtros normais da Activity -->
</activity>
Use IDs estáveis e legíveis. Trocar nova_tarefa por um UUID em cada build impede o sistema de reconhecer continuidade. Também mantenha o rótulo curto de verdade: launchers podem truncar textos longos.
Cuidado: atalhos estáticos não devem carregar identificadores de usuário, tokens, pedido atual ou qualquer estado que envelheça. Para isso existem atalhos dinâmicos.
Abrindo o destino com deep link
O atalho não deve conhecer detalhes internos da árvore de Composables. Ele entrega uma Intent; uma fronteira de navegação transforma a URI em destino tipado. É o mesmo princípio do guia de App Links e deep links com Kotlin.
sealed interface AppDestination {
data object Home : AppDestination
data object NovaTarefa : AppDestination
data class Pedido(val id: String) : AppDestination
}
fun Intent.toDestination(): AppDestination {
val uri = data ?: return AppDestination.Home
val segments = uri.pathSegments
return when {
segments == listOf("tarefas", "nova") -> AppDestination.NovaTarefa
segments.size == 2 && segments[0] == "pedidos" ->
AppDestination.Pedido(segments[1])
else -> AppDestination.Home
}
}
Valide host, esquema, caminho e parâmetros antes de navegar. Um atalho pode sobreviver a atualização, backup ou mudança de sessão. Se o pedido não existe mais, abra uma tela de estado vazio com saída clara; não deixe a Activity fechar silenciosamente.
Em Compose, processe a entrada em um ponto único e também trate novas intents quando a Activity já estiver aberta:
class MainActivity : ComponentActivity() {
private val shortcutDestinations = MutableSharedFlow<AppDestination>(
extraBufferCapacity = 1,
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
shortcutDestinations.tryEmit(intent.toDestination())
setContent {
AppRoot(shortcutDestinations)
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
shortcutDestinations.tryEmit(intent.toDestination())
}
}
Se o app usa Navigation Compose ou Navigation 3, converta AppDestination para a rota adotada pelo projeto. Não chame navigate() em vários lugares em resposta à mesma intent, pois isso cria telas duplicadas. O guia de Navigation 3 com Compose ajuda a estruturar destinos tipados.
Atalhos dinâmicos com ShortcutManagerCompat
Atalhos dinâmicos devem refletir contexto recente e legítimo: as conversas mais usadas, o projeto ativo ou a entrega em andamento. A biblioteca AndroidX Core permite manter uma implementação compatível:
fun publicarAtalhoDePedido(
context: Context,
pedido: Pedido,
) {
val intent = Intent(
Intent.ACTION_VIEW,
"https://exemplo.com/pedidos/${pedido.id}".toUri(),
context,
MainActivity::class.java,
)
val shortcut = ShortcutInfoCompat.Builder(
context,
"pedido_${pedido.id}",
)
.setShortLabel("Pedido ${pedido.codigo}")
.setLongLabel("Acompanhar pedido ${pedido.codigo}")
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_package))
.setIntent(intent)
.build()
ShortcutManagerCompat.pushDynamicShortcut(context, shortcut)
}
pushDynamicShortcut() é útil quando o aplicativo aprende que uma entidade acabou de se tornar relevante. Para publicar um conjunto calculado, use as operações de atalhos dinâmicos da API compatível conforme a estratégia do projeto.
O código de produção precisa responder:
- qual evento torna o atalho relevante?
- quando ele deixa de ser relevante?
- o ID continuará o mesmo após sincronização?
- o rótulo expõe informação privada na tela do aparelho?
- o destino funciona offline?
- o que acontece depois do logout?
Não atualize atalhos em cada recomposição, abertura de tela ou emissão de Flow. O sistema aplica limites de frequência a operações de publicação. Atualize quando a lista relevante realmente mudar e evite loops de escrita.
Ranking e seleção: mostre o que ajuda agora
O launcher dispõe de pouco espaço. Em vez de publicar tudo, selecione os destinos com uma regra explicável.
Exemplo de pontuação:
data class CandidatoAtalho(
val id: String,
val titulo: String,
val ultimoAcessoEpoch: Long,
val fixadoPeloUsuario: Boolean,
val disponivel: Boolean,
)
fun selecionarAtalhos(
candidatos: List<CandidatoAtalho>,
limite: Int,
): List<CandidatoAtalho> = candidatos
.asSequence()
.filter { it.disponivel }
.sortedWith(
compareByDescending<CandidatoAtalho> { it.fixadoPeloUsuario }
.thenByDescending { it.ultimoAcessoEpoch },
)
.take(limite)
.toList()
Na prática, atalhos fixados são controlados pelo launcher e não devem ser confundidos com itens apenas “favoritados” dentro do app. O exemplo mostra uma regra de produto para priorizar candidatos; consulte o estado real das APIs antes de remover ou desabilitar qualquer atalho.
Boas fontes de relevância:
- ação concluída recentemente;
- frequência de uso com decaimento temporal;
- entidade favoritada explicitamente;
- tarefa ativa com prazo curto;
- disponibilidade local e permissão atual.
Evite usar dados sensíveis ou inferências opacas para ranking. “Última conversa aberta” é compreensível. “Pessoa com maior probabilidade de receber cobrança” não deve aparecer inesperadamente no launcher.
Atalho fixado na tela inicial
Um atalho fixado ganha um ícone próprio, próximo aos apps e widgets. Ofereça isso depois que o usuário demonstrar intenção: ao favoritar uma loja, acompanhar uma rota todos os dias ou usar repetidamente uma ação.
fun solicitarAtalhoFixado(
context: Context,
projetoId: String,
projetoNome: String,
): Boolean {
if (!ShortcutManagerCompat.isRequestPinShortcutSupported(context)) {
return false
}
val shortcut = ShortcutInfoCompat.Builder(context, "projeto_$projetoId")
.setShortLabel(projetoNome)
.setLongLabel("Abrir projeto $projetoNome")
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_project))
.setIntent(
Intent(
Intent.ACTION_VIEW,
"https://exemplo.com/projetos/$projetoId".toUri(),
context,
MainActivity::class.java,
),
)
.build()
return ShortcutManagerCompat.requestPinShortcut(context, shortcut, null)
}
A solicitação não garante que o launcher adicionará o atalho: pode haver confirmação do usuário, falta de suporte ou política do dispositivo. Mostre a opção como melhoria, nunca como etapa obrigatória de onboarding.
Para uma presença que exibe informação e muda visualmente, talvez um widget com Jetpack Glance seja melhor. A diferença é útil:
- atalho: inicia uma tarefa;
- widget: mostra estado e pode oferecer poucas ações;
- notificação: alerta sobre um evento;
- deep link: define como uma URL chega a um destino.
Conversas e atalhos long-lived
Apps de mensagens podem publicar atalhos associados a pessoas e marcá-los como long-lived. Isso permite integração mais rica com superfícies de conversa do Android, desde que o app mantenha identidade consistente para a pessoa e o canal.
Conceitualmente:
val pessoa = Person.Builder()
.setName(contato.nome)
.setKey(contato.id)
.setImportant(contato.favorito)
.build()
val shortcut = ShortcutInfoCompat.Builder(context, "conversa_${contato.id}")
.setShortLabel(contato.nome)
.setPerson(pessoa)
.setLongLived(true)
.setIntent(abrirConversaIntent(contato.id))
.build()
Não marque qualquer tela como conversa para ganhar visibilidade. O atalho precisa representar comunicação real e deve se alinhar às notificações daquela conversa. Foto, nome e conteúdo podem aparecer fora do app; respeite preferências de privacidade, perfil de trabalho e bloqueio de tela.
Login, permissões e estado expirado
Todo atalho é uma entrada externa. Trate estes cenários antes de publicar:
Usuário deslogado
Guarde o destino solicitado, leve ao login e retome somente depois de autenticação bem-sucedida. Não inclua token de sessão na URI.
Entidade removida
Exiba “Este pedido não está mais disponível” e ofereça voltar à lista. Em seguida, remova ou desabilite o atalho dinâmico obsoleto quando apropriado.
Permissão negada
Um atalho “Escanear” pode chegar à câmera sem permissão. Explique o valor, solicite no contexto e ofereça alternativa. Siga o guia de permissões Android com Kotlin.
Sem rede
Se o destino existe no cache, abra em modo offline. Caso contrário, mostre estado de conexão e retry. Uma arquitetura offline-first no Android evita que o atalho pareça quebrado.
Processo já aberto
Trate onNewIntent() e defina corretamente o comportamento da Activity. Teste para que tocar duas vezes não empilhe destinos duplicados.
Ícones, rótulos e acessibilidade
O ícone precisa ser reconhecível em tamanho pequeno e funcionar com máscaras e estilos do launcher. Prefira desenho simples, bom contraste e recurso específico para o atalho. Não reutilize uma ilustração detalhada de onboarding.
Para os textos:
- comece com verbo quando a ação for o principal: “Criar tarefa”, “Escanear item”;
- use substantivo específico quando abrir uma entidade: “Pedido 4821”, “Equipe Mobile”;
- não dependa apenas de cor ou ícone para diferenciar ações;
- evite emoji como substituto de ícone;
- localize rótulos com recursos de string;
- não coloque texto promocional como “Oferta imperdível”.
O atalho também precisa abrir uma tela acessível. Revise foco, descrição de componentes e tamanho de toque com o guia de acessibilidade no Android e Compose.
Como testar App Shortcuts
Teste em pelo menos um launcher real além do emulador principal. Fabricantes podem variar apresentação, quantidade visível e confirmação de pin.
Checklist manual:
- instale o app limpo e pressione o ícone por alguns segundos;
- confirme rótulo, ícone, ordem e truncamento;
- abra cada atalho com o processo encerrado;
- abra novamente com o app em foreground e background;
- teste usuário autenticado e deslogado;
- remova a entidade de destino e tente o atalho antigo;
- desligue rede e repita;
- altere idioma e tamanho de fonte;
- solicite um atalho fixado e cancele a confirmação;
- atualize o app sem limpar dados e verifique IDs existentes.
Para validar a resolução de uma intent via ADB:
adb shell am start \
-a android.intent.action.VIEW \
-d "https://exemplo.com/pedidos/4821" \
br.dev.exemplo
Esse comando testa o contrato do destino, mas não substitui o toque real no launcher. Para cobertura automatizada da tela aberta, combine testes instrumentados e fluxos de UI descritos no guia de testes Android com Compose e Maestro.
Analytics sem contar abertura duas vezes
Meça o atalho no ponto em que a entrada é interpretada, antes da navegação, e passe uma origem interna como app_shortcut. Não adicione informação sensível à URL pública apenas para analytics.
Eventos úteis:
app_shortcut_opened, com tipo e ID lógico não sensível;app_shortcut_destination_unavailable;app_shortcut_pin_requested;app_shortcut_pin_supported;- conclusão da tarefa iniciada pelo atalho.
A métrica importante não é apenas abertura. Compare a taxa de conclusão e o tempo até a ação com o fluxo iniciado pela home. Um atalho que recebe muitos toques acidentais e poucas conclusões precisa de rótulo ou seleção melhor.
Evite registrar o evento uma vez em onCreate() e novamente no Composable. Modele a intent como evento consumível, preserve o ID da entrada durante a navegação e deduplique conforme a arquitetura de analytics do app.
Erros comuns
Publicar atalhos demais
O launcher mostra poucos e pode ignorar o restante. Priorize. Se o usuário precisa estudar uma lista, a lista pertence ao app.
Usar ID instável
IDs aleatórios quebram atualização, remoção e continuidade de atalhos fixados. Derive o ID de uma entidade estável, sem usar dado pessoal em texto puro quando isso expõe informação.
Colocar token na URI
Intents e logs podem expor dados. O atalho leva a um identificador; autenticação e autorização continuam sendo responsabilidade do app e do backend.
Esquecer o logout
Remova ou desabilite atalhos dinâmicos vinculados à conta quando a sessão terminar. Um rótulo com nome de cliente pode vazar contexto mesmo que a tela exija login.
Atualizar em excesso
Publicação frequente encontra rate limits e gasta recursos. Calcule a lista quando eventos de domínio mudarem a relevância, não em timer curto.
Confundir shortcut com background work
O atalho inicia uma interação. Sincronização confiável e adiável pertence ao WorkManager; tarefa contínua visível pode exigir foreground service. Não tente manter o processo vivo depois do toque sem justificativa.
Checklist de produção
Antes de liberar App Shortcuts, confirme:
- cada item representa uma tarefa específica;
- atalhos estáticos não dependem de estado variável;
- atalhos dinâmicos usam IDs estáveis;
- rótulos são curtos, localizados e não promocionais;
- ícones permanecem legíveis no launcher;
- URIs são validadas antes da navegação;
- login, logout e autorização foram testados;
- destino removido possui fallback útil;
- o app funciona quando o processo estava morto;
-
onNewIntent()não duplica navegação; - atualizações respeitam limites do sistema;
- dados privados não aparecem no rótulo sem necessidade;
- analytics registra origem uma única vez;
- o fluxo principal continua acessível sem atalhos.
Perguntas frequentes
Quantos App Shortcuts devo criar?
Crie poucos e priorizados — normalmente duas a quatro tarefas já permitem validar valor. O número efetivamente exibido varia por launcher e dispositivo. Consulte maxShortcutCountPerActivity nas APIs da plataforma quando precisar ajustar publicação dinâmica, mas não trate o máximo técnico como meta de produto.
App Shortcut funciona com Jetpack Compose?
Sim. O launcher entrega uma Intent à Activity; Compose entra depois, quando o app converte essa entrada em estado ou destino de navegação. O cuidado principal é tratar tanto onCreate() quanto onNewIntent() sem disparar navegação duplicada.
Qual a diferença entre atalho estático e dinâmico?
O estático é declarado no APK e serve para ações universais que mudam raramente. O dinâmico é publicado em runtime e pode representar contexto personalizado, como conversa recente ou projeto ativo.
Posso criar um atalho fixado sem confirmação?
Não conte com isso. A solicitação passa pelo launcher e normalmente depende de ação ou confirmação do usuário. Primeiro verifique suporte e trate retorno sem prometer que o ícone foi criado.
Devo usar URL HTTPS ou uma action customizada?
URL HTTPS é uma boa escolha quando o destino também existe como contrato público e pode aproveitar App Links. Uma action explícita pode ser adequada para ação estritamente interna. Em ambos os casos, valide a entrada, mantenha IDs estáveis e não transporte segredos.
App Shortcuts melhoram retenção?
Podem melhorar quando reduzem passos de uma tarefa frequente. Não há ganho automático: meça conclusão, recorrência e remoção. Atalho irrelevante vira ruído no launcher.
Conclusão
App Shortcuts são uma camada pequena de interface com impacto desproporcional em tarefas recorrentes. A implementação robusta combina quatro decisões: escolher ações úteis, manter identidade estável, tratar a intent como entrada não confiável e atualizar somente quando a relevância muda.
Comece com um atalho estático para uma ação universal e um dinâmico para o contexto mais valioso do produto. Teste processo morto, logout, destino expirado e launcher real. Depois use analytics para responder à pergunta que importa: o atalho ajudou a pessoa a concluir a tarefa mais rápido? Se a resposta for sim, expanda com cuidado; se for não, simplifique em vez de publicar mais opções.