Chore — Rimozione provider e codice ChangeNotifier orfano pre-Riverpod
Commit:
chore(deps): drop provider and mockito — remove orphan ChangeNotifier code and refactor test to ProviderScope· Branch:edit/mobile-audit-ux-polish· Audit: 2026-04-20 UX polish (finding L1, esteso)
Cosa non andava
L’audit (L1) aveva marcato provider: ^6.1.2 come “unused dependency”. La verifica ha mostrato una situazione più profonda:
- Il package era usato, ma solo da un test:
test/widget/features/onboarding/profile_setup_screen_test.dartwrappavaProfileSetupScreenin unMultiProvider+ChangeNotifierProvider<AuthProvider>.value. AuthProviderera orfano in produzione: la classeAuthProvider extends ChangeNotifieresisteva inlib/shared/providers/auth_provider.dart(426 righe), ma nessun codice inlib/la istanziava più — la app live usaauthNotifierProvider(RiverpodStateNotifier).RegistrationWizardProviderera orfano in produzione: stesso pattern. La classe esisteva con unasubmit(BuildContext context, AuthProvider authProvider)method, ma il call site inregistration_wizard_screen.dartchiamanotifier.submit(context)di una classe DIVERSA (RegistrationWizardNotifier extends StateNotifier, inlib/features/auth/notifiers/registration_wizard_notifier.dart).- Il test passava comunque: perché
ProfileSetupScreen(che usa Riverpod) ignorava il wrappingMultiProvider; i test esercitavano solo navigation + validation (non auth state), quindi ilMockAuthProviderera cerimonia morta. mockitoera usato solo per mockare la classe orfana: quindi anchemockitoera dead weight.
Decisioni
| Elemento | Status | Decisione |
|---|---|---|
AuthProvider (ChangeNotifier, 426 righe) | Orfano in prod, usato solo da test legacy | Rimosso (lib/shared/providers/auth_provider.dart) |
RegistrationWizardProvider (ChangeNotifier) | Orfano in prod, signature disallineata dal call site | Rimosso (lib/features/auth/providers/registration_wizard_provider.dart) |
auth_provider_test.dart | Testava classe ora eliminata | Rimosso |
profile_setup_screen_test.dart | Testava screen Riverpod-based con wrapping MultiProvider fasullo | Refattorizzato — ora usa ProviderScope diretto |
provider: ^6.1.2 (pubspec) | Nessun consumer rimasto | Rimosso |
mockito: ^5.4.4 (pubspec) | Nessun consumer rimasto | Rimosso |
Classi orfane NON rimosse in questo pass
Durante la verifica sono emersi 6 altri ChangeNotifier in lib/shared/providers/ (AppStateProvider, ChatProvider, EventProvider, NotificationNotifier [shadow-name della versione Riverpod], SocketProvider, TagProvider). Non sono usati in prod (nessun call site esterno ai loro file), ma possono avere relazioni con altri test o codice ancora da verificare.
Out of scope per questo chore. Da gestire in un pass dedicato “purge legacy ChangeNotifier providers” dopo aver verificato che non siano referenziati in test/integrations.
Diff riassuntivo
test/widget/features/onboarding/profile_setup_screen_test.dart
Prima (wrapping artificiale):
import 'package:provider/provider.dart';
import 'package:mockito/mockito.dart';
import 'package:flow_app/shared/providers/auth_provider.dart';
class MockAuthProvider extends Mock implements AuthProvider { /* … */ }
Widget createTestWidget() => MultiProvider(
providers: [ChangeNotifierProvider<AuthProvider>.value(value: mockAuthProvider)],
child: MaterialApp(/* … */, home: ProfileSetupScreen()),
);Dopo (wrapping canonico Riverpod):
import 'package:flutter_riverpod/flutter_riverpod.dart';
Widget createTestWidget() => const ProviderScope(
child: MaterialApp(/* … */, home: ProfileSetupScreen()),
);Non serve override di authNotifierProvider perché i test esercitano solo navigation + validation — non toccano auth state. Se in futuro un test esercita la path updateProfile, aggiungi ProviderScope(overrides: [authNotifierProvider.overrideWith(() => FakeAuthNotifier())]).
pubspec.yaml
- # State Management
- provider: ^6.1.2
+ # State Management (Riverpod is the single source of truth — see CLAUDE.md)
riverpod: ^2.5.1 # Testing
- mockito: ^5.4.4
integration_test:
sdk: flutterRimosso
lib/shared/providers/auth_provider.dartlib/features/auth/providers/registration_wizard_provider.darttest/unit/providers/auth_provider_test.dartlib/features/auth/providers/(cartella vuota, auto-pruned)
Perché il test passava prima nonostante il wrapping sbagliato
Domanda giusta da farsi — “se il MultiProvider era fasullo, perché i test non fallivano?“.
Risposta: ProfileSetupScreen è ConsumerStatefulWidget + usa authNotifierProvider (Riverpod). Non consulta MAI Provider.of<AuthProvider>(context). Quindi il wrapping MultiProvider era pura decorazione — il widget non sapeva che esistesse.
I 4 test esercitavano:
- Step 1 render (Bio, Location, Website)
- Navigation a step 2 (Interests)
- Validation ”< 3 interests”
- Finish con 3 interessi selezionati (assert sull’assenza del msg di validation, NON sul successo della submit)
Nessuna di queste path toccava ref.read(authNotifierProvider). Quindi il mock era letteralmente inerte.
Il nuovo test Riverpod-based produce un log AuthService.updateProfile nello stack quando il Finish button viene tappato (perché Riverpod ora funziona DAVVERO, ma senza un utente reale la call fallisce gracefully). Il test continua a passare perché assert è sull’assenza della validation error, non sulla presenza del success.
Verifica
flutter analyze
# → 1 pre-existing info (squad_detail_sheet.dart context-across-async)
flutter test test/widget/features/onboarding/profile_setup_screen_test.dart
# → All tests passed!
flutter test test/unit/
# → All tests passed!
flutter pub get
# → "These packages are no longer being depended on:
# - mockito 5.4.4
# Changed 2 dependencies!"Lessons
- “Unused dependency” richiede full graph trace. Un
flutter pub depsavrebbe mostratoprovidercome usato (dal test). La vera domanda è: “il codice che lo usa è ancora vivo?“. Nel nostro caso, i test testavano classi fossili — la dep era usata ma il sottostante era morto. - Test che passano senza testare nulla. Il caso qui è un classico “cargo cult testing”: il wrapping
MultiProviderera stato copiato da un template, ma il widget sotto era migrato a Riverpod. I test continuavano a passare perché non esercitavano mai la path mockata. Lesson: se unaverify()su un mock non è asserita, il mock è decorazione. - Migration tail è lunga. Il grosso della migrazione
provider→riverpodera stato fatto mesi fa (la app live è tutta Riverpod). Ma sono rimasti indietro: 2 classi orfane, 1 test mal-refattorizzato, 2 deps. Migration “complete” ≠ codebase pulita. Serve un pass dedicato dopo ogni big migration per spazzare il longtail. - Shadowing names è un red flag. Esistevano DUE classi
NotificationNotifier: unaChangeNotifierorfana inlib/shared/providers/, unaStateNotifierlive inlib/shared/notifiers/. Il compile risolve per import path (zero ambiguità a livello di tipo), ma cognitivamente è un mine-field. Convention flow d’ora in avanti: un solo file può esportare una classe con un dato nome nel progetto. Shadowing = debt. - Rifattorizzare un test prima di eliminare il suo soggetto. Ordine corretto: (1) fai funzionare il test senza la classe da eliminare, (2) verifica che passi, (3) elimina la classe. Ordine sbagliato: elimina la classe, rompe il test, rippiegi sotto time-pressure. Questo pass ha seguito il primo.