I don't know if this is possible but how to change a value in another function ?
for exemple in this function, the function receive the value but it can' return it because it return the widget "Switch":
import 'package:flutter/material.dart';
class CustumSwitch extends StatefulWidget {
bool _switchGD;
CustumSwitch(this._switchGD);
#override
_CustumSwitchState createState() => _CustumSwitchState();
}
class _CustumSwitchState extends State<CustumSwitch> {
#override
Widget build(BuildContext context) {
return Switch(
value: widget._switchGD,
onChanged: (value) {
setState(() {
widget._switchGD = !widget._switchGD;
});
});
}
}
You have to use setState() method in every case where you want to dynamically change values during build.
onChanged: (value) {
setState((){
widget._switchGD = !widget._switchGD;
});
});
It will work.. But I recommend you to learn something about Flutter best practices, because you never want to use non-final fields as Widget parameters.
I would like to pass a function through the constructor of a class. but when i call it, nothing happening. debug write: func : {_Closure}
import 'package:flutter/material.dart';
void main() {
runApp(Myclass(func: myfunction()));
}
class Myclass extends StatelessWidget {
final Function func;
Myclass({#required this.func});
#override
Widget build(BuildContext context) {
/* some code here */
this.func(); // <----- don't call myfunction() ! :(
}
}
myfunction()
{
/* some code here */
}
Thank you for the help
You don't passing your function in method, just its result. Check this line:
runApp(Myclass(func: myfunction()));
Using brackets you tell your program "do myfunction, return the result and put it in args". just use myfunction without brackets like that:
runApp(Myclass(func: myfunction));
Try this.
class CsCommonButtonWidget extends StatefulWidget {
const CsCommonButtonWidget(
{required this.onPressed,
required this.titleText,
this.titleTextAlign = TextAlign.center});
final Function? onPressed;
final String titleText;
final TextAlign titleTextAlign;
#override
_CsCommonButtonWidgetState createState() => _CsCommonButtonWidgetState();
}
class _CsCommonButtonWidgetState extends State<CsCommonButtonWidget> {
#override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.red, padding: const EdgeInsets.all(CsDimens.SPACE16)),
child: Text(widget.titleText,style: TextStyle(fontSize: CsDimens.SPACE14),),
onPressed: () {
widget.onPressed!();
},
);
}
}
Usage
CsCommonButtonWidget(
onPressed: () {
print("Login clicked");
},
titleText: "Login"),
Please, consider the below code.
A textController is created during initState. If a button is pressed, another textController is created, inside of setState:
import 'package:flutter/material.dart';
void main() { runApp(Test()); }
class Test extends StatefulWidget {
TestState createState() => TestState();
}
class TestState extends State<Test> {
TextEditingController textController;
void initState() {
print("initState");
super.initState();
textController = TextEditingController(text: "1st textController");
}
void dispose() {
print("dispose");
textController.dispose();
super.dispose();
}
void onPressed() {
print("onPressed");
setState(() {
print("setState");
// It breaks if this line is uncommented.
if (textController != null) textController.dispose();
textController = TextEditingController(text: "2nd textController");
});
}
Widget build(BuildContext context) {
print("build");
var button = MaterialButton(onPressed: onPressed, child: const Text("Click Me"));
var textField = TextField(keyboardType: TextInputType.number, controller: textController);
return MaterialApp(
home: Material(
child: Padding(
padding: const EdgeInsets.all(30.0),
child: Column(children: [button, textField]),
),
),
);
}
}
It works. However, I've never disposed of the old textController. I can do that inside of setState, before creating the new textController:
setState(() {
print("setState");
if (textController != null) textController.dispose();
textController = TextEditingController(text: "2nd textController");
});
However, then, I get an error:
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞══
I/flutter ( 4645): The following assertion was thrown building
InputDecorator(decoration: InputDecoration(), isFocused:
I/flutter ( 4645): false, isEmpty: false, state:
_InputDecoratorState#8195a(tickers: tracking 2 tickers)):
I/flutter ( 4645): A TextEditingController was used after being disposed.
I/flutter ( 4645): Once you have called dispose() on a TextEditingController, it can no longer be used.
My questions:
1) Why am I getting this error? Is the textControlled still being used? Where?
2) How to fix this?
I've got a couple of observations for you that might help, plus, I've prepared a code sample.
First, there is no need to recreate the TextEditingController, as usually there's going to be one for each TextField, or TextFormField (depending on the implementation). You could also declare it as final without the need for the use of initState().
Second, remember to dispose of the TextEditingController inside dispose() when it is no longer needed. This will ensure we discard any resources used by the object. There is no need to dispose of it while in use. Remember: the controller is not there to notify listeners of the changes inside the text input field.
import 'package:flutter/material.dart';
// Main method
void main() {
runApp(App());
}
// I've made a separate App class that returns MaterialApp
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomeScreen(),
);
}
}
// HomeScreen replaces your Test
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
// TextEditingController could have been declared final here
// as well, for example:
//
// final _controller = TextEditingController(text: 'default');
//
// but take that as a suggestion for future ;)
TextEditingController _controller;
// String we'll be changing
String _mutableTextString = '';
#override
void initState() {
super.initState();
// Simple declarations
_controller = TextEditingController(text: 'default');
_mutableTextString = _controller.text;
}
#override
void dispose() {
// Call the dispose() method of the TextEditingController
// here, and remember to do it before the super call, as
// per official documentation:
// https://api.flutter.dev/flutter/widgets/TextEditingController-class.html
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
minimum: const EdgeInsets.all(30.0),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _controller,
keyboardType: TextInputType.text,
),
MaterialButton(
child: Text('CLICK ME'),
onPressed: _handleOnPressed,
),
Text(_mutableTextString),
],
),
),
),
);
}
// This method handles actions when the button is pressed
// and updates the UI
void _handleOnPressed() {
setState(() {
_mutableTextString = _controller.text;
});
}
}
I've got a short demo for you as well, hopefully, it helps!
Why am I getting this error? Is the textControlled still being
used? Where?
From what I've seen in your implementation, you are creating the TextController in a setState call. It should be created once in initState and reusing it during the lifetime of your stateful widget. Here is the part where the error occurs:
void onPressed() {
print("onPressed");
setState(() {
print("setState");
// It breaks if this line is uncommented.
if (textController != null) textController.dispose();
textController = TextEditingController(text: "2nd textController");
});
}
Since the textController has a String value of "1st textController" that was set here:
void initState() {
print("initState");
super.initState();
textController = TextEditingController(text: "1st textController");
}
Every time you call the onPressed(), this line:
if (textController != null) textController.dispose();
checks the value of textController. Since it is not null, the textController was disposed by the disposed() method. There is no way that this next line will run successfully:
textController = TextEditingController(text: "2nd textController");
How to fix this?
Currently, you have implemented the dispose() inside onPressed() method. If you'll check closely the documentation for dispose() method, it states that:
Called when this object is removed from the tree permanently.
The framework calls this method when this
State
object will never build again. After the framework calls
dispose,
the State
object is considered unmounted and the
mounted
property is false. It is an error to call
setState
at this point. This stage of the lifecycle is terminal: there is no
way to remount a State object that has been disposed.
Re using your code, what I did is implement the dispose() in the dispose() method as described in the documentation:
void dispose() {
print("dispose");
// This is the line that breaks when called inside setState() of onPressed() method
if (textController != null) textController.dispose();
super.dispose();
}
So your code will look like this way:
import 'package:flutter/material.dart';
void main() {
runApp(Test());
}
class Test extends StatefulWidget {
TestState createState() => TestState();
}
class TestState extends State<Test> {
TextEditingController textController;
void initState() {
print("initState");
super.initState();
textController = TextEditingController(text: "1st textController");
}
void onPressed() {
print("onPressed");
setState(() {
print("setState");
print(textController);
// It breaks if this line is uncommented.
// if (textController != null) textController.dispose();
textController = TextEditingController(text: "2nd textController");
});
}
void dispose() {
print("dispose");
// This is the line that breaks when called inside setState() of onPressed() method
if (textController != null) textController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
print("build");
var button =
MaterialButton(onPressed: onPressed, child: const Text("Click Me"));
var textField = TextField(
keyboardType: TextInputType.number, controller: textController);
return MaterialApp(
home: Material(
child: Padding(
padding: const EdgeInsets.all(30.0),
child: Column(children: [button, textField]),
),
),
);
}
}
And it's working like this:
To answer why
Here is what's happening:
You dispose the TextEditingController. It's OK by itself.
The app is rebuilt.
The TextField sees its controller changed.
It stops listening to the old controller by calling removeListener on it.
The controller checks if it is disposed and breaks:
So the rule is to never dispose controllers until they stop being listened to. And this can only happen after everything is rebuilt with new controllers.
To answer how to fix
You say that
In the real code I need to substitute a subclass of TextEditingController with another different subclass.
I see the following options:
Ideally, TextEditingController (as any ValueNotifier) should go hand-in-hand with its widget. So you may have your own stateful widget instead of that TextField, create its controller there and dispose it there. When you need to replace the controller, replace the whole widget. This way, Flutter framework takes care of disposal.
Make your own super-ValueNotifier that aggregates your different subclasses, exposes different TextEditingController instances in different states of your screen, and disposes them all in its dispose(). Create it once in your initState(), dispose it once in dispose().
Keep a list of old controllers and dispose them all in your state's dispose() method as the last resort. But it makes it all too messy.
TextEditingController is something that maintains the value, allows to change it and to know that it is changed. That's it. If you want to replace it while the screen is still shown, it means you are treating it as something else which can bring problems.
I have a TabBarView in my main.dart and every tab got a class to show the content(it's listview object), when i go between the tabs, the listview page refresh everytime, is it normal for tabbarview? I don't expect it will refresh everytime when i go between the tabs.
is it the problem my class? how to fix this? the code is something like this.
class ListWidget extends StatefulWidget {
final catID;
ListWidget(this.catID);
_ListWidgetState createState() => new _ListWidgetState(catID);
}
class _ListWidgetState extends State<ListWidget> {
var catID;
void initState() {
super.initState();
_fetchListData();
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(.......
}
MahMoos is right, however it's good to have an example here ...
Use AutomaticKeepAliveClientMixin
override wantKeepAlive property and return true
`
class ListWidget extends StatefulWidget {
#override
_ListWidgetState createState() => _ListWidgetState();
}
class _ListWidgetState extends State<ListWidget> with
AutomaticKeepAliveClientMixin<ListWidget>{ // ** here
#override
Widget build(BuildContext context) {
super.build(context)
return Container();
}
#override
bool get wantKeepAlive => true; // ** and here
}
If I understood you well, you are complaining about the refreshing because you need the views to save their states after moving between tabs. There is an open issue on the subject and there is a way around this problem mentioned in the comments.
Update:
There is a workaround for this issue by using AutomaticKeepAliveClientMixin which you can learn more about in this article.
If you want that your Tab view data does not refresh when you change Tab you should use
AutomaticKeepAliveClientMixin
class BaseScreen extends StatefulWidget {
BaseScreen(this.title, this.listener, {Key key}) : super(key: key);
}
class BaseScreenState extends State<BaseScreen> with AutomaticKeepAliveClientMixin {
#override
Widget build(BuildContext context) {
screenWidth = MediaQuery.of(context).size.width;
screenHeight = MediaQuery.of(context).size.height;
primaryColor = Theme.of(context).primaryColor;
textTheme = Theme.of(context).textTheme;
return Scaffold(
key: scaffoldKey,
appBar: getAppBar(),
body: Container(),
);
}
#override
bool get wantKeepAlive => true;
}
I was facing the same issue and this tutorial helped me.
Happy Coding.
I'm trying to use the findOn from within the class that I want to search. Is this possible or is there a better way?
class CmsSettings extends ActiveRecord
{
public static function tableName()
{
return 'cms_settings';
}
//not working
public static function run(){
$results = CmsSettings::findOn(1):
return $results;
}
// not working
public static function check(){
$results = CmsSettings::findOn(1):
if($results->somesetting){
return true;
}
}
}
You should probably use static::findOne(1). By using self or CmsSettings you are just hardcoding returned type, which makes this class less flexible and will give you unexpected results on inheritance. For example if you create child model which extends your class:
class CmsSettings extends ActiveRecord {
public static function run() {
$results = CmsSettings::findOne(1);
return $results;
}
// ...
}
class ChildCmsSettings extends CmsSettings {
}
You expect that ChildCmsSettings::run() will return instance of ChildCmsSettings. Wrong - you will get CmsSettings. But if you write this method with using static:
public static function run() {
$results = static::findOne(1);
return $results;
}
You will get instance of class which you're used for call run() - ChildCmsSettings.
Use self
Refer findOne()
class CmsSettings extends ActiveRecord
{
public static function tableName()
{
return 'cms_settings';
}
public static function run()
{
$results = self::findOne(1);
return $results;
}
public static function check()
{
$results = self::findOne(1);
if ($results->somesetting) {
return true;
}
}
}