Documentation Index
Fetch the complete documentation index at: https://mintlify.com/ihfaz297/MND/llms.txt
Use this file to discover all available pages before exploring further.
The MND mobile app uses the Provider pattern for state management, providing reactive updates across the application.
Provider Package
The app uses the provider package for state management:
dependencies:
provider: ^6.1.1
Source: pubspec.yaml:39
ChangeNotifier Pattern
Providers extend ChangeNotifier to notify listeners of state changes.
Basic Structure
class MyProvider extends ChangeNotifier {
// Private state
int _counter = 0;
// Public getter
int get counter => _counter;
// Method that updates state
void increment() {
_counter++;
notifyListeners(); // Notify all listeners
}
}
AuthProvider
The AuthProvider manages authentication state.
Implementation
class AuthProvider extends ChangeNotifier {
final AuthService _authService = AuthService();
bool _isLoading = false;
String? _error;
bool get isLoading => _isLoading;
bool get isLoggedIn => _authService.isLoggedIn;
User? get user => _authService.currentUser;
String? get error => _error;
AuthService get authService => _authService;
Future<void> init() async {
await _authService.init();
notifyListeners();
}
}
Source: lib/providers/auth_provider.dart:5-20
State Properties
isLoading: Indicates async operation in progress
isLoggedIn: User authentication status
user: Current user data (null if not logged in)
error: Error message from last operation
authService: Access to underlying AuthService
Sending Magic Link
Future<bool> sendMagicLink(String email) async {
_isLoading = true;
_error = null;
notifyListeners();
try {
await _authService.sendMagicLink(email);
_isLoading = false;
notifyListeners();
return true;
} catch (e) {
_error = e.toString();
_isLoading = false;
notifyListeners();
return false;
}
}
Source: lib/providers/auth_provider.dart:22-38
Verifying Token
Future<bool> verifyToken(String token) async {
_isLoading = true;
_error = null;
notifyListeners();
try {
await _authService.verifyMagicLink(token);
_isLoading = false;
notifyListeners();
return true;
} catch (e) {
_error = e.toString();
_isLoading = false;
notifyListeners();
return false;
}
}
Source: lib/providers/auth_provider.dart:40-56
Logout
Future<void> logout() async {
await _authService.logout();
notifyListeners();
}
Source: lib/providers/auth_provider.dart:58-61
Clear Error
void clearError() {
_error = null;
notifyListeners();
}
Source: lib/providers/auth_provider.dart:63-66
Provider Setup
Providers are configured at the app root in main.dart.
Initializing Providers
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await dotenv.load(fileName: ".env");
// Initialize AuthProvider
final authProvider = AuthProvider();
await authProvider.init();
// Run app with MultiProvider
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider.value(value: authProvider),
// Add more providers here as needed
],
child: const MyApp(),
),
);
}
Source: lib/main.dart:12-37
MultiProvider
Use MultiProvider to provide multiple providers:
MultiProvider(
providers: [
ChangeNotifierProvider.value(value: authProvider),
ChangeNotifierProvider(create: (_) => RouteProvider()),
ChangeNotifierProvider(create: (_) => FavoriteProvider()),
],
child: const MyApp(),
)
Consuming Providers
Widgets consume provider state using Provider.of or Consumer.
Provider.of
Access provider in build method or event handlers:
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final auth = Provider.of<AuthProvider>(context);
return Text(
auth.isLoggedIn ? 'Logged in' : 'Not logged in',
);
}
}
Provider.of with listen: false
For one-time access without rebuilding:
void _addFavorite() {
final auth = Provider.of<AuthProvider>(context, listen: false);
if (!auth.isLoggedIn) {
// Navigate to login
}
}
Source: lib/screens/home/home_screen.dart:86
Rebuild only specific parts of the UI:
Consumer<AuthProvider>(
builder: (context, auth, child) {
return Text(
auth.user?.name ?? 'Guest',
);
},
)
Selector for Granular Updates
Listen to specific properties:
Selector<AuthProvider, bool>(
selector: (context, auth) => auth.isLoggedIn,
builder: (context, isLoggedIn, child) {
return Text(isLoggedIn ? 'Logged in' : 'Not logged in');
},
)
State Update Pattern
Always follow this pattern when updating state:
Future<void> performAction() async {
// 1. Set loading state
_isLoading = true;
_error = null;
notifyListeners();
try {
// 2. Perform async operation
final result = await someAsyncOperation();
// 3. Update state with result
_data = result;
_isLoading = false;
notifyListeners();
} catch (e) {
// 4. Handle errors
_error = e.toString();
_isLoading = false;
notifyListeners();
}
}
Example: Creating a RouteProvider
class RouteProvider extends ChangeNotifier {
final RouteService _routeService = RouteService();
List<RouteOption> _routes = [];
List<Node> _nodes = [];
bool _isLoading = false;
String? _error;
List<RouteOption> get routes => _routes;
List<Node> get nodes => _nodes;
bool get isLoading => _isLoading;
String? get error => _error;
Future<void> loadNodes() async {
_isLoading = true;
_error = null;
notifyListeners();
try {
_nodes = await _routeService.getNodes();
_isLoading = false;
notifyListeners();
} catch (e) {
_error = e.toString();
_isLoading = false;
notifyListeners();
}
}
Future<void> planRoute({
required String from,
required String to,
required String time,
}) async {
_isLoading = true;
_error = null;
_routes = [];
notifyListeners();
try {
_routes = await _routeService.planRoute(
from: from,
to: to,
time: time,
);
_isLoading = false;
notifyListeners();
} catch (e) {
_error = e.toString();
_isLoading = false;
notifyListeners();
}
}
void clearRoutes() {
_routes = [];
notifyListeners();
}
}
Using the RouteProvider
// In main.dart
MultiProvider(
providers: [
ChangeNotifierProvider.value(value: authProvider),
ChangeNotifierProvider(create: (_) => RouteProvider()),
],
child: const MyApp(),
)
// In a widget
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<RouteProvider>(
builder: (context, routeProvider, child) {
if (routeProvider.isLoading) {
return CircularProgressIndicator();
}
return ListView.builder(
itemCount: routeProvider.routes.length,
itemBuilder: (context, index) {
return RouteCard(route: routeProvider.routes[index]);
},
);
},
);
}
void searchRoutes(BuildContext context) {
final routeProvider = Provider.of<RouteProvider>(context, listen: false);
routeProvider.planRoute(
from: 'CAMPUS',
to: 'AMBARKHANA',
time: '08:30',
);
}
}
Best Practices
1. Always call notifyListeners()
After any state change:
void updateValue(int newValue) {
_value = newValue;
notifyListeners(); // Don't forget this!
}
2. Use listen: false for actions
Prevent unnecessary rebuilds:
void _handleButtonPress() {
final provider = Provider.of<MyProvider>(context, listen: false);
provider.performAction();
}
3. Initialize async state
Load initial data in initState or provider:
@override
void initState() {
super.initState();
// Load data after build
WidgetsBinding.instance.addPostFrameCallback((_) {
Provider.of<MyProvider>(context, listen: false).loadData();
});
}
4. Handle loading and error states
Always provide feedback:
if (provider.isLoading) {
return CircularProgressIndicator();
}
if (provider.error != null) {
return ErrorWidget(message: provider.error!);
}
return DataWidget(data: provider.data);
5. Dispose resources
Clean up in provider if needed:
class MyProvider extends ChangeNotifier {
StreamSubscription? _subscription;
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
}
Use Provider when:
- State needs to be shared across multiple screens
- State should persist across navigation
- Multiple widgets need to react to the same state
- Complex state logic needs centralization
- State is local to a single widget
- State doesn’t need to be shared
- Simple UI state (e.g., dropdown selection)
- Form field values
Next Steps