Search code examples
flutterflutter-webflutter-provider

Provider state management for PageController in Flutter


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


Solution

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