[{"data":1,"prerenderedAt":22},["ShallowReactive",2],{"articolo-onboarding-e-prime-esecuzioni-in-flutter-gestire-lapp-tour-con-introduction-screen-e-shared-preferences":3,"comments-article-onboarding-e-prime-esecuzioni-in-flutter-gestire-lapp-tour-con-introduction-screen-e-shared-preferences":21},{"id":4,"title":5,"slug":6,"excerpt":7,"body":8,"cover_image":9,"video_url":10,"status":11,"published_at":12,"meta_title":13,"meta_description":14,"category":15,"author":19},43,"Onboarding e prime esecuzioni in Flutter: gestire l'app tour con introduction_screen e shared_preferences","onboarding-e-prime-esecuzioni-in-flutter-gestire-lapp-tour-con-introduction-screen-e-shared-preferences","Impara a creare un'esperienza di onboarding efficace in Flutter: schermate introduttive, gestione della prima esecuzione e feature discovery per guidare i nuovi utenti.","## Perché l'onboarding conta\n\nLe prime interazioni di un utente con la tua app determinano spesso se la userà davvero o la disinstallerà entro pochi minuti. Un buon flusso di onboarding presenta le funzionalità chiave, spiega i permessi necessari e riduce l'abbandono iniziale. In questa guida vedremo come costruire un onboarding completo in Flutter usando `introduction_screen` per le schermate introduttive e `shared_preferences` per ricordare se l'utente ha già completato il tour.\n\n## Le dipendenze\n\nAggiungi i pacchetti al tuo `pubspec.yaml`:\n\n```yaml\ndependencies:\n  introduction_screen: ^3.1.14\n  shared_preferences: ^2.2.3\n```\n\nPoi esegui `flutter pub get`.\n\n## Rilevare la prima esecuzione\n\nIl primo passo è capire se mostrare o meno l'onboarding. Salviamo un flag persistente con `shared_preferences`.\n\n```dart\nimport 'package:shared_preferences\u002Fshared_preferences.dart';\n\nclass OnboardingService {\n  static const _key = 'onboarding_completed';\n\n  Future\u003Cbool> isCompleted() async {\n    final prefs = await SharedPreferences.getInstance();\n    return prefs.getBool(_key) ?? false;\n  }\n\n  Future\u003Cvoid> setCompleted() async {\n    final prefs = await SharedPreferences.getInstance();\n    await prefs.setBool(_key, true);\n  }\n}\n```\n\nAll'avvio dell'app decidiamo quale schermata mostrare:\n\n```dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  final completed = await OnboardingService().isCompleted();\n  runApp(MyApp(showOnboarding: !completed));\n}\n\nclass MyApp extends StatelessWidget {\n  final bool showOnboarding;\n  const MyApp({super.key, required this.showOnboarding});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      home: showOnboarding ? const OnboardingPage() : const HomePage(),\n    );\n  }\n}\n```\n\n## Costruire le schermate introduttive\n\nIl widget `IntroductionScreen` gestisce automaticamente indicatori di pagina, pulsanti di navigazione e skip. Ogni pagina è definita da un `PageViewModel`.\n\n```dart\nimport 'package:flutter\u002Fmaterial.dart';\nimport 'package:introduction_screen\u002Fintroduction_screen.dart';\n\nclass OnboardingPage extends StatelessWidget {\n  const OnboardingPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final pageDecoration = PageDecoration(\n      titleTextStyle: Theme.of(context).textTheme.headlineSmall!,\n      bodyTextStyle: Theme.of(context).textTheme.bodyLarge!,\n      imagePadding: const EdgeInsets.all(24),\n    );\n\n    return IntroductionScreen(\n      globalBackgroundColor: Colors.white,\n      pages: [\n        PageViewModel(\n          title: 'Benvenuto',\n          body: 'Organizza le tue attività quotidiane in modo semplice.',\n          image: const Icon(Icons.task_alt, size: 120),\n          decoration: pageDecoration,\n        ),\n        PageViewModel(\n          title: 'Sincronizzazione',\n          body: 'I tuoi dati sono sempre al sicuro e sincronizzati sul cloud.',\n          image: const Icon(Icons.cloud_sync, size: 120),\n          decoration: pageDecoration,\n        ),\n        PageViewModel(\n          title: 'Iniziamo',\n          body: 'Sei pronto a essere piu produttivo? Cominciamo!',\n          image: const Icon(Icons.rocket_launch, size: 120),\n          decoration: pageDecoration,\n        ),\n      ],\n      onDone: () => _completeOnboarding(context),\n      onSkip: () => _completeOnboarding(context),\n      showSkipButton: true,\n      skip: const Text('Salta'),\n      next: const Icon(Icons.arrow_forward),\n      done: const Text('Fine', style: TextStyle(fontWeight: FontWeight.w600)),\n      dotsDecorator: DotsDecorator(\n        activeColor: Theme.of(context).primaryColor,\n        size: const Size(10, 10),\n        activeSize: const Size(22, 10),\n        activeShape: RoundedRectangleBorder(\n          borderRadius: BorderRadius.circular(25),\n        ),\n      ),\n    );\n  }\n\n  Future\u003Cvoid> _completeOnboarding(BuildContext context) async {\n    await OnboardingService().setCompleted();\n    if (context.mounted) {\n      Navigator.of(context).pushReplacement(\n        MaterialPageRoute(builder: (_) => const HomePage()),\n      );\n    }\n  }\n}\n```\n\nNota l'uso di `context.mounted` dopo l'operazione asincrona: è una best practice per evitare di usare un `BuildContext` non più valido.\n\n## Feature discovery: guidare dentro l'app\n\nL'onboarding non finisce con le schermate iniziali. Spesso serve evidenziare singole funzionalità direttamente nell'interfaccia. Un pattern comune è mostrare dei tooltip contestuali solo la prima volta.\n\n```dart\nclass HighlightTip extends StatefulWidget {\n  final Widget child;\n  final String message;\n  final String prefKey;\n\n  const HighlightTip({\n    super.key,\n    required this.child,\n    required this.message,\n    required this.prefKey,\n  });\n\n  @override\n  State\u003CHighlightTip> createState() => _HighlightTipState();\n}\n\nclass _HighlightTipState extends State\u003CHighlightTip> {\n  final _tooltipKey = GlobalKey\u003CTooltipState>();\n\n  @override\n  void initState() {\n    super.initState();\n    WidgetsBinding.instance.addPostFrameCallback((_) async {\n      final prefs = await SharedPreferences.getInstance();\n      final shown = prefs.getBool(widget.prefKey) ?? false;\n      if (!shown && mounted) {\n        _tooltipKey.currentState?.ensureTooltipVisible();\n        await prefs.setBool(widget.prefKey, true);\n      }\n    });\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Tooltip(\n      key: _tooltipKey,\n      message: widget.message,\n      triggerMode: TooltipTriggerMode.manual,\n      child: widget.child,\n    );\n  }\n}\n```\n\nCon questo widget puoi avvolgere qualsiasi elemento e mostrare un suggerimento contestuale solo alla prima apparizione.\n\n## Best practice per un onboarding efficace\n\n- **Sii breve**: tre o quattro schermate al massimo. Troppe pagine aumentano l'abbandono.\n- **Consenti sempre di saltare**: gli utenti esperti vogliono arrivare subito al contenuto.\n- **Mostra valore, non funzionalità**: comunica i benefici, non l'elenco tecnico delle feature.\n- **Rispetta i temi**: usa `Theme.of(context)` invece di colori hardcoded per supportare dark mode.\n- **Testa il reset**: durante lo sviluppo aggiungi un'opzione nascosta per resettare il flag e rivedere l'onboarding.\n\n## Conclusione\n\nCon `introduction_screen` e `shared_preferences` hai tutti gli strumenti per costruire un onboarding professionale in poche righe di codice. Ricorda che l'onboarding è la prima impressione: investi tempo nel renderlo chiaro, accessibile e coerente con l'identità visiva della tua app. Un buon primo contatto si traduce direttamente in una maggiore retention degli utenti.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002F0a0c9931-9ac9-4ebb-a365-9f703f41fede.jpg",null,"published","2026-07-17T04:00:40+00:00","Onboarding in Flutter con introduction_screen","Guida pratica all'onboarding in Flutter: schermate introduttive con introduction_screen, gestione prima esecuzione e feature discovery.",{"id":16,"name":17,"slug":18},1,"Guide","guide",{"id":16,"name":20},"Flutter Bot",[],1785219600795]