-3

I’m a beginner when it comes to coding. I just started using flutter and want to make a morning devotion app for church. I’m currently using the routes method to join pages for month and day but I just realized I have to do the same for every single day As in : January > Day one Day two, three and so on.

Is there a more efficient way to do this?

these are the routes I have so far. The loading screen, home, devotion, months, January etc

'''dart

    void main()=>runApp(MaterialApp(
     initialRoute:  "/",
      routes: {

  "/": (context)=> loading(),
  "/home":(context)=>home(),
  "/Devotion":(context)=>Dpg(),
  "/Months":(context)=>months(),
  "/jan":(context)=>jan(),
  "/feb":(context)=>feb(),
  
  },
));
'''

Right now i want to work for the month of january so there will have to be pages for Day one to 31 and then February all the way to december. Im not so sure i have to continue with

"/JAN Day one":(context)=>jd1(),
   "/JAN Day two":(context)=>jd2(),

then go to

  "/FEB Day one":(context)=>fb1(),

etc.

is there a better way to do this?

1 Answers1

1

Welcome to Stack Overflow! If I understand your question correctly, you are currently defining your routes using the routes parameter similar to this:

MaterialApp(
  routes: {
    '/01-01': (context) {
      return Scaffold(
        appBar: AppBar(
          title: const Text('01 JAN'),
        ),
      );
    },
    '/02-01': (context) {
      return Scaffold(
        appBar: AppBar(
          title: const Text('02 JAN),
        ),
      );
    },
  },
)

Instead of defining each route manually, you could investigate using the new Navigator 2.0 changes to easily read parameters from the URL similar to this:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(App());
}

class App extends StatefulWidget {
  @override
  _AppState createState() => _AppState();
}

class _AppState extends State<App> {
  final _routeInformationParser = AppRouteInformationParser();
  final _routerStateData = RouterStateData();

  late AppRouterDelegate _routerDelegate;

  @override
  Widget build(BuildContext context) {
    return RouterState(
      notifier: _routerStateData,
      child: MaterialApp.router(
        routeInformationParser: _routeInformationParser,
        routerDelegate: _routerDelegate,
      ),
    );
  }

  @override
  void dispose() {
    _routerDelegate.dispose();
    _routerStateData.dispose();
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
    _routerDelegate = AppRouterDelegate(
      routerStateData: _routerStateData,
    );
  }
}

/// https://api.flutter.dev/flutter/widgets/InheritedNotifier-class.html
class RouterState extends InheritedNotifier<RouterStateData> {
  const RouterState({
    Key? key,
    RouterStateData? notifier,
    required Widget child,
  }) : super(
          key: key,
          notifier: notifier,
          child: child,
        );

  static RouterStateData? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<RouterState>()?.notifier;
  }
}

/// https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html
class RouterStateData extends ChangeNotifier {
  /// The devotion day.
  int? _devotionDay;
  
  /// The devotion month.
  int? _devotionMonth;

  /// Gets the devotion day.
  int? get devotionDay => _devotionDay;

  /// Gets the devotion month.
  int? get devotionMonth => _devotionMonth;

  /// Updates the state of the router to a devotion day and a devotion month.
  void setDevotionDayMonth(int? devotionDay, int? devotionMonth) {
    _devotionDay = devotionDay;
    _devotionMonth = devotionMonth;
    notifyListeners();
  }
}

/// A base route path that all route paths can extend from.
abstract class RoutePath {
  const RoutePath();
}

/// The route path of the home.
class HomeRoutePath extends RoutePath {
  const HomeRoutePath();
}

/// The route path of a devotion.
class DevotionRoutePath extends RoutePath {
  /// The day of the devotion.
  final int day;
  
  /// The month of the devotion.
  final int month;

  const DevotionRoutePath({
    required this.day,
    required this.month,
  });
}

/// https://api.flutter.dev/flutter/widgets/RouteInformationParser-class.html
class AppRouteInformationParser extends RouteInformationParser<RoutePath> {
  @override
  Future<RoutePath> parseRouteInformation(
    RouteInformation routeInformation,
  ) async {
    /// Gets the uri of the route, for example "/devotions/01-01".
    final uri = Uri.parse(routeInformation.location!);

    /// Switches on the number of path segments of the uri.
    switch (uri.pathSegments.length) {
      /// Cases on uris that have 2 path segments, for example "/devotions/1-1".
      case 2:
        /// Switches on the value of the first path segment of the uri.
        switch (uri.pathSegments[0]) {
          /// Cases on uris that start with devotions, for example "/devotions/1-1".
          case 'devotions':
            /// Gets the day and month dynamically from the uri.
            final dayMonth = uri.pathSegments[1].split('-');
            
            /// Returns the devotion route path with the day and month from the uri.
            return SynchronousFuture(
              DevotionRoutePath(
                day: int.parse(dayMonth[0]),
                month: int.parse(dayMonth[1]),
              ),
            );
        }
        break;
    }

    /// Returns the default home route path if no other route paths match the uri.
    return SynchronousFuture(HomeRoutePath());
  }

  @override
  RouteInformation? restoreRouteInformation(
    RoutePath configuration,
  ) {
    /// If the current route path is home, then sets the uri to /.
    if (configuration is HomeRoutePath) {
      return RouteInformation(
        location: '/',
      );
    /// If the current route path is devotion, then sets the uri to /devotions/day-month, for example "/devotions/1-1".
    } else if (configuration is DevotionRoutePath) {
      return RouteInformation(
        location: '/devotions/${configuration.day}-${configuration.month}',
      );
    }

    return null;
  }
}

/// https://api.flutter.dev/flutter/widgets/RouterDelegate-class.html
class AppRouterDelegate extends RouterDelegate<RoutePath>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<RoutePath> {
  @override
  final navigatorKey = GlobalKey<NavigatorState>();

  final RouterStateData routerStateData;

  AppRouterDelegate({
    required this.routerStateData,
  }) {
    routerStateData.addListener(notifyListeners);
  }

  @override
  RoutePath? get currentConfiguration {
    final day = routerStateData.devotionDay;
    final month = routerStateData.devotionMonth;

    /// If both the day and the month are not null, then returns the route path for devotion; otherwise, returns the route path for home.
    return day != null && month != null
        ? DevotionRoutePath(day: day, month: month)
        : HomeRoutePath();
  }

  @override
  Widget build(BuildContext context) {
    final day = routerStateData.devotionDay;
    final month = routerStateData.devotionMonth;

    return Navigator(
      key: navigatorKey,
      pages: [
        /// Pushes the home page onto the navigator stack.
        const MaterialPage<void>(
          child: HomePage(),
          key: ValueKey('home_page'),
        ),
        /// If both the day and the month are not null, then pushes the devotion page onto the navigator stack.
        if (day != null && month != null)
          MaterialPage<void>(
            child: DevotionPage(
              day: day,
              month: month,
            ),
            key: ValueKey('devotion_page'),
          ),
      ],
      onPopPage: (route, result) {
        if (!route.didPop(result)) {
          return false;
        }

        /// If the devotion page is being popped, then clears the devotion day and devotion month from the router state.
        routerStateData.setDevotionDayMonth(null, null);

        return true;
      },
    );
  }

  @override
  void dispose() {
    routerStateData.removeListener(notifyListeners);
    super.dispose();
  }

  @override
  Future<void> setNewRoutePath(RoutePath configuration) async {
    /// If the route path is home, then clears the devotion day and devotion month from the router state.
    if (configuration is HomeRoutePath) {
      routerStateData.setDevotionDayMonth(
        null,
        null,
      );
    /// If the route path is devotion, then sets the devotion day and devotion month in the router state.
    } else if (configuration is DevotionRoutePath) {
      routerStateData.setDevotionDayMonth(
        configuration.day,
        configuration.month,
      );
    }
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final _dayController = TextEditingController();
  final _monthController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home'),
      ),
      body: ListView(
        children: [
          TextFormField(
            controller: _dayController,
            decoration: InputDecoration(
              labelText: 'Day',
              hintText: '01',
            ),
          ),
          TextFormField(
            controller: _monthController,
            decoration: InputDecoration(
              labelText: 'Month',
              hintText: '01',
            ),
          ),
          ElevatedButton(
            onPressed: () {
              /// Updates the router state with the entered devotion day and devotion month. This calls the `notifyListeners()` internally, which notifies the `AppRouterDelegate` that the route needs updating.
              RouterState.of(context)?.setDevotionDayMonth(
                int.parse(_dayController.text),
                int.parse(_monthController.text),
              );
            },
            child: Text('GO TO DEVOTION'),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _dayController.dispose();
    _monthController.dispose();
    super.dispose();
  }
}

class DevotionPage extends StatelessWidget {
  final int day;
  final int month;

  const DevotionPage({
    Key? key,
    required this.day,
    required this.month,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Devotion'),
      ),
      body: Center(
        child: Text('$day-$month'),
      ),
    );
  }
}

Please note this is by no means the only solution and others can be found here and here.

If you have any questions, then please do let me know!

tnc1997
  • 1,832
  • 19
  • 20
  • Hi. Thanks so much for replying. To be honest I don’t get this so much. You mentioned URL but doesn’t that have to do with websites ? – Othniel Twumasi Dec 13 '20 at 01:17
  • Also, I’ve been doing some digging and it seems I need more help understanding some concepts – Othniel Twumasi Dec 13 '20 at 01:18
  • @OthnielTwumasi you're welcome! You could use these solutions with both mobile apps and web apps, thinking of each page as having a URL. If you always know which day and month that you are navigating to beforehand, then you could pass arguments to a named route as per the documentation [here](https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments). If you need to be able to retrieve the day and month dynamically from the URL, then you could use the solution [here](https://stackoverflow.com/questions/57552885/how-can-a-named-route-have-url-parameters-in-flutter-web). – tnc1997 Dec 13 '20 at 07:15