Flutter web – Set state based on url

Issue

I am developing a flutter website with a persistent navigation bar on top that displays the current page the user is on, by adding a border around the title of the active page as shown below.

Navigation Bar

I am using url navigation on the website, so the user may be able to access different pages using the specific url:

  • webpage.com/#/about : Opens the ‘About Me’ page
  • webpage.com/#/skills : Opens the ‘Skills’ page and so on

Since the users can access a website page directly, I need to set the state of the navigation bar upon loading the page based on the url. But I am unable to call setState during build and I get the following error:

Another exception was thrown: setState() or markNeedsBuild() called during build.

I am using Navigators and Routes to take of the url navigation as follows:

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: Strings.appName,
      theme: ThemeData(
        primarySwatch: Colors.cyan,
      ),
      builder: (context, child) => Dashboard(child: child!),
      navigatorKey: locator<NavigationService>().navigatorKey,
      onGenerateRoute: generateRoute,
      initialRoute: Routes.aboutRoute,
    );
  }
}

I am using Provider to control the state of the navigation bar as follows:

class NavigationBarProvider extends ChangeNotifier {
  List<bool> isSelected = [false, false, false, false];
  List<bool> isHovering = [false, false, false, false];

  void setSelected(int _index) {
    isSelected = List.filled(4, false);

    isSelected[_index] = true;

    notifyListeners();
  }

  void setHovering(int _index, bool _val) {
    isHovering[_index] = _val;

    notifyListeners();
  }

  bool isSelHov(int _index) {
    return isSelected[_index] || isHovering[_index];
  }
}

This is my widget for the Navigation Bar buttons:

return InkWell(
      onTap: () {
        locator<NavigationService>().navigateTo(routeName);

        provider.setSelected(index);
      },
      onHover: (value) {
        provider.setHovering(index, value);
      },
      child: SizedBox(
        width: 100.0,
        height: 50.0,
        child: Stack(
          children: [
            Center(
              child: AnimatedContainer(
                duration: const Duration(milliseconds: 500),
                curve: Curves.easeInCirc,
                width: provider.isSelHov(index) ? 100.0 : 0.0,
                height: provider.isSelHov(index) ? 50.0 : 0.0,
                decoration: BoxDecoration(
                  border: Border.all(
                    color: Colors.white,
                    width: provider.isSelHov(index) ? 0.0 : 10.0,
                  ),
                  borderRadius: BorderRadius.circular(provider.isSelHov(index) ? 10.0 : 50.0),
                ),
              ),
            ),
            Center(
              child: Text(
                name,
                style: const TextStyle(
                  fontSize: 16.0,
                  color: Colors.white,
                ),
              ),
            ),
          ],
        ),
      ),
    );

Please help me understand how I may be able to go about this to set the state, and show the active Navigation Button when the website is launched using a specific url.

I appreciate any little help. Thank you very much.

EDIT

This is my code for the Navigation Bar, with the indexes:

ChangeNotifierProvider<NavigationBarProvider>(
            create: (context) => NavigationBarProvider(),
            child: Consumer<NavigationBarProvider>(
              builder: (context, provider, child) {
                return Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  crossAxisAlignment: CrossAxisAlignment.end,
                  children: [
                    NavBarButton(
                      index: 0,
                      name: 'About Me',
                      routeName: Routes.aboutRoute,
                      provider: provider,
                    ),
                    NavBarButton(
                      index: 1,
                      name: 'Projects',
                      routeName: Routes.projectsRoute,
                      provider: provider,
                    ),
                    NavBarButton(
                      index: 2,
                      name: 'Skills',
                      routeName: Routes.skillsRoute,
                      provider: provider,
                    ),
                    NavBarButton(
                      index: 3,
                      name: 'Contact Me',
                      routeName: Routes.contactsRoute,
                      provider: provider,
                    ),
                  ],
                );
              },
            ),
          )

EDIT 2

I managed to find a solution myself, and I have provided it below as one of the answers. Thank you.

Solution

I have managed to find a solution. I implemented Provider across the whole app, and changed the selected values when generating Routes as well. The following is my working code:

My runApp:

runApp(
    ChangeNotifierProvider<NavBarProvider>(
      create: (context) => NavBarProvider(),
      child: const MyApp(),
    ),
  );

I am using a custom RouteRouteBuilder as follows:

_FadeRoute(
      {required this.index, required this.child, required this.routeName})
      : super(
          settings: RouteSettings(name: routeName),
          pageBuilder: (
            BuildContext context,
            Animation<double> animation,
            Animation<double> secondaryAnimation,
          ) {
            Future.delayed(Duration.zero, () async {
              Provider.of<NavBarProvider>(context, listen: false).setSelected(index);
            });
            return child;
          },
          transitionsBuilder: (
            BuildContext context,
            Animation<double> animation,
            Animation<double> secondaryAnimation,
            Widget child,
          ) {
            return FadeTransition(
              opacity: animation,
              child: child,
            );
          },
        );

The rest of the code is the same, except I removed ChangeNotifierProvider as the parent in the NavBar Widget, and just left Consumer as follows:

Consumer<NavigationBarProvider>(
          builder: (context, provider, child) {
            return Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              crossAxisAlignment: CrossAxisAlignment.end,
              children: [
                NavBarButton(
                  index: 0,
                  name: 'About Me',
                  routeName: Routes.aboutRoute,
                  provider: provider,
                ),
                NavBarButton(
                  index: 1,
                  name: 'Projects',
                  routeName: Routes.projectsRoute,
                  provider: provider,
                ),
                NavBarButton(
                  index: 2,
                  name: 'Skills',
                  routeName: Routes.skillsRoute,
                  provider: provider,
                ),
                NavBarButton(
                  index: 3,
                  name: 'Contact Me',
                  routeName: Routes.contactsRoute,
                  provider: provider,
                ),
              ],
            );
          },
        )

Answered By – Abinandhan Selvaraj

Answer Checked By – David Goodson (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *