StatelessWidget and ScrollController - flutter

I have a StatelessWidget in flutter that has a ScrollController , In a child of this component I have StatefulWidget that add scrollController.addListener in its initState
class MyStatelessWidget extends StatelessWidget {
final ScrollController scrollController = ScrollController();
#override
Widget build(BuildContext context) {
return Column(
children: [
Placeholder(), //Complex child
Placeholder(), //Complex child
Placeholder(), //
MyStatefulWidget(
scrollController: scrollController,
) // Complex child
],
);
}
}
class MyStatefulWidget extends StatefulWidget {
final ScrollController scrollController;
const MyStatefulWidget({
#required this.scrollController,
});
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
#override
Widget build(BuildContext context) {
return Container();
}
#override
void initState() {
print('Scroll listener added');
widget.scrollController.addListener(() {
print('Scroll position changed');
});
super.initState();
}
}
My problem is, When build of parent of MyStatelessWidget executed,sometimes I lost scroll listener
Why? and how can I fix it?

_myStatefulWidget is calling build but not initState. Therefore, you are not registering a listener on the new controller.
The solution would be to use a state management solution in order to share the controller between the widgets. Check out the Provider package.

Related

Call setState method in Constructor Error

I want to call method from another class which contains setState. I got some error in page 2like this
This happens when you call setState() on a State object for a widget that hasn't been inserted into the widget tree yet. It is not necessary to call setState() in the constructor, since the state is already assumed to be dirty when it is initially created.
I've read this answer, but I didnt get it on my case. Any Ideas? Thank you
class Page1 extends StatefulWidget {
#override
_Page1State createState() => _Page1State();
}
class _Page1State extends State<Page1> {
#override
void initState(){
Page2().method();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
),
);
}
}
class Page2 extends StatefulWidget {
method() => createState().methodInPage2();
#override
_Page2State createState() => _Page2State();
}
class _Page2State extends State<Page2> {
Future<List<String>> methodInPage2() async{
//error here
setState(){
//setState here,
}
}
#override
Widget build(BuildContext context) => Container();
}

How to dispose the controller for custom widget?

I have created my custom Widget that uses a controller like TextField.
Widget textField({...}) {
TextEditingController controller = TextEditingController();
return TextFormField(controller: controller, ...);
}
The main page uses this widget like this.
import 'package:flutter/material.dart';
class mainPage extends StatefulWidget {
const mainPage({Key? key}) : super(key: key);
#override
State<mainPage> createState() => _mainPageState();
}
class _mainPageState extends State<mainPage> {
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SafeArea(
child: Column(
children: [
textField(),
],
),
),
),
);
}
}
The main page doesn't see the controller from textWidget. How to dispose of this controller?
Or I must create the controller on the main page and send this controller by using a parameter like this.
Widget textField({required TextEditingController controller}) {
return TextFormField(controller: controller, ...);
}
Is it work?
Another way?
you can do that in two ways
Make the textField widget stateful and dispose the controller on onDispose
create the text editing controller in the mainPage and pass it to the textField widget

Builder widget for ChangeNotifier in Flutter

Background
A ValueNotifier has a ValueListenableBuilder widget.
A Stream has a StreamBuilder widget.
A Future has a FutureBuilder widget.
Question
What is the builder for ChangeNotifier?
What I tried
I tried using a ValueListenableBuilder with ChangeNotifier but ChangeNotifier doesn't implement ValueListenable.
I know I could use ChangeNotifierProvider from the Provider package, but I'd like to know if there is a solution that doesn't require a third-party package.
This is a supplemental answer demonstrating using an AnimatedBuilder to rebuild the UI on a change from a ChangeNotifier.
It's just the standard counter app.
counter_model.dart
import 'package:flutter/foundation.dart';
class CounterModel extends ChangeNotifier {
int _counter = 0;
int get count => _counter;
void increment() {
_counter++;
notifyListeners();
}
}
main.dart
import 'counter_model.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _counterModel = CounterModel();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
AnimatedBuilder(
animation: _counterModel,
builder: (context, child) {
return Text(
'${_counterModel.count}',
style: Theme.of(context).textTheme.headline4,
);
}
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _counterModel.increment,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
ChangeNotifier is a direct implementation of the Listenable Widget and for the Listenable, you can use AnimatedBuilder, which triggers rebuilds from a Listenable without passing back a specific value
Also, your class could extend from ChangeNotifier and add new capability to it and you can create a custom Builder widget base on these new functionalities
You can wirte a simple widget by yourself.
use setState as a listener for a ChangeNotifier.
class ChangeNotifierBuilder<T extends ChangeNotifier> extends StatefulWidget {
const ChangeNotifierBuilder({
Key? key,
required this.value,
required this.builder,
}) : super(key: key);
final T value;
final Widget Function(BuildContext context, T value) builder;
#override
_ChangeNotifierBuilderState<T> createState() =>
_ChangeNotifierBuilderState<T>();
}
class _ChangeNotifierBuilderState<T extends ChangeNotifier>
extends State<ChangeNotifierBuilder<T>> {
#override
void initState() {
widget.value.addListener(_listener);
super.initState();
}
#override
void didUpdateWidget(covariant ChangeNotifierBuilder<T> oldWidget) {
if (widget.value != oldWidget.value) {
_miggrate(widget.value, oldWidget.value, _listener);
}
super.didUpdateWidget(oldWidget);
}
#override
void dispose() {
widget.value.removeListener(_listener);
super.dispose();
}
void _miggrate(Listenable a, Listenable b, void Function() listener) {
a.removeListener(listener);
b.addListener(listener);
}
void _listener() {
setState(() {});
}
#override
Widget build(BuildContext context) {
return widget.builder(context, widget.value);
}
}
You can use consumer for Change and the build of your UI!
Try out these - https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple
The builder for ChangeNotifierProvider, ChangeNotifierProvider.value and other providers is a Consumer:
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: Consumer<CounterModel>(
builder: (context, model, child) {
return Text('${model.count}');
}
),
),
As of today, AnimatedBuilder is renamed and update as ListenableBuilder
Hope documents will be updated soon but you can see related issue and examples here https://github.com/flutter/flutter/pull/116543/files

Trigger animations on widget load flutter

So, I have a AnimatedPositioned widget in my widget tree, which contains a form. I want the AnimatedPositioned widget to slide up from bottom when user navigates to the screen. Now, there are many tutorials which show how to do this when user clicks a button using the setState method. But how do I trigger this automatically when this screen is loaded?
The "right" way to do that would be not to use an implicit animation widget like AnimatedPositioned and instead use an explicit animation. Where you start your AnimationController in the initState of a StatefulWidget.
But just technically you could set the "destination" values for your AnimatedPositioned widget in a Future so that the value changes on the next frame. You try remoing the Future to see that otherwise the widget renders at its end position.
This way is not recommended but possible:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
double _dist = 0.0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(children: [
AnimatedPositioned(
child: Text('Hello Words'),
duration: Duration(seconds: 5),
left: _dist,
top: _dist,
),
]),
);
}
#override
initState() {
super.initState();
Future(() {
setState(() {
_dist = 250.0;
});
});
}
}

How to call function in a StatefulWidget from a button somewhere within another widget?

How do I call the movePage(page) function in Widget1 from MaterialButton that placed deeply nested down below within the widget tree?
Please refer to example code below:
class Widget1 extends StatefulWidget {
#override
_Widget1State createState() => _Widget1State();
}
class _Widget1State extends State<Widget1> {
int _selectedIndex = 0;
void movePage(page) {
setState(() {
_selectedIndex = page;
});
}
#override
Widget build(BuildContext context) {
return Container();
}
}
///Somewhere nested down below within another widget in the widget tree
class Widget12 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialButton(onPressed: () => {});
}
}
You could just pass it to the constructor. Try this on DartPad.
class Widget1 extends StatefulWidget {
#override
_Widget1State createState() => _Widget1State();
}
class _Widget1State extends State<Widget1> {
int _selectedIndex = 0;
void movePage(int page) => setState(() => _selectedIndex += page);
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('$_selectedIndex'),
Widget2(func: movePage),
],
);
}
}
class Widget2 extends StatelessWidget {
final void Function(int) func;
const Widget2({Key key, #required this.func}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialButton(
/// Try with any number.
onPressed: () => func(2),
child: Text('button'),
);
}
}
I finally find it working using InheritedWidget.
Reference:
Call method of a widget from another widget
The codes are in his blog:
http://www.hellomonk.com/2018/03/communication-between-widgets-using.html
I will just leave it here for who might need it as well.