Flutter: Equivalent of Animation<double> for a ScrollController - flutter

With Flutter we can provide an animation with a default value before we have built an AnimationController to act as it's default value.
This is done with AlwaysStoppedAnimation(double). This can also be assigned whenever an animation does not need to listen to it's controller, hypothetically.
How do you achieve similar functionality with a ScrollController. We know that ScrollController can be used as the animation property of an AnimatedBuilder and can be used accordingly. But how do you assign an effective AlwaysStoppedScrollController(double) as that property before a ScrollController has been assigned or after it no longer needs to be listened to.

Flutter allows the absence of solid field declarations so this can be achieved by not providing a scrollController to a class that is still expecting one. This is done by means of a question mark but requires some further logic.
For example
class MyTestWidget extends StatelessWidget {
const MyTestWidget({
this.animator: const AlwaysStoppedAnimation(0),
this.scrollController,
});
final Animation<double> animator;
final ScrollController? scrollController
}
The addition of the question mark after declaring that scrollController must be a ScrollController also allows it to be null and therefore no value to be provided.
Then in the build method of this Widget "MyTestWidget" you would need to address whether scroll controller is null or not.
Something like
ScrollController _scrollController;
if(scrollController != null){
_scrollController = scrollController;
}
It adds a bit more code but technically all the flexibility to do whatever you want both with this and any other widget and type will allow for any issue such as this. You can a allow a null field without declaring it then just have to make sure your build code knows what to do with a null value in that field.

Related

Is there a way to navigate to an specific child or row in flutter?

Is there a way to navigate from one dart "page" to a specific point in another? This will get me to a given page
Navigator.push(
context,
MaterialPageRoute(builder: (context) => WK3()),
);
But I want to navigate to a specific child or row within that page (which are unfortunately fairly long, and would otherwise require a lot of scrolling).
I am used to working with html, where you just have to indicate a position within a page using a hash tag:
#here
That should be possible to do in Flutter/Dart, right?
This is not possible by just using the flutter Navigator. What I would do to tackle that issue is that I would pass an argument which contains the scroll position to the Navigator for example:
Navigator.pushNamed(
context,
'/wk3',
arguments: {'scrollTo': elementId}, // or any other logic like half of the screen or so
);
To read more about Navigator and arguments you can check out the official documentation here. You can also do that for none named routes obviously.
Inside your target widget you could then do the following approach.
Take the argument and parse it to whatever you need.
Depending on your page and your scroll behavior you could use the initState to directly scroll to your desired location. What happens next is a bit dependend on your concrete implementation or where you want to scroll. In certain situations it might be more useful to add a postFrameCallBack for your scrolling instead of doing it in the initState. I'll add it for educational reasons in the snippet below.
Assuming we have a ScrollController of a ListView for example the widget we navigated to knows where we want it to scroll to due to our passed argument. If you use for instance a position value here and we have the ScrollController to do something like this:
controller.position.animateTo(
widget.args.scrollTo, //make sure it has the correct type
duration: const Duration(seconds: 1),
curve: Curves.easeInOut,
);
There are also ways you could scroll to a certain element in a list or a column (like for example the 100th element). Check this question for more information. You can find a slight implentation with a scroll controller below:
class ScreenArguments {
final String scrollTo;
ScreenArguments(this.scrollTo);
}
class Screen extends StatefulWidget {
final ScreenArguments args;
Screen(this.args, {Key key}) : super(key: key);
#override
ScreenState createState() => ScreenState();
}
class ScreenState extends State<Screen> {
#override
void initState() {
scrollMeTo = widget.args.scrollTo;
scrollController = ScrollController();
WidgetsBinding.instance
.addPostFrameCallback((_) => scrollTo(context)); // this is probably safer than doing scrollTo(context) directly in your initState
enter code here
// if you do not use addPostFrameCallback you can call scrollTo(context) directly.
//scrollTo could use scrollControler.animateTo() etc.
}
I dont have ScrollController / ListView implementation
If thats not the case and you do not have a ScrollController and you want just to scroll to any element on your widget things get a little bit more complicated. In that case I'd recommened you to use flutters Scrollable.ensureVisible. Taken from the documentation it does the following:
Scrolls the scrollables that enclose the given context so as to make
the given context visible.
Lets assume you have Column inside a SingleChildScrollView to have a foundation for your scrolling behavior. You would then define a GlobalKey for each section of your widget you would like to scroll to. This key would be the identifier which we pass in as an argument. Assuming we have a GlobalKey in the widget which is called second we could do the following:
Scrollable.ensureVisible(
GlobalObjectKey(widget.args.scrollTo).currentContext, //this would reference second
alignment: 0.5, //
duration: Duration(seconds: 2),
curve: Curves.easeInOut);
You can read more about Scrollable.ensureVisible here.
What approach to take is dependended on your needs and on your implementation.

Changes in Object from Second Screen is also Changing the Value in First Screen in flutter dart

A class Object passing from First Screen to the second Screen While on the second screen when the object changed its value, it's also changing the value on first screen.
Code of First Screen. (widget.templateModel is an object class that i am passing to the second screen)
Navigator.push(context,MaterialPageRoute(builder: (context) =>
EditEmojiTextTemplateScreen(templateModel: widget.templateModel,));
Code of Second Screen (On the second screen i am receiving the object and when i am changing the value of widget.templateModel it also changing the value on the first screen for a simple understandable code below i changed the value in initState while in the gif i am changing value in TextFormField)
class EditEmojiTextTemplateScreen extends StatefulWidget {
final TemplateModel templateModel;
EditEmojiTextTemplateScreen({
Key? key,
required this.templateModel,
}) : super(key: key);
#override
State<EditEmojiTextTemplateScreen> createState() =>
_EditEmojiTextTemplateScreenState();
}
class _EditEmojiTextTemplateScreenState
extends State<EditEmojiTextTemplateScreen> {
final SharedPreferences sharedPreferences = sl();
var txtNameController = TextEditingController();
var txtColorController = TextEditingController();
_EditEmojiTextTemplateScreenState();
#override
void initState() {
widget.templateModel.emoji[0].titleTwo = "kdfff"; //here i am changing the value and it also changing the value on first screen and i dont want this behavior of this object
super.initState();
}
Note: This is happening because of widget variable as mentioned in the documentation but i don't know how to prevent this behavior.
package:flutter/src/widgets/framework.dart
The current configuration.
A [State] object's configuration is the corresponding [StatefulWidget]
instance. This property is initialized by the framework before calling
[initState]. If the parent updates this location in the tree to a new
widget with the same [runtimeType] and [Widget.key] as the current
configuration, the framework will update this property to refer to the
new widget and then call [didUpdateWidget], passing the old
configuration as an argument.
Now I see what you are trying to do.
You could initialize a NEW istance of TemplateModel in the InitState of the second screen.
Then, set the new object's properties like this (or write a cleaner method to do that):
newObject.property1 = oldObject.property1;
newObject.property2 = oldObject.property2;
...
Once the user presses the save button, change oldObject's properties again, so that the first page updates.
You might want to take a look at state management to better understand how to approach this kind of problems.
As the other answer suggests, take a look at state management solutions.
Also keep the models immutable by creating them with final fields. Then to modify, create new instances via copyWith()
Please update you code after navigation.then method
template = snapshot.data;

Flutter - Reuse same custom widget class for instances appear not at same time

In shorter word, my question is how to call child's initState() again on parent's setState().
I need to reuse a widget class for multiple instances. I need the widgets to appear in turn like below where I can use setState() to switch between them.
bool showthefirstone = true;
#override
Widget build(BuildContext context) {
...
TheWidget it = showthefirstone ? TheWidget(...) : TheWidget(..);
...
}
The later one's initState() is not called. No problem showing them both together along at the same time. The issue only occurs upon setState().
For those who don't understand, this trigger the change
setState((){showthefirstone = !showthefirstone})
It's the same answer with different question. My question is more generic. At least works for my case.
https://stackoverflow.com/a/70789848/1297048

Understanding Flutter Render Engine

The docs here about how to update a ListView say:
In Flutter, if you were to update the list of widgets inside a
setState(), you would quickly see that your data did not change
visually. This is because when setState() is called, the Flutter
rendering engine looks at the widget tree to see if anything has
changed. When it gets to your ListView, it performs a == check, and
determines that the two ListViews are the same. Nothing has changed,
so no update is required.
For a simple way to update your ListView, create a new List inside of
setState(), and copy the data from the old list to the new list.
I don't get how the Render Engine determines if there are any changes in the Widget Tree in this case.
AFAICS, we care calling setState, which marks the State object as dirty and asks it to rebuild. Once it rebuilds there will be a new ListView, won't it? So how come the == check says it's the same object?
Also, the new List will be internal to the State object, does the Flutter engine compare all the objects inside the State object? I thought it only compared the Widget tree.
So, basically I don't understand how the Render Engine decides what it's going to update and what's going to ignore, since I can't see how creating a new List sends any information to the Render Engine, as the docs says the Render Engine just looks for a new ListView... And AFAIK a new List won't create a new ListView.
Flutter isn't made only of Widgets.
When you call setState, you mark the Widget as dirty. But this Widget isn't actually what you render on the screen.
Widgets exist to create/mutate RenderObjects; it's these RenderObjects that draw your content on the screen.
The link between RenderObjects and Widgets is done using a new kind of Widget: RenderObjectWidget (such as LeafRenderObjectWidget)
Most widgets provided by Flutter are to some extent a RenderObjectWidget, including ListView.
A typical RenderObjectWidget example would be this:
class MyWidget extends LeafRenderObjectWidget {
final String title;
MyWidget(this.title);
#override
MyRenderObject createRenderObject(BuildContext context) {
return new MyRenderObject()
..title = title;
}
#override
void updateRenderObject(BuildContext context, MyRenderObject renderObject) {
renderObject
..title = title;
}
}
This example uses a widget to create/update a RenderObject. It's not enough to notify the framework that there's something to repaint though.
To make a RenderObject repaint, one must call markNeedsPaint or markNeedsLayout on the desired renderObject.
This is usually done by the RenderObject itself using custom field setter this way:
class MyRenderObject extends RenderBox {
String _title;
String get title => _title;
set title(String value) {
if (value != _title) {
markNeedsLayout();
_title = value;
}
}
}
Notice the if (value != previous).
This check ensures that when a widget rebuilds without changing anything, Flutter doesn't relayout/repaint anything.
It's due to this exact condition that mutating List or Map doesn't make ListView rerender. It basically has the following:
List<Widget> _children;
List<Widget> get children => _children;
set children(List<Widget> value) {
if (value != _children) {
markNeedsLayout();
_children = value;
}
}
But it implies that if you mutate the list instead of creating a new one, the RenderObject will not be marked as needing a relayout/repaint. Therefore there won't be any visual update.

AnimatedWidget and AnimatedBuilder in Flutter

Hi everyone ,I have a problem, I don’t understand the difference between AnimatedWidget and AnimatedBuilder. The comments in the source code are as follows:
AnimatedWidget:
/// For more complex case involving additional state, consider using
/// [AnimatedBuilder].
AnimatedBuilder:
/// For simple cases without additional state, consider using
/// [AnimatedWidget].
I want to know how to choose between them, because I don't quite understand the documentation, thanks!
There's no real difference between them besides the syntax needed to use it.
To be clear, this is the code of AnimatedBuilder :
class AnimatedBuilder extends AnimatedWidget {
const AnimatedBuilder({
Key key,
#required Listenable animation,
#required this.builder,
this.child,
}) : assert(builder != null),
super(key: key, listenable: animation);
final TransitionBuilder builder;
final Widget child;
#override
Widget build(BuildContext context) {
return builder(context, child);
}
}
...Yup, does nothing
From this code we can clearly see that AnimatedBuilder is just a different syntax of using AnimatedWidget. Since AnimatedBuilder is an AnimatedWidget that delegate all the layout logic to a callback
So in the end, it's really up to you. Both do the same thing. Use what makes it more readable for you
Both the animatedWidget and animatedBuilder do the same work related to the animations.
before moving on u must know that to create an animation we must require atleast two things 1. The animation itself and 2. The widget on which we are going to apply the animation.
The clear cut difference between then is :
AnimatedWidget only takes animation as a parameter whereas AnimatedBuilder takes two arguments "child" and "animation".
AnimatedWidget is implemented as a class extending AnimatedWidget.
E.g.
class abc extends AnimatedWidget
Whereas AnimatedBuilder is implemented as a widget inside a class.
E.g.
child : AnimatedBuilder( .....),
Now, although both do the same work but both have different ways of doing it. In AnimatedWidget it has its own child so we have to pass only the animation.whereas in AnimatedBuilder we need to pass both child and animation.
Take AnimatedWidget for example u can use the same AnimatedWidget class for any number of animations with different values.
Now you will be thinking that if AnimatedWidget can do the thing why we need AnimatedBuilder?
The answer is simple
Changing the animation in AnimatedWidget requires changing the widget that renders the logo or our child. So what AnimatedBuilder did is to provided a choice to is to pass both child of your choice and animation explicitly.