Flutter - PageController " ScrollController not attached to any scroll views." - flutter

Is there a way to animate to a page using PageController after awaiting an async function without causing the exception below to be thrown?
Exception:
_AssertionError ('package:flutter/src/widgets/scroll_controller.dart': Failed assertion: line 107 pos 12: '_positions.isNotEmpty': ScrollController not attached to any scroll views.)
Example widget:
class ExampleWidget extends StatelessWidget {
ExampleWidget({super.key});
final pageController = PageController();
#override
Widget build(BuildContext context) {
return Stack(
children: [
PageView(
controller: pageController,
physics: const NeverScrollableScrollPhysics(),
children: [],
),
InkWell(
onTap: () async {
await exampleFn();
await Future.delayed(const Duration()); // to wait for UI to build
pageController.animateToPage(
1,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut
);
},
)
],
);
}
}
Awaiting with if (pageController.hasClients) causes the animation to not occur. What is the right way to do this?

I think it's working fine, you have to add the children property in the PageView, You can checkout this example:
import 'package:flutter/material.dart';
class ExampleWidget extends StatelessWidget {
ExampleWidget({super.key});
final pageController = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
PageView(
controller: pageController,
physics: const NeverScrollableScrollPhysics(),
children: [
Container(
color: Colors.amber,
),
Container(
color: Colors.red,
)
],
),
InkWell(
onTap: () async {
// await exampleFn();
await Future.delayed(
const Duration(seconds: 2)); // to wait for UI to build
pageController.animateToPage(1,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut);
},
)
],
),
);
}
}
Here I have delayed for 2 seconds and it works perfectly fine.

Related

Flutter UncontrolledProviderScope error with Riverpod

Here i have a simple animated dialog which with that i can show one screen into that. this screen into animated dialog sending request on appearing into dialog and can be receive data from server without any problem. but when i drag this dialog on top of screen to get full width and height i get this error:
This UncontrolledProviderScope widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
in fact i think this screen send multiple request when appear into dialog and when its shows as a new screen instance
problem is
ref.read(profileProvider.notifier).send(
method: HTTP.POST,
endPoint: Server.$userProfile,
parameters: {
'uuid': '123',
},
);
into MyProfile class
Full source code:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:my_app/core/service/server.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'core/service/repository/request_repository.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
ProviderScope(child: MyApp()),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'test',
home: MyHome(),
);
}
}
class MyHome extends HookConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GestureDetector(
onLongPress: () {
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: "hi",
// barrierColor: Colors.black.withOpacity(0.2),
// transitionDuration: Duration(milliseconds: 500),
pageBuilder: (context, pAnim, sAnim) {
return const SafeArea(
child: FloatingDialog(
previewType: PreviewDialogsType.profile,
));
},
transitionBuilder: (context, pAnim, sAnim, child) {
if (pAnim.status == AnimationStatus.reverse) {
return FadeTransition(
opacity: Tween(begin: 0.0, end: 0.0).animate(pAnim),
child: child,
);
} else {
return FadeTransition(
opacity: pAnim,
child: child,
);
}
},
);
},
child: Container(
width: double.infinity,
height: 50.0,
color: Colors.indigoAccent,
child: const Text('LongPress please'),
),
),
],
),
);
}
}
enum PreviewDialogsType {
profile,
}
class FloatingDialog extends StatefulWidget {
final PreviewDialogsType previewType;
const FloatingDialog({super.key, required this.previewType});
#override
_FloatingDialogState createState() => _FloatingDialogState();
}
class _FloatingDialogState extends State<FloatingDialog> with TickerProviderStateMixin {
late double _dragStartYPosition;
late double _dialogYOffset;
late AnimationController _returnBackController;
late Animation<double> _dialogAnimation;
#override
void initState() {
super.initState();
_dialogYOffset = 0.0;
_returnBackController = AnimationController(vsync: this, duration: const Duration(milliseconds: 1300))
..addListener(() {
setState(() {
_dialogYOffset = _dialogAnimation.value;
});
});
}
#override
void dispose() {
_returnBackController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
Widget myContents = PreviewDialog(
type: widget.previewType,
);
return Padding(
padding: const EdgeInsets.only(
top: 100.0,
bottom: 10.0,
left: 16.0,
right: 16.0,
),
child: Transform.translate(
offset: Offset(0.0, _dialogYOffset),
child: Column(
children: <Widget>[
const Icon(
Icons.keyboard_arrow_up,
color: Colors.white,
size: 30.0,
),
Expanded(
child: GestureDetector(
onVerticalDragStart: (dragStartDetails) {
_dragStartYPosition = dragStartDetails.globalPosition.dy;
},
onVerticalDragUpdate: (dragUpdateDetails) {
setState(() {
_dialogYOffset = (dragUpdateDetails.globalPosition.dy) - _dragStartYPosition;
});
if (_dialogYOffset < -130) {
Navigator.of(context).pop();
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (context, pAnim, sAnim) => myContents,
transitionDuration: const Duration(milliseconds: 500),
transitionsBuilder: (context, pAnim, sAnim, child) {
if (pAnim.status == AnimationStatus.forward) {
return ScaleTransition(
scale: Tween(begin: 0.8, end: 1.0)
.animate(CurvedAnimation(parent: pAnim, curve: Curves.elasticOut)),
child: child,
);
} else {
return child;
}
}),
);
}
},
onVerticalDragEnd: (dragEndDetails) {
_dialogAnimation = Tween(begin: _dialogYOffset, end: 0.0)
.animate(CurvedAnimation(parent: _returnBackController, curve: Curves.elasticOut));
_returnBackController.forward(from: _dialogYOffset);
_returnBackController.forward(from: 0.0);
},
child: myContents,
),
),
],
),
),
);
}
}
class PreviewDialog extends StatefulWidget {
final PreviewDialogsType type;
final String? uuid;
const PreviewDialog({Key? key, required this.type, this.uuid}) : super(key: key);
#override
State<PreviewDialog> createState() => _PreviewDialogState();
}
class _PreviewDialogState extends State<PreviewDialog> {
late ScrollController scrollController;
#override
void initState() {
super.initState();
scrollController = ScrollController();
}
#override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: Scaffold(
body: MyProfile(),
),
),
);
}
}
class MyProfile extends HookConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
useEffect(() {
ref.read(profileProvider.notifier).send(
method: HTTP.POST,
endPoint: Server.$userProfile,
parameters: {
'uuid': '123',
},
);
return () {};
}, []);
return SafeArea(
child: Scaffold(
body: Container(
width: double.infinity,
height:double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ref.watch(profileProvider).when(
idle: () => const SizedBox(),
loading: () => const CircularProgressIndicator(),
success: (success) {
return const Text('successful');
},
error: (error, stackTrace) {
return Text('error');
},
)
],
),
),
),
);
}
}

How to add a dot indicator on a Gridview ? (Flutter)

Is there a way in Flutter to show a dot indicator on Gridview ? or is there a widget like gridview that I can combine it with CarouselSlider ?
Something like this:
Thank you in advance for your answers!
based on The Design picture you provided, Instead of a grid view, you actually need a Carousel, or a page, to implement the pagination.
carousel_builder package or PageView widget is the solution for implementing the pagination.
to achieve the dots indicator, you can use dots_indicator mentioned in the comments.
then you should use your pageview or carousel controller, and on changing the index, update your indicator.
I'll provide the code of an image_slider I implemented, you can edit and achieve what you want:
class ImageSlider extends StatefulWidget {
final List<String> urls;
final bool openImagePopUp;
final PageController sliderController;
const ImageSlider(
{Key key, this.urls, this.openImagePopUp = false, this.sliderController})
: super(key: key);
#override
_ImageSliderState createState() => _ImageSliderState();
}
class _ImageSliderState extends State<ImageSlider> {
int currentSlideIndex;
PageController sliderController;
void setCurrentImageIndex(int newIndex) {
setState(() {
currentSlideIndex = newIndex;
});
}
#override
void initState() {
currentSlideIndex = 0;
sliderController =
widget.sliderController ?? PageController(initialPage: 0);
super.initState();
}
#override
void dispose() {
sliderController.dispose();
super.dispose();
}
void nextPage() {
sliderController.nextPage(
duration: Duration(milliseconds: 250), curve: Curves.easeOut);
}
void previousPage() {
sliderController.previousPage(
duration: Duration(milliseconds: 250), curve: Curves.easeOut);
}
#override
Widget build(BuildContext context) {
return Stack(children: [
Directionality(
textDirection: TextDirection.ltr,
child: PageView.builder(
controller: sliderController,
onPageChanged: (newIndex) {
setCurrentImageIndex(newIndex);
},
itemCount: widget.urls.length,
itemBuilder: (context, index) {
return InkWell(
onTap: widget.openImagePopUp
? () {
context.read<NavigationService>().showPopUp(
ProductDetailsImagePopUp(urls: widget.urls), context);
}
: null,
child: Image.network(
createImageUrl(widget.urls[index]),
fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) {
return LoadImageErrorWidget();
},
),
);
},
),
),
if (widget.urls.length > 1) ...[
Positioned(
left: 0,
bottom: 10,
child: CircleIndicators(
index: currentSlideIndex,
length: widget.urls.length,
),
),
Center(
child: Directionality(
textDirection: TextDirection.rtl,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
if (currentSlideIndex < widget.urls.length - 1)
IconButton(
icon: Icon(Icons.chevron_left), onPressed: nextPage),
Expanded(child: Container()),
if (currentSlideIndex > 0)
IconButton(
icon: Icon(Icons.chevron_right), onPressed: previousPage),
],
),
),
)
]
]);
}
}

Change scrollView offset without callback listener

I want to change scrollView's offset with code so I use ScrollController:
ScrollController _controller;
_controller.addListener(() {
print('call listener');
});
My way to change offset:
_controller.jumpTo(200);
it will callback listener once.
or
_controller.animateTo(200, duration: Duration(milliseconds: 1), curve: Curves.linear);
it will callback listener, too.
I wonder is there any way to change scrollView offset without callback listener.
Here is all my code and you can coppy and test:
import 'package:flutter/material.dart';
class SingleChildScrollViewDemoPage extends StatefulWidget {
SingleChildScrollViewDemoPage({Key key}) : super(key: key);
#override
_SingleChildScrollViewDemoPageState createState() =>
_SingleChildScrollViewDemoPageState();
}
class _SingleChildScrollViewDemoPageState
extends State<SingleChildScrollViewDemoPage> {
ScrollController _controller;
#override
void initState() {
super.initState();
_controller = ScrollController();
_controller.addListener(() {
print('call listener');
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SingleChildScrollView')),
body: SingleChildScrollView(
controller: _controller,
child: Column(
children: [
RaisedButton(
child: Text('change offset'),
onPressed: () {
//_controller.jumpTo(200);
_controller.animateTo(200,
duration: Duration(milliseconds: 1), curve: Curves.linear);
},
),
Container(
width: 375,
height: 200,
color: Colors.red,
),
SizedBox(height: 30),
Container(
width: 375,
height: 3000,
color: Colors.green,
),
],
),
),
);
}
}

Flutter PageView check if page changed using drag gesture or animateToPage()

In a Scaffold page with something like the following structure
#override
Widget build(BuildContext context){
body: PageView(
controller: _controller;
children: <Widget>[Page1(), Page2(), Page3()];
);
bottomNavigationBar: BottomNavBar(
onItemSelected: (index) => _controller.animateToPage()
)
}
there are two ways to go from Page2() to Page1():
Swipe the screen from left to right
Tap the Page1() icon on the bottomNavigationBar, and thus calling _controller.animateToPage(0)
The problem is, how can I tell if the page is changed through swiping gesture or animateToPage() function?
Thanks.
Maybe you can add a flag to set if animateToPage is ongoing or not.
Sample:
import 'package:flutter/material.dart';
void main() => runApp(Root());
class Root extends StatelessWidget {
#override
Widget build(BuildContext context) {
final PageController controller = PageController();
bool isAnimateToPage = false;
controller.addListener(() {
print('LISTENER isAnimateToPage = $isAnimateToPage');
});
return MaterialApp(
home: Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: PageView(
controller: controller,
onPageChanged: (int page) {
print(
'ONPAGECHANGED isAnimateToPage = $isAnimateToPage ; page = $page');
},
children: <Widget>[
const Center(child: Text('page 1')),
const Center(child: Text('page 2')),
const Center(child: Text('page 3')),
],
),
),
FlatButton(
onPressed: () async {
isAnimateToPage = true;
await controller.animateToPage(
controller.page.toInt() + 1,
duration: const Duration(seconds: 1),
curve: Curves.easeIn,
);
isAnimateToPage = false;
},
child: const Text('Next'),
),
],
),
),
);
}
}

Flutter Using Controller from a child widget

I'm fairly new to Flutter and a few days ago I run into carousel_slider package.
One of the examples provided in the doc is a Manually Controlled Slider, but when I change how the widget is build I lose the onPressed ability since the child widget does not know what the _controller is. Which is the proper way to pass info from the state to the child so the child can call _controller.nextPage(), _controller.previousPage() or _controller.animateToPage() ?
class ManuallyControlledSlider extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _ManuallyControlledSliderState();
}
}
class _ManuallyControlledSliderState extends State<ManuallyControlledSlider> {
final CarouselController _controller = CarouselController();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Manually controlled slider')),
body: Column(
children: <Widget>[
CarouselSlider(
items: imageSliders,
options: CarouselOptions(enlargeCenterPage: true, aspectRatio: 16/9),
carouselController: _controller,
),
imageSelector(), //MAIN ISSUE
],
),
);
}
}
The child definition:
class imageSelector extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
child: RaisedButton(
onPressed: () => _controller.previousPage(
duration: Duration(milliseconds: 300), curve: Curves.linear),
child: Text('←'),
),
),
Flexible(
child: RaisedButton(
onPressed: () => _controller.nextPage(
duration: Duration(milliseconds: 300), curve: Curves.linear),
child: Text('→'),
),
),
...Iterable<int>.generate(imgList.length).map(
(int pageIndex) => Flexible(
child: RaisedButton(
onPressed: () => _controller.animateToPage(
pageIndex,
duration: Duration(milliseconds: 300),
curve: Curves.linear),
child: Text("$pageIndex"),
),
),
),
],
);
}
In the imageSelector widget create a variable to access the controller like:
class imageSelector extends StatelessWidget {
final CarouselController _controller;
imageSelector(this._controller);
now pass the controller value as a parameter when you are calling imageSelector like:
imageSelector(_controller)
So your corrected code would be:
For ManuallyControlledSlider:
class ManuallyControlledSlider extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _ManuallyControlledSliderState();
}
}
class _ManuallyControlledSliderState extends State<ManuallyControlledSlider> {
final CarouselController _controller = CarouselController();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Manually controlled slider')),
body: Column(
children: <Widget>[
CarouselSlider(
items: imageSliders,
options: CarouselOptions(enlargeCenterPage: true, aspectRatio: 16/9),
carouselController: _controller,
),
imageSelector(_controller), //Corrected
],
),
);
}
}
And the child:
class imageSelector extends StatelessWidget {
final CarouselController _controller;
imageSelector(this._controller);
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
child: RaisedButton(
onPressed: () => _controller.previousPage(
duration: Duration(milliseconds: 300), curve: Curves.linear),
child: Text('←'),
),
),
Flexible(
child: RaisedButton(
onPressed: () => _controller.nextPage(
duration: Duration(milliseconds: 300), curve: Curves.linear),
child: Text('→'),
),
),
...Iterable<int>.generate(imgList.length).map(
(int pageIndex) => Flexible(
child: RaisedButton(
onPressed: () => _controller.animateToPage(
pageIndex,
duration: Duration(milliseconds: 300),
curve: Curves.linear),
child: Text("$pageIndex"),
),
),
),
],
);
}
You can pass the _controller as an optional parameter to imageSelector.