Provider state management for PageController in Flutter

Viewed 936

I made a website with PageController to control the scroll of some screens. And I made a menu section with screen names, and when any of them are pressed, the user will navigate to the respective screen.

The app is working fine. But I refactored the menu button so I can control it's style, and to add animation in the future.

But when I refactored the button, I can't pass the PageController index, or the nextPage function.

That's why I thought of using the provider package. However, I didn't the package before, and my setup seems complex. For I should pass the PageController state to the button, and the button should send the nextPage function with a new number.

How can I achieve this?

Here's my code:

The button:

class NavigationButton extends StatefulWidget {

  const NavigationButton({
    Key key,
    this.title,
    // this.onPressed
  }) : super(key: key);

  final String title;
  // final VoidCallback onPressed;

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

class _NavigationButtonState extends State<NavigationButton> {

  bool isHovering = false;

  @override
  Widget build(BuildContext context) {

    return InkWell(
      child: Container(
        decoration: BoxDecoration(
          border: isHovering == true
          ? Border(right: BorderSide(color: Colors.blueGrey, width: 10))
          : Border(right: BorderSide.none)
        ),
        child: Text(
          widget.title,
          style: TextStyle(
            color: Colors.white,
            fontSize: 25
          )
        )
      ),
      onHover: (value) {
        setState(() {
          isHovering = value;
        });
      },
      onTap: () {}
    );
  }
}

The main layout:

class MainLayout extends StatefulWidget {
  @override
  _MainLayoutState createState() => _MainLayoutState();
}

class _MainLayoutState extends State<MainLayout> {

  int _index = 0;
  int selectedButton;
  bool isHovering = false;
  PageController pageController = PageController();

  final List<String> _sectionsName = [
    "Home",
    "About",
    "Services",
    "Portfolio",
    "Testimonials",
    "News",
    "Contact"
  ];


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBodyBehindAppBar: true,
      appBar: MediaQuery.of(context).size.width > 760
      ? null
      : AppBar(
        elevation: 0.0,
        backgroundColor: Colors.transparent
      ),
      drawer: MediaQuery.of(context).size.width < 760 ? DrawerWidget() : null,
      body: Stack(
        children: [
          // MediaQuery.of(context).size.width < 760 ? Container() : DrawerWidget(),
          Listener(
            onPointerSignal: (pointerSignal) {
              if (pointerSignal is PointerScrollEvent) {
                if (pointerSignal.scrollDelta.dy > 0) {
                  if(_index < _sectionsName.length) {
                    // int newIndex = _index + 1;
                    // pageController.jumpToPage(newIndex);
                    pageController.nextPage(
                      curve: Curves.easeIn, duration: Duration(milliseconds: 500)
                    );
                  }
                }
                else
                {
                  if(_index < _sectionsName.length) {
                    // int newIndex = _index - 1;
                    // pageController.jumpToPage(newIndex);
                    pageController.previousPage(
                      curve: Curves.easeIn, duration: Duration(milliseconds: 500)
                    );
                  }
                }
              }
            },
            child: PageView(
              controller: pageController,
              scrollDirection: Axis.vertical,
              physics: NeverScrollableScrollPhysics(),
              children: [
                HeroSection(),
                AboutSection(),
                ServicesSection(),
                PortfolioSection(),
                TestimonialsSection(),
                NewsSection(),
                ContactSection()
              ],
              onPageChanged: (index) {
                _index = index;
              }
            )
          ),
          MediaQuery.of(context).size.width < 760
          ? Container()
          : Align(
            alignment: Alignment.centerRight,
            child: Container(
              height: 205,
              width: 200,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  for (int index = 0; index < _sectionsName.length; index++)
                  NavigationButton(title: _sectionsName[index])
                ]
              )
            )
          )
        ]
      )
    );
  }
}

Thanks in advance...

Edit:

I followed @EdwynZN answer, and it was very helpful. But I had to pass the index from MainLayout to NavigationButton like so:

MainLayout: NavigationButton(title: _sectionsName[index], index: index)

NavigationButton: Added this to the constructor: this.index. And this to the class: final int index;

finally:

onTap: () {
        controller.animateToPage(widget.index, curve: Curves.easeIn, duration: Duration(milliseconds: 500));
      }

I hope this will help someone some day...

1 Answers

Using Provider you could wrap the widget that will need PageController with a ChangeNotifierProvider.value

ChangeNotifierProvider.value(
    value: pageController,
    Align(
        alignment: Alignment.centerRight,
        child: Container(
          height: 205,
          width: 200,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              for (int index = 0; index < _sectionsName.length; index++)
              NavigationButton(title: _sectionsName[index])
            ]
          )
        )
      ),
),

Then all NavigationButton can access pageController as an inheretedWidget

class _NavigationButtonState extends State<NavigationButton> {

  bool isHovering = false;
  PageController controller;
  int index;

  @override
  void didChangeDependencies() {
    controller = Provider.of<PageController>(context); //you can read the pagecontroller here
    index = controller.page.round(); // will update when the page changes so you can do some logic with colors
    super.didChangeDependencies();
  } 

  @override
  Widget build(BuildContext context) {

    return InkWell(
      child: Container(
        decoration: BoxDecoration(
          border: isHovering == true
          ? Border(right: BorderSide(color: Colors.blueGrey, width: 10))
          : Border(right: BorderSide.none)
        ),
        child: Text(
          widget.title,
          style: TextStyle(
            color: Colors.white,
            fontSize: 25
          )
        )
      ),
      onHover: (value) {
        setState(() {
          isHovering = value;
        });
      },
      onTap: () {}
    );
  }
}
Related