La gestió de l'estat és un dels conceptes més importants en el desenvolupament d'aplicacions amb Flutter. L'estat es refereix a qualsevol dada que pot canviar durant el cicle de vida de l'aplicació, com ara el text d'un camp de formulari, la selecció d'un element en una llista o la resposta d'una API.
Objectius d'Aquesta Secció
- Entendre què és l'estat en Flutter.
- Conèixer les diferents maneres de gestionar l'estat.
- Aprendre a utilitzar
setState
iInheritedWidget
per a la gestió bàsica de l'estat.
Què és l'Estat?
L'estat és qualsevol dada que pot canviar durant el cicle de vida de l'aplicació. En Flutter, hi ha dos tipus principals de widgets que gestionen l'estat:
- Stateless Widgets: No mantenen cap estat intern. Són immutables i es tornen a construir cada vegada que es criden.
- Stateful Widgets: Mantenen un estat intern que pot canviar durant el cicle de vida del widget. Són mutables i poden actualitzar-se dinàmicament.
Maneres de Gestionar l'Estat
Hi ha diverses maneres de gestionar l'estat en Flutter, cadascuna amb els seus avantatges i inconvenients. Les més comunes són:
- setState: La manera més bàsica de gestionar l'estat en Flutter. S'utilitza per actualitzar l'estat d'un
StatefulWidget
. - InheritedWidget: Permet compartir dades entre widgets sense necessitat de passar-les explícitament a través de constructors.
- Provider: Un paquet popular per a la gestió de l'estat que es basa en
InheritedWidget
. - Riverpod: Una alternativa moderna a
Provider
amb una API més senzilla i funcionalitats avançades. - Bloc (Business Logic Component): Un patró que separa la lògica de negoci de la interfície d'usuari, facilitant la gestió de l'estat en aplicacions complexes.
Utilitzant setState
setState
és la manera més bàsica i directa de gestionar l'estat en Flutter. S'utilitza dins d'un StatefulWidget
per notificar a Flutter que l'estat ha canviat i que el widget s'ha de reconstruir.
Exemple Pràctic
A continuació, es mostra un exemple senzill d'un StatefulWidget
que utilitza setState
per actualitzar un comptador:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: CounterScreen(), ); } } class CounterScreen extends StatefulWidget { @override _CounterScreenState createState() => _CounterScreenState(); } class _CounterScreenState extends State<CounterScreen> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Gestió de l\'Estat amb setState'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'Has premut el botó aquest nombre de vegades:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); } }
Explicació del Codi
- MyApp: És un
StatelessWidget
que defineix l'aplicació principal. - CounterScreen: És un
StatefulWidget
que conté l'estat_counter
. - _CounterScreenState: La classe d'estat que manté el valor del comptador i defineix el mètode
_incrementCounter
per actualitzar l'estat. - setState: Quan es crida
_incrementCounter
,setState
notifica a Flutter que l'estat ha canviat, i el widget es reconstrueix amb el nou valor del comptador.
Utilitzant InheritedWidget
InheritedWidget
és una manera més avançada de compartir dades entre widgets sense necessitat de passar-les explícitament a través de constructors. És útil quan es necessita compartir dades entre molts widgets en diferents nivells de l'arbre de widgets.
Exemple Pràctic
A continuació, es mostra un exemple senzill d'un InheritedWidget
que comparteix un comptador entre diversos widgets:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: CounterProvider( counter: 0, child: CounterScreen(), ), ); } } class CounterProvider extends InheritedWidget { final int counter; CounterProvider({Key? key, required this.counter, required Widget child}) : super(key: key, child: child); @override bool updateShouldNotify(CounterProvider oldWidget) { return oldWidget.counter != counter; } static CounterProvider? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<CounterProvider>(); } } class CounterScreen extends StatelessWidget { @override Widget build(BuildContext context) { final counter = CounterProvider.of(context)?.counter ?? 0; return Scaffold( appBar: AppBar( title: Text('Gestió de l\'Estat amb InheritedWidget'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'Has premut el botó aquest nombre de vegades:', ), Text( '$counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), ); } }
Explicació del Codi
- CounterProvider: És un
InheritedWidget
que conté l'estat del comptador. - updateShouldNotify: Defineix quan s'ha de notificar als widgets dependents que l'estat ha canviat.
- of: Un mètode estàtic que permet als widgets descendents accedir a l'estat del
CounterProvider
. - CounterScreen: Un
StatelessWidget
que accedeix a l'estat del comptador a través delCounterProvider
.
Exercicis Pràctics
Exercici 1: Incrementar i Decrementar el Comptador
Modifica l'exemple de setState
per afegir un botó que decrementi el valor del comptador.
Solució
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: CounterScreen(), ); } } class CounterScreen extends StatefulWidget { @override _CounterScreenState createState() => _CounterScreenState(); } class _CounterScreenState extends State<CounterScreen> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } void _decrementCounter() { setState(() { _counter--; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Gestió de l\'Estat amb setState'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'Has premut el botó aquest nombre de vegades:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), SizedBox(height: 10), FloatingActionButton( onPressed: _decrementCounter, tooltip: 'Decrement', child: Icon(Icons.remove), ), ], ), ); } }
Exercici 2: Compartir Estat amb InheritedWidget
Modifica l'exemple de InheritedWidget
per permetre incrementar el comptador des de diferents widgets.
Solució
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return CounterProvider( counter: _counter, incrementCounter: _incrementCounter, child: MaterialApp( home: CounterScreen(), ), ); } } class CounterProvider extends InheritedWidget { final int counter; final VoidCallback incrementCounter; CounterProvider({ Key? key, required this.counter, required this.incrementCounter, required Widget child, }) : super(key: key, child: child); @override bool updateShouldNotify(CounterProvider oldWidget) { return oldWidget.counter != counter; } static CounterProvider? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<CounterProvider>(); } } class CounterScreen extends StatelessWidget { @override Widget build(BuildContext context) { final counterProvider = CounterProvider.of(context); return Scaffold( appBar: AppBar( title: Text('Gestió de l\'Estat amb InheritedWidget'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'Has premut el botó aquest nombre de vegades:', ), Text( '${counterProvider?.counter ?? 0}', style: Theme.of(context).textTheme.headline4, ), ElevatedButton( onPressed: counterProvider?.incrementCounter, child: Text('Incrementar Comptador'), ), ], ), ), ); } }
Conclusió
En aquesta secció, hem après què és l'estat en Flutter i com gestionar-lo utilitzant setState
i InheritedWidget
. Hem vist exemples pràctics i hem realitzat exercicis per reforçar els conceptes apresos. En la següent secció, explorarem el paquet Provider
per a una gestió de l'estat més avançada i escalable.
Curs de Desenvolupament Flutter
Mòdul 1: Introducció a Flutter
- Què és Flutter?
- Configuració de l'Entorn de Desenvolupament
- Comprensió de l'Arquitectura de Flutter
- Creació de la Teva Primera Aplicació Flutter
Mòdul 2: Conceptes Bàsics de Programació en Dart
- Introducció a Dart
- Variables i Tipus de Dades
- Sentències de Flux de Control
- Funcions i Mètodes
- Programació Orientada a Objectes en Dart
Mòdul 3: Widgets de Flutter
- Introducció als Widgets
- Widgets Stateless vs Stateful
- Widgets Bàsics
- Widgets de Disseny
- Widgets d'Entrada i Formulari
Mòdul 4: Gestió de l'Estat
Mòdul 5: Navegació i Enrutament
- Introducció a la Navegació
- Navegació Bàsica
- Rutes Nomenades
- Passar Dades Entre Pantalles
- Deep Linking
Mòdul 6: Xarxes i APIs
- Obtenir Dades d'Internet
- Analitzar Dades JSON
- Gestió d'Errors de Xarxa
- Ús d'APIs REST
- Integració de GraphQL
Mòdul 7: Persistència i Emmagatzematge
- Introducció a la Persistència
- Preferències Compartides
- Emmagatzematge de Fitxers
- Base de Dades SQLite
- Ús de Hive per a l'Emmagatzematge Local
Mòdul 8: Conceptes Avançats de Flutter
- Animacions en Flutter
- Pintura Personalitzada i Canvas
- Canals de Plataforma
- Isolates i Concurrència
- Optimització del Rendiment
Mòdul 9: Proves i Depuració
- Introducció a les Proves
- Proves Unitàries
- Proves de Widgets
- Proves d'Integració
- Tècniques de Depuració
Mòdul 10: Desplegament i Manteniment
- Preparació per al Llançament
- Construcció per a iOS
- Construcció per a Android
- Integració i Desplegament Continu (CI/CD)
- Manteniment i Actualització de la Teva Aplicació