Provider state management for PageController in Flutter

624

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: () {}
    );
  }
}
Share:
624
user10033434
Author by

user10033434

Updated on December 22, 2022

Comments

  • user10033434
    user10033434 over 1 year

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

    • EdwynZN
      EdwynZN almost 3 years
      What exactly do you want to accomplish? you want NavigationButton to react when PageController changes and also have an instance of PageController there so you can use nextPage inside NavigationButton ? (using Provider I suppose)
    • user10033434
      user10033434 almost 3 years
      Thanks for your comment and your time... I refactored the NavigationButton to another class so I can style it. The button when clicked, should navigate to another screen (page). And, the button should change color after being clicked.
  • user10033434
    user10033434 almost 3 years
    I really appreciate your help and taking time to answer my question. I tried your code, but this error was thrown: NoSuchMethodError: invalid member on null: 'round'. I tried to solve it on my own, but couldn't.. How can I solve this?
  • user10033434
    user10033434 almost 3 years
    Also, when I tried to put the ChangeNotifierProvider.value up the tree, the provider couldn't find it.. How can I solve this? And where can I study and understand the provider package? All the sources that I found before, didn't quite deliver the subject, and there's always something missing.
  • user10033434
    user10033434 almost 3 years
    And I tried a few things also, I think I should share it with you: I changed the index to double, hashed out the controller.page.round();, then created int newIndex = index.toInt(); and controller.animateToPage(newIndex, curve: Curves.easeIn, duration: Duration(milliseconds: 500)); inside onTap. However, I get this error: NoSuchMethodError: invalid member on null: 'toInt'
  • user10033434
    user10033434 almost 3 years
    Also I tried this inside didChangeDependencies(): index = 0; controller.addListener(() { setState(() { index = controller.page.toInt(); }); });
  • user10033434
    user10033434 almost 3 years
    I solved it at last.. I'll update my question, and I'll mark your answer as accepted.. But please, if you know any good resources to study provider from them, please share them with me. I can't thank you enough...
  • EdwynZN
    EdwynZN almost 3 years
    code with andrea has some really nice videos and courses that I find really helpful codewithandrea.com/tags/provider