I form in Flutter sembrano banali.
Finché non devono gestire:
- login reale
- errori backend
- loading asincrono
- persistenza del token
- UX coerente
- separazione tra UI e business logic
La Lezione 8 del corso entra nel dettaglio di come costruire form professionali, scalabili e integrati in un’architettura MVVM con Riverpod.
📺 Guarda la Lezione 8 su YouTube
https://youtu.be/dh0SBic9cWw
📁 Slide ufficiali della lezione
https://drive.google.com/file/d/1xnRVRo8vOVtF_AQXiyM02WcgjFiEOtb8/view?usp=sharing
Il problema dei Form “da tutorial”
Molti esempi online mostrano qualcosa del genere:
TextFormField(
controller: TextEditingController(),
validator: (value) {
if (value == null || value.isEmpty) {
return "Campo obbligatorio";
}
return null;
},
)
Sembra corretto.
Ma in un’app reale questo approccio genera:
- controller creati dentro il widget
- validazione hardcoded
- logica mischiata alla UI
- difficoltà di testing
- codice non riutilizzabile
Il problema non è il TextFormField.
Il problema è la struttura.
Architettura corretta di un Form in Flutter
Nel contesto del corso utilizziamo:
- MVVM
- Riverpod
- ChangeNotifier
- Controller esterni
- Validatori separati
Il principio è semplice:
La UI mostra.
Il ViewModel gestisce stato e logica.
Controller esterni al widget
Nel tuo approccio ogni campo:
- riceve il controller dall’esterno
- non crea istanze inline
- non contiene logica di business
ViewModel
class LoginViewModel extends ChangeNotifier {
final emailController = TextEditingController();
final passwordController = TextEditingController();
bool isLoading = false;
String? errorMessage;
String? validateEmail(String? value) {
if (value == null || value.isEmpty) {
return "Email obbligatoria";
}
if (!value.contains("@")) {
return "Email non valida";
}
return null;
}
}
UI
TextFormField(
controller: viewModel.emailController,
validator: viewModel.validateEmail,
)
Separazione chiara.
Responsabilità chiare.
Validazione scalabile
La validazione deve essere:
- riutilizzabile
- isolata
- testabile
Esempio con classe dedicata:
class Validators {
static String? required(String? value) {
if (value == null || value.isEmpty) {
return "Campo obbligatorio";
}
return null;
}
static String? email(String? value) {
if (value == null || !value.contains("@")) {
return "Email non valida";
}
return null;
}
}
Questo permette:
- riuso su più form
- codice leggibile
- zero duplicazioni
Integrazione con Riverpod
Il ViewModel viene esposto tramite provider:
final loginViewModelProvider =
ChangeNotifierProvider((ref) => LoginViewModel());
Nel widget:
final viewModel = ref.watch(loginViewModelProvider);
Vantaggi:
- rebuild controllati
- lifecycle gestito
- stato centralizzato
Nessun setState sparso.
Gestione di Loading, Error e Success
Un form professionale deve gestire:
- loading
- errore backend
- stato finale
Nel ViewModel:
Future<void> login() async {
isLoading = true;
notifyListeners();
try {
await authService.login(
emailController.text,
passwordController.text,
);
} catch (e) {
errorMessage = "Credenziali non valide";
}
isLoading = false;
notifyListeners();
}
Nella UI:
ElevatedButton(
onPressed: viewModel.isLoading ? null : viewModel.login,
child: viewModel.isLoading
? CircularProgressIndicator()
: Text("Login"),
)
La UI reagisce.
Non decide.
SharedPreferences come Singleton
Questa lezione introduce anche un concetto chiave per la Lezione 9:
persistenza locale tramite SharedPreferences inizializzate una sola volta.
Implementazione
class LocalStorage {
static late SharedPreferences _prefs;
static Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
static void setToken(String token) {
_prefs.setString("token", token);
}
static String? getToken() {
return _prefs.getString("token");
}
}
Nel main():
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await LocalStorage.init();
runApp(MyApp());
}
Questo evita:
- async sparsi nel codice
- inizializzazioni multiple
- dipendenze disordinate
Ed è la base per la gestione token e interceptor nella Lezione 9.
Errori comuni nei Form Flutter
- controller creati dentro il build
- validazione duplicata
- logica API dentro il widget
- stato loading gestito in UI
- SharedPreferences usate con await in ogni schermata
Conclusione
Un form non è solo una schermata.
È:
- stato
- validazione
- UX
- persistenza
- preparazione all’autenticazione
La Lezione 8 prepara il terreno per:
- gestione token
- clean architecture
- app Flutter in produzione
📺 Lezione 8 su YouTube
https://youtu.be/dh0SBic9cWw
📁 Slide ufficiali
https://drive.google.com/file/d/1xnRVRo8vOVtF_AQXiyM02WcgjFiEOtb8/view?usp=sharing
📚 Playlist completa del corso Flutter gratuito
https://www.youtube.com/watch?v=Y0aZvwv3puk&list=PL5DSRxOKWSGLGAtbSLWoe_EsEz29zHSag

