Perché serve gestire i permessi
Le app moderne accedono a risorse sensibili: fotocamera, microfono, posizione, contatti, archiviazione. Sia Android che iOS richiedono che l'utente conceda esplicitamente questi accessi a runtime. Gestire male questo flusso porta a crash, recensioni negative e funzionalità bloccate.
In Flutter lo standard di fatto è il pacchetto permission_handler, che astrae le differenze tra piattaforme con un'API uniforme.
Installazione
Aggiungi la dipendenza:
dependencies:
permission_handler: ^11.3.1
Configurazione Android
Dichiara i permessi nel file android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Da Android 13 (API 33) le notifiche richiedono il permesso runtime POST_NOTIFICATIONS.
Configurazione iOS
Su iOS devi aggiungere le stringhe di motivazione (purpose strings) in ios/Runner/Info.plist. Senza queste l'app verrà rifiutata da App Store:
<key>NSCameraUsageDescription</key>
<string>L'app usa la fotocamera per scattare foto del profilo.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>La posizione serve per mostrarti i contenuti vicini.</string>
Inoltre permission_handler su iOS richiede di abilitare i permessi tramite macro nel Podfile, decommentando le voci pertinenti nella sezione post_install:
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
'PERMISSION_CAMERA=1',
'PERMISSION_LOCATION=1',
]
end
end
end
Richiedere un permesso
L'API base ruota attorno all'enum Permission e alla classe PermissionStatus:
import 'package:permission_handler/permission_handler.dart';
Future<bool> richiediFotocamera() async {
final status = await Permission.camera.request();
return status.isGranted;
}
Il metodo request() mostra il dialog di sistema solo se necessario; se il permesso è già concesso ritorna immediatamente lo stato.
Gli stati possibili
PermissionStatus può assumere diversi valori, e gestirli tutti è la chiave per un flusso robusto:
granted: permesso concesso.denied: negato, ma è ancora possibile richiederlo di nuovo.permanentlyDenied: negato in modo permanente (l'utente ha scelto "Non chiedere più"). Va gestito aprendo le impostazioni.restricted: bloccato a livello di sistema (es. controllo parentale, solo iOS).limited: accesso parziale (es. solo alcune foto, iOS 14+).
Future<void> gestisciPosizione(BuildContext context) async {
final status = await Permission.locationWhenInUse.request();
switch (status) {
case PermissionStatus.granted:
case PermissionStatus.limited:
// Procedi con la funzionalità
break;
case PermissionStatus.denied:
_mostraMessaggio(context, 'Permesso necessario per continuare.');
break;
case PermissionStatus.permanentlyDenied:
_mostraDialogImpostazioni(context);
break;
case PermissionStatus.restricted:
_mostraMessaggio(context, 'Accesso bloccato dal sistema.');
break;
default:
break;
}
}
Aprire le impostazioni dell'app
Quando un permesso è permanentlyDenied l'unica via è invitare l'utente ad abilitarlo manualmente:
void _mostraDialogImpostazioni(BuildContext context) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('Permesso richiesto'),
content: const Text(
'Abilita il permesso dalle impostazioni per usare questa funzione.',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Annulla'),
),
TextButton(
onPressed: () {
openAppSettings();
Navigator.pop(context);
},
child: const Text('Apri impostazioni'),
),
],
),
);
}
La funzione globale openAppSettings() apre la schermata di sistema dell'app.
Controllare lo stato senza richiedere
A volte vuoi solo sapere se un permesso è concesso, senza mostrare il dialog (per esempio all'avvio per decidere cosa visualizzare):
final status = await Permission.notification.status;
if (status.isGranted) {
// Mostra contenuti che richiedono il permesso
}
Richiedere più permessi insieme
È possibile richiedere un gruppo di permessi in un'unica chiamata:
Map<Permission, PermissionStatus> esiti = await [
Permission.camera,
Permission.microphone,
].request();
if (esiti[Permission.camera]!.isGranted &&
esiti[Permission.microphone]!.isGranted) {
// Avvia registrazione video
}
Best practice
- Chiedi al momento giusto: richiedi un permesso solo quando l'utente attiva la funzionalità che lo necessita, non all'avvio. Aumenta il tasso di concessione.
- Spiega il perché: mostra una schermata o un dialog "di pre-permesso" che spiega il motivo prima di lanciare il dialog di sistema.
- Gestisci sempre il rifiuto permanente: non lasciare l'utente in un vicolo cieco, offri il link alle impostazioni.
- Ricontrolla al ritorno in foreground: l'utente può revocare i permessi dalle impostazioni mentre l'app è in background. Usa
WidgetsBindingObserverper riverificare lo stato. - Centralizza la logica: incapsula la gestione dei permessi in un service dedicato, per evitare codice duplicato e facilitare i test.
class PermessiService {
Future<bool> assicuraFotocamera() async {
final status = await Permission.camera.status;
if (status.isGranted) return true;
if (status.isPermanentlyDenied) return false;
return (await Permission.camera.request()).isGranted;
}
}
Conclusione
Una gestione attenta dei permessi non è solo un requisito tecnico, ma un fattore di fiducia per l'utente. Con permission_handler hai un'API coerente tra Android e iOS: il resto dipende dal contesto e dalla chiarezza con cui comunichi le tue richieste. Centralizza la logica, gestisci tutti gli stati e chiedi sempre al momento opportuno.
