Skip to main content

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
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

Consumer Widget

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();
  }
}

When to Use Provider vs StatefulWidget

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

Use StatefulWidget when:

  • 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

Build docs developers (and LLMs) love