Detect if a SingleChildScrollview/Listview can be scrolled - flutter

So, i have a widget with infinite width (Row) . To fill the widget with items i'm using a Lisview builder with Axis horizontal. I also can use a SingleChildScrollview with axis horizontal and a Row as the child.
If there is a few items, the width of the screen is not filled, so that's great. However, when there is a lot of items, the Listview becomes "scrollable" , and i can scroll to the right to reveal the items.
I would like to know if the list is scrollable (if it overflows). The purpose is to show a little text saying "Scroll to reveal more>>".
I know i can use maths and calculate the items width with the screen width. However... The Listview already knows this, so i was wondering if there was a way of getting access to that information

I had a similar problem. I found this solution here: Determine Scroll Widget height
Here's my question in case it's helpful: How do you tell if scrolling is not needed in a SingleChildScrollView when the screen is first built?
Solution:
Import the scheduler Flutter library:
import 'package:flutter/scheduler.dart';
Create a boolean flag inside the state object but outside of the build method to track whether build has been called yet:
bool buildCalledYet = false;
Create a boolean isScrollable variable inside the state object but outside of the build method:
bool isScrollable;
Add the following in the beginning of the build method:
if (!firstBuild) {
firstBuild = true;
SchedulerBinding.instance.addPostFrameCallback((_) {
setState(() {
isScrollable = !(_scrollController.position.maxScrollExtent > 0);
});
});
}
(The purpose of buildCalledYet is to prevent this code from causing build to be called over and over again.)
If isScrollable is true, then show your text.

I created a widget called ScrollCheckerWidget with a controller property to pass the controller of a scrollable widget and check if it is scrollable in the view. The ScrollCheckerWidget widget has a builder callback function where you can return a widget and use the isScrollable boolean from the builder to check if the widget related to the controller is scrollable or not. Here you have the code:
import 'package:flutter/material.dart';
class ScrollCheckerWidget extends StatefulWidget {
const ScrollCheckerWidget({
Key? key,
required this.controller,
required this.builder,
}) : super(key: key);
final ScrollController controller;
final Widget Function(bool isScrollable) builder;
#override
State<EGScrollCheckerWidget> createState() => _EGScrollCheckerWidgetState();
}
class _EGScrollCheckerWidgetState extends State<EGScrollCheckerWidget> {
late final ScrollController _scrollController;
late bool _isScrollable;
#override
void initState() {
_scrollController = widget.controller;
_isScrollable = false;
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_isScrollable = _scrollController.position.maxScrollExtent > 0;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return widget.builder.call(_isScrollable);
}
}

In builder:
return NotificationListener(
onNotification: (scrollNotification) {
print("ScrollStartNotification");
// add your logic here
},
child: SingleChildScrollView(...
Am trying to detect exact same thing myself, wrapping SingleChildScrollView with NotificationListener at least gave me a point of reference to start working on this.

Related

how to set state while opening bottom sheet in flutter?

So I'm having an issue with a bottom sheet that I'm trying to display.
the idea is that i want to display a bottom sheet and remove my bottom navigation bar when the bottom sheet shows. anyway, I've made a boolean called sheetOpen which is set to false initially and the idea is to set it to true in order to close the bottom navigation bar when the sheet pops up .
doing so without using setstate does not reflect any changes to the UI . But if I use set state in the show Bottom sheet function the app crashes and i get this message : 'Looking up deactivated widget's ancestor is unsafe. at this point the state of the widget's element tree is no longer stable . to safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.'
I have tried multiple solutions(Stateful Builder, calling _controller.setstate ..) but nothing works.
Been stuck at this for 3 days..
anyway i will show the code that i have written and i would really appreciate anyone who can help.
class FeedScreen extends StatefulWidget {
static bool sheetOpen = false;
static int selectedIndex = 0;
const FeedScreen();
#override
_FeedScreenState createState() => _FeedScreenState();
}
class _FeedScreenState extends State<FeedScreen> {
late final GlobalKey<ScaffoldState> _key;
late PersistentBottomSheetController _controller;
void _showPreview(
final BuildContext context,
) {
//this is what's causing the issue
setState(() {
FeedScreen.sheetOpen = true;
});
_controller = _key.currentState!.showBottomSheet(
(ctx) {
//etc....
}
#override
void initState() {
super.initState();
_key = GlobalKey<ScaffoldState>();
}
Widget build(BuildContext context) {
return Scaffold(
//....
bottomNavigationBar: FeedScreen.sheetOpen
? null
: BottomNavBar(
FeedScreen.selectedIndex,
_changeTab,
),
),
https://api.flutter.dev/flutter/widgets/StatefulBuilder-class.html?
you can wrap Stateful builder with Bottomsheet to use setState.
wrap it with statefulbuilder
StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return BottomNavBar(
FeedScreen.selectedIndex,
_changeTab,
);}

Will my whole widget tree rebuild when a keyboard appears?

I am trying to build a responsive mobile app so I found an approach were i would divide the sreen into definite number of grids and get the grid width and height and then use this width and height to size my widgets
Question:
I would definitly get my screen's size from MediaQuery.of(context) but since i will only use it once to do my calculations will my widget tree rebuild (assuming i did this calculation in my root widget) whenever a keyboard appears or not? And if it will rebuild should i do the calculations in a different place?
No, if you didn't place any set state or callback during that rebuild the widget when you open the keyboard. However, the issue can be easily resolved by putting your main widget "below" the Scaffold in a SingleChildScrollView to avoid rendering issues.
If you absolutely need to perform actions when the keyboard appears you can use a FocusNode in the textField and add a listener to it with the addListener method. By passing a function to add Listener, you can trigger a setState every time you need, causing the widget to rebuild with the new parameters.
This is a very simplified version of what I mean:
class MyWidget extends StatefulWidget{
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
FocusNode _focusNode;
int state=0;
#override
Widget build(BuildContext context) {
return Container(
height: state==0?100:200, //change the height depending on "state"
child: TextField(
focusNode: _focusNode,
),
);
}
void onFocus(){
setState(() {
//Check if the focus node is focused
if(_focusNode.hasFocus) state=1; //Change the value of the state
});
}
#override
void initState() {
super.initState();
_focusNode=FocusNode();
_focusNode.addListener(onFocus); //Here on focus will be called
}
#override
void dispose() {
super.dispose();
_focusNode.dispose();
}
}

Trying to access state from widget, probably using wrong design

I'm learning flutter and trying to make a kind of MutableImage widget. The idea is to make a MutableImage StatefulWidget that would rebuild when a new image is provided. I try to avoid rebuilding the whole widget tree each time the image is changed because that seems overkill, and I plan to update the image several times per second. So I want to rebuild only that MutableImage widget.
So here is the code I have, with comments to explain where I'm stuck :
class MutableImage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MutableImageState();
}
void updateImage(List<int> bytes) {
// !!!!!! Would like to call this method here, but state is not available from Widget, which means I want to do something wrong, but not sure exactly how I should do it...
// this.state.updateImage(bytes);
}
}
class MutableImageState extends State<MutableImage> {
List<int> _bytes;
void updateImage(List<int> bytes) {
setState(() {
_bytes=bytes;
});
}
#override
Widget build(BuildContext context) {
if ((_bytes==null)||(_bytes.length==0)) {
return Center(child: CircularProgressIndicator());
}
return Image.memory(_bytes);
}
}
Then the idea was to use this widget like this for example in another stateful widget
MutableImage _mutableImage;
#override
Widget build(BuildContext context) {
if (_mutableImage == null) _mutableImage=MutableImage();
return : Row( //using Row as an example, the idea is that the mutable Image is deep into a tree of widgets, and I want to rebuild only _mutableImage when image changes, not all widgets.
children : <Widget>[
child0, child1, _mutableImage, child3, child4
]
);
}
void updateImage(List<int> bytes) {
_mutableImage?.updateImage(bytes);
}
So is there a good way to do this ? I'm quite confused, thx for any help/hint.
This is a place for an application of a GlobalKey. In the parent Widget of MutableImage make a global key and pass that to MutableImage. With that key you can access MutableImage state by using .currentState on the key and calling updateImage.
You'll have to add key as an argument of the MutableImage constructor and call super(key: key). updateImage should also be moved the the state of MutableImage.
Key:
final GlobalKey<MutableImageState> _imageKey = GlobalKey<MutableImageState>();
Pass the key:
MutableImage(key: _imageKey);
Access the state:
_imageKey.currentState.updateImage();

I want to avoid to recreate the AppBar and Drawer when I change the tool that I am using

I am creating an app that has by default the google map satellite view at the center of the application, but I have other tools in my app, so I would like to keep the same AppBar and the same Drawer in all screens, avoiding to create it over and over again. The only thing that I would like to change is the satellite view so that the space occupied by the map would fit another tool's functionalities.
Is there a way to create only once the AppBar and Drawer and fit other tools' screens in the space of the map without needing to recreate the AppBar and Drawer many times?
Thanks!
Within a scaffold, set the body to be an AnimatedSwitcher, and have the child be a variable. Any time you wish to change the satellite view out for another widget, call SetState and change the child variable.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar(), //This will never change
body: DynamicContainer(),
);
}
ChangeWidget() {
myChangingWidget = Container();
}
}
class DynamicContainer extends StatefulWidget {
DynamicContainer({Key key}) : super(key: key);
_DynamicContainerState createState() =>
_DynamicContainerState(controller: controller);
}
class _DynamicContainerState extends State<DynamicContainer> {
Widget _myAnimatedWidget;
#override
void initState() {
super.initState();
_myAnimatedWidget = myListOfWidgets[0]; //The widget it will start as
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedSwitcher(
duration: Duration(milliseconds: 250),
child: _myAnimatedWidget, //Our dynamic widget
);
}
switchWidget() {
setState(() {
_myAnimatedWidget =
myListOfWidgets[1]; //Where you change the widget
});
}
}
This will redraw the body while maintaining your AppBar and Drawer. Check out the official Flutter video on AnimatedSwitcher - https://youtu.be/2W7POjFb88g
**Code Snippet has been updated and should be correct.

TextEditingController vs OnChanged

I am looking for a better explanation on the benefit of TextEditingController over OnChanged event for a TextField.
My understanding is that onChanged's setState notifies all widgets of the change in state variable value. This way any widget (e.g. Text) can simply use the state variable and it will be notified of its changes.
My false hopes were TextEditingController would make it even simpler that I won't even need a state variable. Something like below:
import "package:flutter/material.dart";
class TestForm extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return TestFormState();
}
}
class TestFormState extends State<TestForm> {
//string myStateVariable = "";
final ctrl = TextEditingController();
#override
Widget build(BuildContext context) {
var tf = TextField(
controller: ctrl,
);
var t = Text("Current value: " + ctrl.text); // <<<<<<<<<<< false hope! doesnt work!
var x = Column(children: <Widget>[tf,t],);
return MaterialApp(home: Material(child: Scaffold(
appBar: AppBar(title: Text("Test Form"),),
body: x,
)));
}
}
Can anyone explain why TextEditingController or something similar cannot manage the state itself and notifies all consumers of change in state?
Thanks.
You are just not setting state synchronously that's all. What onChanged does is exactly possible with this approach:
class _TestFormState extends State<TestForm> {
late TextEditingController controller;
#override
void initState() {
controller = TextEditingController()
..addListener(() {
setState(() {});
});
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text('Current Value: ${controller.text}'),
TextField(
controller: controller,
),
],
);
}
}
As you see, we have listener that setting state every time state of the controller changes. This is exactly what onChanged does.
So, about benefits, you can achieve everything with both approach, it's a subjective way.
About benefits:
If you need to hold field values within Stream, onChanged is what you need. In other cases you may use controller.
Actually you won't need both in most of time in my opinion because TextFormField + Form within StatefulWidget is quite complete way to implement form pages. Checkout cookbook: https://flutter.dev/docs/cookbook/forms/validation
TextEditingController actually is managing his own state, that's why you can see the input on the screen once you change it.
You have 2 problems here, the first is that you are not adding any listener to the TextEditingController, you are just asking "give me the current value" only when you build the widget, not "give me the value any time it changes". To achieve this you need to add a listener to the text controller and it will be called every time that the value change.
Try this :
#override
void initState() {
super.initState();
// Start listening to changes.
ctrl.addListener(_printValue);
}
_printValue() {
print("Value: ${ctrl.text}");
}
This will work because print doesn't need to render anything on the screen but if you change it to return a widget it will not work either. That is the second problem, as you pointed out, your parent widget is not been rebuild when the value change, in this case you cannot avoid the setState (or other way to tell flutter that needs to rebuild the widget) when the value change because you need to rebuild the widget to view the change.
Another thing that ill like to point out is that TextEditingController is much powerful and it can be used for more things that just add notifiers to changes. For example if you want a button on other part of the screen that clear the text on a TextField you will need a TextEditingController binded to that field.
Hope it helps!