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
Related
Im using a SideBar() widget inside the SideBarWidget() and using it in my main screen SideBarScreen()
but i cant initialize the controller in the Sidebar() widget... How can i fix this
sidebar_Screen
class _SideBarScreenState extends State<SideBarScreen> {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Stack(
children: [
ScaffoldScreen(
iconButton: IconButton(
onPressed: () {SideBarWidget().toggle();},
icon: kScaffoldScreenButtonIcon,
),
),
SideBarWidget(),
],
),
);
}
}
sideBarWidget
class SideBarWidget extends StatelessWidget {
SideBarWidget({Key? key}) : super(key: key);
void toggle() {
SideBarState().toggle();
}
late final SideBar sideBarWidget = SideBar();
#override
Widget build(BuildContext context) {
return sideBarWidget;
}
}
SideBar
class SideBar extends StatefulWidget {}
class SideBarState extends State<SideBar> with SingleTickerProviderStateMixin{
late AnimationController controller;
late Animation<Offset> _offsetAnimation;
#override
void initState() {
super.initState();
controller = AnimationController(vsync: this, duration: const Duration(seconds: 2));
_offsetAnimation = Tween<Offset>(
begin: const Offset(-1.0, 0.0),
end: const Offset(0.0, 0.0),
).animate(CurvedAnimation(parent: controller, curve: Curves.easeOut));
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
void toggle() {
if (controller.isCompleted) {
controller.reverse();
}
else {controller.forward();}
}
#override
Widget build(BuildContext context) {
return SlideTransition();
}
}
This gives the error LateInitializationError: Field 'controller' has not been initialized.
I tried passing the controller as a parameter to the SideBar() but it gives off an exception:
This widget has been unmounted, so the State no longer has a context (and should be considered defunct).
The problem is that you're creating a new instance of SideBar every time you call SideBarWidget().toggle(). Instead, you should be using the same instance of SideBar so that the state is retained.
class SideBarWidget extends StatelessWidget {
SideBarWidget({Key? key}) : super(key: key);
final sideBar = SideBar();
void toggle() {
sideBar.state.toggle();
}
#override
Widget build(BuildContext context) {
return sideBar;
}
}
So now you are using the same instance of SideBar every time and you don't need to pass any parameters.
Note:
To use like this, you also need to change the SideBar class to a StatefulWidget
class SideBar extends StatefulWidget {
#override
_SideBarState createState() => _SideBarState();
}
In a Flutter desktop for Windows project I have a TextField widget in a statefulWidget with a controller attached to it.
late TextEditingController searchController;
#override
void initState() {
super.initState();
searchController = TextEditingController();
}
#override
void dispose() {
searchController.dispose();
super.dispose();
}
TextField(
keyboardType: TextInputType.text,
controller: searchController,
decoration: defaultTextFieldDecoration.copyWith(hintText: "Type to Search"),
style: textFieldStyle,
onChanged: (value) {},
),
Now when I type something into the textfield like "abc" every key gets input twice like "aabbcc" and I can't figure out why. I have used TextFields many times and that never happended.
It is also not a problem with my keyboard since I can type this without problems :D
Edit: Here is a full example to reproduce this problem.
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(home: Main()));
}
class Main extends StatefulWidget {
const Main({Key? key}) : super(key: key);
#override
_MainState createState() => _MainState();
}
class _MainState extends State<Main> {
#override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: TextFieldTestWidget(),
),
);
}
}
class TextFieldTestWidget extends StatefulWidget {
const TextFieldTestWidget({Key? key}) : super(key: key);
#override
_TextFieldTestWidgetState createState() => _TextFieldTestWidgetState();
}
class _TextFieldTestWidgetState extends State<TextFieldTestWidget> {
TextEditingController controller = TextEditingController();
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizedBox(
width: 300,
height: 100,
child: TextField(
controller: controller,
),
);
}
}
Edit: Added Image
Edit again:
Found that it has something to do with the initial text value... I just dont't get what exactly. When i change the TextEditingController to TextEditingController(text:"") it works somehow. I think instancing TextEditingControllers is somehow broken.
Upgrading Flutter from beta 2.4.0-4.2.pre -> beta 2.5.0-5.1.pre fixed it for now. At least it wasn't an error in my code :D
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
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.
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;
});
});
}
}