Evan R.
Evan R.

Reputation: 1244

scroll controller error with pagecontroller in flutter

I get the following error when I try to invoke _pagecontroller.jumpToPage or _pagecontroller.animateToPage:

Exception has occurred. _AssertionError ('package:flutter/src/widgets/scroll_controller.dart': Failed assertion: line 157 pos 12: '_positions.isNotEmpty': ScrollController not attached to any scroll views.)

it throws the error in this block of code:

void navigateToPage(int index) {
    log(index.toString());
    _pageController.jumpToPage(  <----- here
      index,
      // duration: Duration(milliseconds: 300),
      // curve: Curves.linear,
    );
  }

I'm trying to learn flutter and not really familiar with the terminology, but what I have is the main.dart file that has the following stack for the build widget:

 Widget build(BuildContext context) {
    return PageControllerManager(
      child: Scaffold(
        appBar: AppBar(
          elevation: 8.0,
          backgroundColor: drawerBackgroundColor,
          title: const Text(
            "Photographers Assistant",
          ),
        ),
        body: Stack(
          children: [
            PageView.builder(
                controller: PageControllerProvider.of(context)?.pageController,
                onPageChanged:
                    PageControllerProvider.of(context)?.onPageChanged,
                physics: const NeverScrollableScrollPhysics(),
                // onPageChanged: (int page) {
                //   setState(() {
                //     _activePage = page;
                //   });
                // },
                itemCount: _pages.length,
                itemBuilder: (BuildContext context, int index) {
                  return _pages[index % _pages.length];
                }),
            const CollapsingNavigationDrawer()
          ],
        ),
      ),
    );

I also have a collapsing navigation drawer that has the following on-tap:

                  onTap: () {
                      navigationItems[counter].onTap();
                      setState(() {
                        currentSelectedIndex = counter;
                      });
                      PageControllerProvider.of(context)
                          ?.navigateToPage(counter);

if i comment the following in the navigation_drawer_widget.dart file, the error goes away but of course no page change happens:

 PageControllerProvider.of(context)
    ?.navigateToPage(counter);

When the tap happens it throws that error and the new "page" is not loaded into the body of the main.dart file. I'm not trying to scroll or anything, just trying to load in another page when I click the items in the navigation drawer. What am i doing wrong?

Edit: If I wrap the animateToPage in a hasclients if, the error goes away, meaning i guess it has no clients, but still stuck

void navigateToPage(int index) {
    log(index.toString());
    if (_pageController.hasClients) {
      _pageController.animateToPage(
        index,
        duration: Duration(milliseconds: 300),
        curve: Curves.linear,
      );
    }
  }

Here's my entire page_controller.dart:

import 'dart:developer';

import 'package:flutter/material.dart';

class PageControllerManager extends StatefulWidget {
  final Widget child;
  const PageControllerManager({Key? key, required this.child})
      : super(key: key);
  //get child => null;

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

class _PageControllerManagerState extends State<PageControllerManager> {
  PageController _pageController = PageController(initialPage: 0);
  int _currentPageIndex = 0;

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  void onPageChanged(int index) {
    setState(() {
      _currentPageIndex = index;
    });
  }

  void navigateToPage(int index) {
    log(index.toString());
    if (_pageController.hasClients) {
      _pageController.animateToPage(
        index,
        duration: Duration(milliseconds: 300),
        curve: Curves.linear,
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return PageControllerProvider(
      pageController: _pageController,
      currentPageIndex: _currentPageIndex,
      onPageChanged: onPageChanged,
      navigateToPage: navigateToPage,
      child: widget.child,
    );
  }
}

class PageControllerProvider extends InheritedWidget {
  final PageController pageController;
  final int currentPageIndex;
  final ValueChanged<int> onPageChanged;
  final ValueChanged<int> navigateToPage;
  final Widget child;

  PageControllerProvider({
    Key? key,
    required this.pageController,
    required this.currentPageIndex,
    required this.onPageChanged,
    required this.navigateToPage,
    required this.child,
  }) : super(key: key, child: child);

  static PageControllerProvider? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<PageControllerProvider>();
  }

  @override
  bool updateShouldNotify(PageControllerProvider oldWidget) {
    return pageController != oldWidget.pageController ||
        currentPageIndex != oldWidget.currentPageIndex;
  }
}

And my collapsing_nvagation_drawer.dart:

import 'package:photographers_assistant/model/page_controller.dart';

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

class CollapsingNavigationDrawer extends StatefulWidget {
  //final PageController controller;
  // const CollapsingNavigationDrawer({super.key, required this.controller});
  const CollapsingNavigationDrawer({super.key});

  @override
  CollapsingNavigationDrawerState createState() {
    return CollapsingNavigationDrawerState();
  }
}

class CollapsingNavigationDrawerState extends State<CollapsingNavigationDrawer>
    with SingleTickerProviderStateMixin {
  double maxWidth = 250;
  double minWidth = 70;
  bool isCollapsed = false;
  late AnimationController _animationController;
  late Animation<double> widthAnimation;
  int currentSelectedIndex = 0;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 300));
    widthAnimation = Tween<double>(begin: maxWidth, end: minWidth)
        .animate(_animationController);
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animationController,
      builder: (context, widget) => getWidget(context, widget),
    );
  }

  Widget getWidget(context, widget) {
    final navigateToPage = PageControllerProvider.of(context)?.navigateToPage;

    return Material(
      elevation: 80.0,
      child: Container(
        width: widthAnimation.value,
        color: drawerBackgroundColor,
        child: Column(
          children: <Widget>[
            CollapsingListTile(
              title: 'Techie',
              icon: Icons.person,
              animationController: _animationController,
            ),
            const Divider(
              color: Colors.grey,
              height: 20.0,
            ),
            Expanded(
              child: ListView.separated(
                separatorBuilder: (context, counter) {
                  return const Divider(height: 12.0, color: Colors.transparent);
                },
                itemBuilder: (context, counter) {
                  return CollapsingListTile(
                    // onTap: () {
                    //   setState(() {
                    //     currentSelectedIndex = counter;
                    //     log(currentSelectedIndex.toString());
                    //   });
                    // },
                    isSelected: currentSelectedIndex == counter,
                    onTap: () {
                      navigationItems[counter].onTap();
                      PageControllerProvider.of(context)
                          ?.navigateToPage(counter);
                      setState(() {
                        currentSelectedIndex = counter;
                      });

                      //Navigator.pop(context);
                      //navigateToPage ?? (currentSelectedIndex) {};
                      //isCollapsed = !isCollapsed;
                      //_animationController.forward();
                      (counter);
                      ////Navigator.pushNamed(context, '/models');
                      //Navigator.of(context).pushNamed('/models');
                    },
                    title: navigationItems[counter].title,
                    icon: navigationItems[counter].icon,

                    animationController: _animationController,
                  );
                },
                itemCount: navigationItems.length,
              ),
            ),
            InkWell(
              onTap: () {
                setState(() {
                  isCollapsed = !isCollapsed;
                  isCollapsed
                      ? _animationController.forward()
                      : _animationController.reverse();
                });
              },
              child: AnimatedIcon(
                icon: AnimatedIcons.close_menu,
                progress: _animationController,
                color: selectedColor,
                size: 50.0,
              ),
            ),
            const SizedBox(
              height: 50.0,
            ),
          ],
        ),
      ),
    );
  }
}

and finally, the main.dart:

import 'package:flutter/material.dart';
import 'package:photographers_assistant/custom_navigation_drawer.dart';
import 'package:photographers_assistant/page/home.dart';
import 'package:photographers_assistant/page/models.dart';
import 'package:photographers_assistant/page/photoshoots.dart';
import 'package:photographers_assistant/page/studios.dart';
import 'package:photographers_assistant/model/page_controller.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Custom Navigation Drawer Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // declare ana initialize the page controller
  final PageController _pageController = PageController(initialPage: 1);

  int _activePage = 0;

  final List<Widget> _pages = [Home(), Photoshoots(), Models(), Studios()];

  @override
  Widget build(BuildContext context) {
    return PageControllerManager(
      child: Scaffold(
        appBar: AppBar(
          elevation: 8.0,
          backgroundColor: drawerBackgroundColor,
          title: const Text(
            "Photographers Assistant",
          ),
        ),
        body: Stack(
          children: [
            PageView.builder(
                controller: PageControllerProvider.of(context)?.pageController,
                onPageChanged:
                    PageControllerProvider.of(context)?.onPageChanged,
                physics: const NeverScrollableScrollPhysics(),
                // onPageChanged: (int page) {
                //   setState(() {
                //     _activePage = page;
                //   });
                // },
                itemCount: _pages.length,
                itemBuilder: (BuildContext context, int index) {
                  return _pages[index % _pages.length];
                }),
            const CollapsingNavigationDrawer()
          ],
        ),
      ),
    );
  }
}

Upvotes: 0

Views: 44

Answers (1)

Evan R.
Evan R.

Reputation: 1244

Ok I got it working using a provider, and via Chat GPT.

https://chatgpt.com/share/5d052ce2-06d0-46f4-a1a7-aca033927615

Basically the final code was:

Main.dart:

import 'package:flutter/material.dart';
import 'package:app/custom_navigation_drawer.dart';
import 'package:app/model/page_controller.dart';
import 'package:app/page/home.dart';
import 'package:app/page/apge1.dart';
import 'package:app/page/page2.dart';
import 'package:papp/page/settings.dart';
import 'package:app/page/page3.dart';
import 'package:provider/provider.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return PageControllerWrapper(
        child: MaterialApp(
      title: 'Custom Navigation Drawer Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: MyHomePage(),
    ));
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  //State<MyHomePage> createState() => _MyHomePageState();
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // declare ana initialize the page controller
  //final PageController _pageController = PageController(initialPage: 0);

  //int _activePage = 0;

  final List<Widget> _pages = [
    Home(),
    Page1(),
    Page2(),
    Page3(),
    Settings()
  ];

  @override
  Widget build(BuildContext context) {
    final pageControllerProvider = Provider.of<PageControllerProvider>(context);

    return Scaffold(
      appBar: AppBar(
        elevation: 8.0,
        backgroundColor: drawerBackgroundColor,
        title: const Text(
          "App Title",
        ),
      ),
      body: Stack(
        children: [
          PageView.builder(
              controller: pageControllerProvider.pageController,
              physics: const NeverScrollableScrollPhysics(),
              itemCount: _pages.length,
              itemBuilder: (context, index) {
                return _pages[index % _pages.length];
              }),
          const CollapsingNavigationDrawer()
        ],
      ),
    );
  }
}

The collapsing_navigation_drawer.dart:

import 'package:flutter/material.dart';
import 'package:app/model/page_controller.dart';
import 'package:provider/provider.dart';
import '../custom_navigation_drawer.dart';

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

  @override
  CollapsingNavigationDrawerState createState() {
    return CollapsingNavigationDrawerState();
  }
}

class CollapsingNavigationDrawerState extends State<CollapsingNavigationDrawer>
    with SingleTickerProviderStateMixin {
  double maxWidth = 250;
  double minWidth = 70;
  bool isCollapsed = false;
  late AnimationController _animationController;
  late Animation<double> widthAnimation;
  int currentSelectedIndex = 0;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 300));
    widthAnimation = Tween<double>(begin: maxWidth, end: minWidth)
        .animate(_animationController);
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animationController,
      builder: (context, widget) => getWidget(context, widget),
    );
  }

  Widget getWidget(context, widget) {
    final pageControllerProvider = Provider.of<PageControllerProvider>(context);
    return Material(
      elevation: 80.0,
      child: Container(
        width: widthAnimation.value,
        color: drawerBackgroundColor,
        child: Column(
          children: <Widget>[
            CollapsingListTile(
              title: 'Techie',
              icon: Icons.person,
              animationController: _animationController,
            ),
            const Divider(
              color: Colors.grey,
              height: 20.0,
            ),
            Expanded(
              child: ListView.separated(
                separatorBuilder: (context, counter) {
                  return const Divider(height: 12.0, color: Colors.transparent);
                },
                itemBuilder: (context, counter) {
                  return CollapsingListTile(
                    isSelected: currentSelectedIndex == counter,
                    onTap: () {
                      navigationItems[counter].onTap();
                      pageControllerProvider.pageController.jumpToPage(counter);
                      setState(() {
                        currentSelectedIndex = counter;
                      });
                    },
                    title: navigationItems[counter].title,
                    icon: navigationItems[counter].icon,
                    animationController: _animationController,
                  );
                },
                itemCount: navigationItems.length,
              ),
            ),
            InkWell(
              onTap: () {
                setState(() {
                  isCollapsed = !isCollapsed;
                  isCollapsed
                      ? _animationController.forward()
                      : _animationController.reverse();
                });
              },
              child: AnimatedIcon(
                icon: AnimatedIcons.close_menu,
                progress: _animationController,
                color: selectedColor,
                size: 50.0,
              ),
            ),
            const SizedBox(
              height: 50.0,
            ),
          ],
        ),
      ),
    );
  }
}

and the page_controller.dart:

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

class PageControllerProvider with ChangeNotifier {
  final PageController _pageController = PageController();

  PageController get pageController => _pageController;

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }
}

class PageControllerWrapper extends StatelessWidget {
  final Widget child;

  PageControllerWrapper({required this.child});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => PageControllerProvider(),
      child: child,
    );
  }
}

posting these because it might help someone else, and because it's a way to give back to the community that's helped me, and as someone new to the language, sometimes it's hard to get context for things, so posting the full files might help others.

Thanks!

Upvotes: 0

Related Questions