Back Press Handling è un meccanismo di intercettazione e elaborazione del pulsante Indietro di sistema in Android, che determina quale azione viene eseguita quando viene premuto. Secondo Android Developers (2024), a partire da Android 11 il metodo tradizionale onBackPressed() è stato sostituito da OnBackPressedDispatcher. La nuova API consente ai componenti a qualsiasi livello della gerarchia di intercettare la pressione, non solo all'Activity. Differenza chiave — supporto di multipli callback in una catena con priorità.
Punti chiave
Back Press Handling è un meccanismo di sistema in Android che determina cosa succede quando l'utente preme il pulsante Indietro hardware o software. A seconda del contesto, la pressione può: chiudere lo schermo corrente e tornare a quello precedente, nascondere la tastiera, chiudere un Drawer o Bottom Sheet, uscire dall'app se l'utente si trova sullo schermo principale.
Il comportamento del pulsante Indietro si è evoluto con ogni versione di Android. Android 10 ha introdotto la navigazione gestuale, Android 11 ha presentato OnBackPressedDispatcher come API standard, e Android 13 ha portato il supporto migliorato per predictive back gesture, dove il sistema mostra un'animazione di transizione prima che l'azione venga effettivamente eseguita. Google si muove costantemente verso un comportamento prevedibile e coerente del Back Press su tutti i dispositivi.
La corretta gestione del pulsante Indietro è un elemento UX critico in un'app Android. L'utente si aspetta che premendo Indietro si ritorni allo schermo precedente nello stack di navigazione, non che l'app si chiuda inaspettatamente. Violare questa aspettativa è una delle principali cause di recensioni negative e valutazioni basse su Google Play.
La storia dell'API Back Press in Android riflette l'evoluzione generale della piattaforma: da un semplice metodo in Activity a un sistema flessibile di callback con supporto del ciclo di vita e Compose. Esaminiamo tre fasi di sviluppo.
Fin dalla prima API Android, il pulsante Indietro veniva gestito nel metodo onBackPressed della classe Activity. Lo sviluppatore sovrascriveva questo metodo e scriveva la propria logica. Il problema era che Fragment e View non potevano intercettare la pressione — tutto il controllo passava attraverso l'Activity. Questo portava ad Activity gonfiate e a complesse catene if-else per determinare chi dovesse gestire la pressione.
Con Activity 1.0.0 (AndroidX), Google ha introdotto OnBackPressedDispatcher. Questo è un dispatcher centrale che accetta callback da qualsiasi componente — Activity, Fragment, Dialog, View personalizzata. I callback vengono registrati con un ordine (tramite priority) e possono essere aggiunti o rimossi dinamicamente. OnBackPressedDispatcher viene invocato prima del vecchio onBackPressed, permettendo di intercettare la pressione prima che l'Activity la gestisca.
Android 13 ha introdotto il predictive back gesture — un'animazione di sistema che mostra dove porterà la pressione di Indietro prima che l'utente completi il gesto. Per supportare questa animazione, gli sviluppatori devono utilizzare OnBackPressedDispatcher e indicare se il callback supporta l'animazione di sistema tramite la proprietà isEnabled. Se il callback non supporta l'animazione predittiva, il sistema mostra un'animazione predefinita che potrebbe non corrispondere al contesto dell'app.
| API | SDK minimo | Supporto Fragment | Predictive Back |
|---|---|---|---|
| onBackPressed | API Level 1 | Tramite Activity | No |
| OnBackPressedDispatcher | Activity 1.0.0 | Diretto | Parziale |
| OnBackPressedDispatcher + lifecycle | Activity 1.3.0 | Lifecycle-aware | Completo |
OnBackPressedDispatcher è il cuore della nuova API Back Press. Gestisce una catena di callback, invocandoli in ordine fino a quando il primo gestisce l'evento. Se nessun callback gestisce la pressione, il dispatcher esegue l'azione predefinita — chiamare finish() per un'Activity o popBackStack() per il Navigation Component.
Un callback viene registrato tramite addCallback con un LifecycleOwner e un oggetto OnBackPressedCallback. Il callback ha una proprietà isEnabled — se impostata su false, il callback viene saltato. Per la priorità, è possibile passare un valore da 0 (più bassa) a Integer.MAX_VALUE. L'API Fragment Activity Result utilizza questo meccanismo per la registrazione automatica di callback legati al ciclo di vita.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callback = object : OnBackPressedCallback(enabled = true) {
override fun handleOnBackPressed() {
if (isDrawerOpen) { closeDrawer() }
else { isEnabled = false; onBackPressed() }
}
}
onBackPressedDispatcher.addCallback(this, callback)
}
I callback vengono rimossi automaticamente quando il LifecycleOwner passa allo stato DESTROYED. Questo risolve il vecchio problema di perdite di callback durante la rotazione dello schermo. Se un callback viene aggiunto in un Fragment, è garantito che venga rimosso quando il Fragment viene distrutto. Per disabilitare temporaneamente un callback, utilizzare la proprietà isEnabled — è possibile commutarla senza rimozione e ri-registrazione.
L'ordine di chiamata è l'inverso dell'ordine di aggiunta: l'ultimo callback aggiunto riceve il controllo per primo. Questo è logico perché l'elemento UI più annidato (ad esempio, un Bottom Sheet all'interno di un Fragment) dovrebbe gestire la pressione prima del suo Fragment padre. Se il callback più profondo non gestisce la pressione (isEnabled = false), il controllo passa al successivo nella catena.
L'API Fragment fornisce la propria integrazione con OnBackPressedDispatcher tramite il metodo requireActivity().onBackPressedDispatcher. A partire da Fragment 1.2.0, ogni Fragment può registrare il proprio callback, che viene automaticamente legato al ciclo di vita del Fragment e rimosso quando viene distrutto.
La registrazione di un callback in un Fragment viene effettuata in onCreate, onViewCreated o anche nella View stessa — l'importante è che il LifecycleOwner (Fragment) sia attivo. Quando il Fragment passa allo stato STARTED, il callback viene abilitato; quando è STOPPED, viene disabilitato. Questo garantisce che un Fragment nascosto (in ViewPager) non gestirà la pressione di Indietro.
class EditorFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
showDiscardDialog()
}
}
requireActivity().onBackPressedDispatcher.addCallback(this, callback)
}
}
Dialoghi e BottomSheet intercettano automaticamente la pressione di Indietro per chiudersi. Se è necessario eseguire un'azione aggiuntiva prima della chiusura — registrare un callback con priorità più alta. Importante: se si imposta setCancelable(false) su un Dialog, il callback non si attiverà — questo è comportamento di sistema.
Jetpack Compose fornisce un'API dichiarativa per gestire il pulsante Indietro tramite la funzione composable BackHandler. BackHandler accetta enabled (stato booleano) e onBack — un callback invocato alla pressione. Se enabled = false, la pressione viene passata più avanti nella catena.
BackHandler registra automaticamente un OnBackPressedCallback nell'OnBackPressedDispatcher dell'Activity padre. Rispetta il ciclo di vita del composable: quando si esce dalla composizione, il callback viene rimosso. enabled può essere legato allo stato — ad esempio, mostrare un dialogo di conferma solo se il modulo contiene modifiche non salvate.
@Composable
fun EditScreen(hasUnsavedChanges: Boolean) {
BackHandler(enabled = hasUnsavedChanges) {
// Show confirmation dialog
}
Column {
TextField(value = ..., onValueChange = ...)
}
}
Il predictive back gesture in Compose è supportato a partire da Compose 1.5.0. BackHandler gestisce automaticamente l'animazione di transizione di sistema se abilitata sul dispositivo. Per animazioni predittive personalizzate, utilizzare il modificatore predictiveBackHandler, che restituisce il progresso del gesto da 0 a 1.
Back Press Handling sembra semplice, ma nella pratica gli sviluppatori commettono una serie di errori sistematici. Esaminiamo i problemi più comuni e le loro soluzioni basate sulle raccomandazioni di Google e sull'esperienza della comunità.
La chiamata diretta di finish() in handleOnBackPressed può portare a un'uscita imprevista dall'app se ci sono schermi in background nello stack di navigazione. Controllare sempre NavController.backStack tramite Navigation Component o Coordinator prima di chiudere l'Activity.
Se si registra un callback senza LifecycleOwner (usando il vecchio addCallback senza parametro), il callback vivrà per sempre e potrebbe causare una NullPointerException se l'Activity è già stata distrutta. Utilizzare sempre addCallback(this, callback) con un LifecycleOwner.
Per finestre modali (Bottom Sheet, Dialog), impostare sempre isEnabled = true solo quando la finestra è visibile. Utilizzare addCallback con una lambda che controlla lo stato della finestra. Il Navigation Component gestisce automaticamente questo per NavHost.
Una rapida doppia pressione di Indietro può portare a una doppia chiamata di finish(). Utilizzare un flag o throttleLast per proteggersi da chiamate ripetute entro 500 ms. Il Navigation Component gestisce questa situazione nativamente, ma in scenari personalizzati è necessario implementare la protezione manualmente.
private var lastBackPressTime = 0L
override fun handleOnBackPressed() {
val currentTime = System.currentTimeMillis()
if (currentTime - lastBackPressTime > 500) {
lastBackPressTime = currentTime
navigateBack()
}
}
Domande frequenti
onBackPressed è stato deprecato in Android 11 perché funziona solo a livello di Activity. OnBackPressedDispatcher consente a qualsiasi componente (Fragment, Dialog, View) di intercettare la pressione tramite un meccanismo unico con supporto del ciclo di vita.
Il sistema Android converte un gesto di scorrimento dal bordo in una pressione di Indietro di sistema prima che l'applicazione lo riceva. A livello di OnBackPressedDispatcher, non è possibile distinguere questi due eventi — entrambi arrivano come handleOnBackPressed.
Sì, la gestione di OnBackPressedDispatcher è la stessa per dispositivi a tre pulsanti e con navigazione gestuale. Il codice di intercettazione non dipende dal tipo di navigazione — il sistema stesso converte il gesto in una chiamata al dispatcher.
Abilitare il predictive back nelle Developer Options di un emulatore Android 13+. Utilizzare ADB: `adb shell settings put global enable_back_animation 1`. Dopo l'abilitazione, l'animazione di sistema mostrerà un'anteprima della transizione alla pressione di Indietro.
Controllare due condizioni: il LifecycleOwner deve essere nello stato STARTED o RESUMED, e isEnabled del callback deve essere true. Se entrambe le condizioni sono soddisfatte, assicurarsi che il callback sia stato aggiunto al corretto OnBackPressedDispatcher — utilizzare requireActivity().onBackPressedDispatcher in un Fragment.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche