usually I can disable/grey-out a button until a TextFormField meets certain parameters in flutter by something like this:
TextFormField(
controller: _controller
value: (value)
)
SubmitButton(
onPressed: _controller.text.isNotEmpty ? _submit : null;
)
But when compiled as a website the Button seems no longer aware of the controller value...
I have tried targeting in several different ways, e.g. _controller.value.text.isEmpty and _controller.text.isEmpty...
I'm guessing I'm missing something or this method just isn't possible for web ... Is there any other way to get the same result?
To be honest, your code shouldn't work in flutter mobile either, but may be works because of screen keyboard causes widget rebuild when showing or hiding.
To fix this issue we have to use stateful widget with state variable like canSubmit and update it in textField's listener onChange with setState method. Then every time the text changes, our stateful widget will update the submit button..
class Page extends StatefulWidget {
#override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> {
bool canSubmit;
#override
void initState() {
canSubmit = false;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
onChanged: (value) {
setState(() {
canSubmit = value.isNotEmpty;
});
},
),
RaisedButton(
onPressed: canSubmit ? _submit : null,
child: Text('Submit'),
)
],
),
),
);
}
void _submit() {
print('Submitted');
}
}
Related
I have several TextFormField on a screen. If I tap one of the fields the keyboard opens as expected however if I then select a new screen from the Drawer menu the keyboard closes and as soon as the new screen finishes loading the keyboard automatically opens again. More than that if I type something the text field is updated in the background if I return to the screen with the TextFormField it shows the correct input.
I would expect the screen/widget to be disposed of when navigating to another screen(widget) from the navigation menu, and I definitely should not be able to update the content of a widget's text field while in another widget.
// Form Field
Form(key: _constructionFormKey,
child: Column(children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(
width: 100,
child: Text(
'Homes',
style: regularBoldText,
),
),
SizedBox(
width: 75,
child: Text(
'${widget.tribe.homes} (${calculatePercent(widget.tribe.land, widget.tribe.homes)}%)',
style: regularText,
),
),
SizedBox(
height: 18,
width: MediaQuery.of(context).size.width / 3,
child: TextFormField(
autovalidateMode:
AutovalidateMode.onUserInteraction,
onChanged: (String? newValue) {
if (newValue != null && isNumber(newValue)) {
setState(() {
buildHomes = int.parse(newValue);
});
// Requiered or variable will not clear properly
// when the user deletes input content
} else if (newValue == null || newValue.isEmpty) {
setState(() {
buildHomes = 0;
});
}
},
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
style: const TextStyle(fontSize: 10),
decoration: const InputDecoration(
border: OutlineInputBorder()),
keyboardType: TextInputType.number,
),
),
],
),));
// Home Screen where I have the navigation logic.
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
//! Default to tribe overview screen - 1 -, 0 is mail screen
int _drawerNavIndex = 3;
/// [setSelectedTab] will update the current screen based on the tapped option
/// from [DrawerContentWidget]
void setSelectedTab(index) {
// if the [_drawerNavIndex] is not the same as [index] update it to [index]
// value
if (_drawerNavIndex != index) {
setState(() {
_drawerNavIndex = index;
});
}
}
/// [selectedTabContent] will return the screen selected from the
/// [DrawerContentWidget] based on [_drawerNavIndex]
Widget selectedTabContent() {
List<Widget> pages = [
// Tribe Screens
const TribeMailScreen(),
const TribeHomeScreen(),
const TribeAdvisorScreen(),
const ConstructionScreen()
// Alliance
];
return IndexedStack(
index: _drawerNavIndex,
children: pages,
);
}
#override
Widget build(BuildContext context) {
TribeSummary tribe = Provider.of<TribeSummary>(context, listen: true);
// If the tribe uid value is `placeHolderTribe` assume that there is no
// existing or active tribe for this account
if (tribe.uid == 'placeHolderTribe') {
return Scaffold(
/// TODO: create a proper drawer or appBar for the [StartTribeWidget]
appBar: AppBar(
title: const Text('Orkfia'),
),
body: const StartTribeWidget(),
);
// If the tribe `uid` value is `placeHolderTribe` assume that an error
// occurred while trying to get the tribe stream or while the tribe stream
// is parsed to [TribeSummary], log should give more information
} else if (tribe.uid == 'placeHolderErrorTribe') {
// TODO: create a bettter error screen for this situation
return const Center(
child: Text('Unable to retrieve tribe data'),
);
}
// This Scaffold wraps the entire app, anything here will be avilable
// globally
return Scaffold(
// App Bar
appBar: const AppBarContent(),
// [DrawerContentWidget] holds all the drawer content, it requires
// [selectedTab] function to handle the navigation between screens
drawer: DrawerContentWidget(
setSelectedTab: setSelectedTab,
selectedTabIndex: _drawerNavIndex,
),
// Display the contents of the selected screen
body: selectedTabContent(),
// Reserved
bottomNavigationBar: SizedBox(
height: 50,
child: Container(
color: Colors.red[100],
child: const Center(child: Text('Reserved space')),
)),
);
}
}
Use TextEditingController for every TextFormField to solve this problem.
A controller for an editable text field.
First Whenever the user modifies a text field with an associated TextEditingController, the text field updates value and the controller notifies its listeners. Listeners can then read the text and selection properties to learn what the user has typed or how the selection has been updated.
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.
To close keyboard from screen
you can use GesterDetector widget.
FocusManager.instance.primaryFocus?.unfocus();
or use can below for hot fix
FocusScope.of(context).unfocus();
Example is given below
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final TextEditingController _controller = TextEditingController();
#override
void initState() {
super.initState();
_controller.addListener(() {
final String text = _controller.text.toLowerCase();
_controller.value = _controller.value.copyWith(
text: text,
selection:
TextSelection(baseOffset: text.length, extentOffset: text.length),
composing: TextRange.empty,
);
});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(6),
child: TextFormField(
controller: _controller,
decoration: const InputDecoration(border: OutlineInputBorder()),
),
),
);
}
}
When you navigate to a new page, you are not really disposing of the previous page. The new page is simply added on top of the previous page. You could try wrapping the entire scaffold in a GestureDetector with the following onTap function:
FocusScope.of(context).unfocus();
This will make sure the keyboard is dismissed when you push a new page with user taps.
On press key 1, ListView adds 1 tile, on press key 2 ListView removes one tile, though after clicking with mouse outside of ListView or Text() widget, keyboard keys stop responding without any error being shown in terminal.
I thought, that maybe FocusNode was disposed after clicking outside of ListView, though, after testing, this seems not to be the case
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class OnTapWidgetIssue extends StatefulWidget {
OnTapWidgetIssue({Key? key}) : super(key: key);
String testOnTap = '';
int nOfList = 1;
#override
_OnTapWidgetIssueState createState() => _OnTapWidgetIssueState();
}
class _OnTapWidgetIssueState extends State<OnTapWidgetIssue> {
final FocusNode _focusNode = FocusNode();
#override
void dispose() {
_focusNode.dispose();
print('_focusNode.dispose()');
super.dispose();
}
void _handleKeyEvent(RawKeyEvent event) {
if (event is RawKeyDownEvent &&
event.data.logicalKey == LogicalKeyboardKey.digit1) {
widget.nOfList += 1;
setState(() {});
}
if (event is RawKeyDownEvent &&
event.data.logicalKey == LogicalKeyboardKey.digit2) {
if (widget.nOfList > 1) {
widget.nOfList--;
setState(() {});
} else {}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: MenuDrawer(),
appBar: AppBar(title: Text('OnTap-widget.Issue')),
body: RawKeyboardListener(
autofocus: true,
focusNode: _focusNode, // <-- more magic
onKey: _handleKeyEvent,
child: Column(children: [
Text(widget.testOnTap, style: TextStyle(fontSize: 52.0)),
Text('''
press 1 to add ListTile
press 2 to remove ListTile
'''),
Expanded(
child: Row(
children: [
Expanded(
flex: 2,
child: SizedBox(),
),
Expanded(
flex: 1,
// child: SizedBox(),
// // ),
child: ListView.builder(
itemCount: widget.nOfList,
// itemCount: widget.testOnTap.length,
itemBuilder: (_, i) {
return ListTile(
title: Text('$i'),
onTap: () {
widget.testOnTap = widget.testOnTap + i.toString();
setState(() {});
},
// Handle your onTap here.
);
},
),
),
Expanded(
flex: 2,
child: SizedBox(),
),
],
),
),
]),
),
);
}
}
Also Im getting error when clicking to go to new page in the app
Error: A FocusNode was used after being disposed.
Once you have called dispose() on a FocusNode, it can no longer be used.
at Object.throw_ [as throw] (http://localhost:49535/dart_sdk.js:5061:11)
at http://localhost:49535/packages/flutter/src/foundation/change_notifier.dart.lib.js:66:21
at focus_manager.FocusNode.new.[_debugAssertNotDisposed] (http://localhost:49535/packages/flutter/src/foundation/change_notifier.dart.lib.js:69:25)
at focus_manager.FocusNode.new.notifyListeners (http://localhost:49535/packages/flutter/src/foundation/change_notifier.dart.lib.js:131:41)
at focus_manager.FocusNode.new.[_notify] (http://localhost:49535/packages/flutter/src/widgets/widget_inspector.dart.lib.js:42893:12)
at focus_manager.FocusManager.new.[_applyFocusChange] (http://localhost:49535/packages/flutter/src/widgets/widget_inspector.dart.lib.js:43665:26)
at Object._microtaskLoop (http://localhost:49535/dart_sdk.js:38778:13)
at _startMicrotaskLoop (http://localhost:49535/dart_sdk.js:38784:13)
at http://localhost:49535/dart_sdk.js:34519:9
How ever, I don't get this error when selecting exercise page in drawer menu, only when going to this new page from home page. Exercise and Home pages are kinda similar, but still different in some aspects.
Thank
Technically, you are not adding the onTap to the ListView.builder, you're adding it to every single ListTile added by the builder. :)
Declare your two state variables:
String testOnTap = '';
int nOfList = 1;
inside the _OnTapWidgetIssueState class, not the OnTapWidgetIssue class. The convention is to name them _testOnTap and _nOfList respectively since they are private to the class.
And update the two variables INSIDE the setState call, not outside it.
Flutter
i am trying to display a widget into Stack IF an condition (true or false ) , and it work with no problems
but when i need to change the condition bool into SetState to hide the Widget again , it is also work but with annoying error messages whish is setState() or markNeedsBuild() called when widget tree was locked.
my code is so complicated but i am gonna show a simple similar example
bool displayWidget = false;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Stack(
children: [
Container(
child: TextButton(
onPressed: () {
final result = await FilePicker.platform.pickFiles(allowMultiple: false );
if (result == null) return;
final path = result.files.single.path;
setState(() => displayWidget = true);
},
child: Text ("studio")
),
),
displayWidget?
GestureDetector(
onTap: ()=> setState(() => displayWidget = false), // the error happen when i click here
child: Container(
child: Image.asset("here is the picture in full secreen"),
),
):Container()
],
),
);
}
}
i know there is a photo viewer better than this way :D but i only give a simple example for other real case
First of all I would like to ask you where is #override? Did you forget to add that in your question? If yes then that error might have something to do with that. If no then try declaring the bool value above #override.
please see if this example can be helpful to you (I simplified yours just a little bit more so it can be easily run in dartpad).
in your case onPressed function should contain your file picking logic and have a return type as you expect it to be (probably, String?) and check if returned value isn't null, then show your image
class MyWidget extends StatefulWidget {
#override
State<MyWidget> createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
bool displayWidget = false;
bool onPressed(bool isShown) {
//instead of this mocked function pickFile here
return !isShown;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Row(
children: [
Container(
child: TextButton(
onPressed: () {
displayWidget = onPressed(displayWidget);
setState(() => displayWidget);
},
child: Text ("Show or hide text"),
),
),
if(displayWidget)
GestureDetector(
onTap: ()=> setState(() => displayWidget = !displayWidget),
child: Container(
child: Text('This text is to be shown'),
),
),
],
),
);
}
}
I found the solution from the official website of flutter
simply wrap set state into
in this solution would be for most cases
if(!mounted) {
setState
}
or
if(mounted) { // depends on your widget case
setState
}
source https://www.kindacode.com/article/flutter-error-setstate-called-after-dispose/
if not , try this
WidgetsBinding.instance
.addPostFrameCallback((_) => setState(() {}));
source https://www.thetopsites.net/article/50288698.shtml
and i find same error with many answers in stackoverflow
press below
Flutter setState() or markNeedsBuild() called when widget tree was locked
This is my screen with TextField and Button. When someone clicks on show button, I want it to show the name below the button as shown in below picture.
Code below:
class Demo extends StatefulWidget {
#override
_DemoState createState() => _DemoState();
}
class _DemoState extends State<Demo> {
final name = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Row(
children: [
Text(
'Name'
),
TextField(
controller: name,
)
],
),
RaisedButton(
onPressed: (){
},
child: Text('Show'),
)
],
),
),
);
}
}
This can be a basic example for your question. The UI is not exactly what you've shown above
class Question extends StatefulWidget {
Question({Key key}) : super(key: key);
#override
_QuestionState createState() => _QuestionState();
}
class _QuestionState extends State<Question> {
String text = '';
bool shouldDisplay = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
Center(
child: TextField(
onChanged: (value) {
setState(() {
text = value;
});
},
),
),
FlatButton(onPressed: () {
setState(() {
shouldDisplay = !shouldDisplay;
});
}, child: Text('Submit')),
shouldDisplay ? Text(text) : Spacer()
],
);
}
}
Hope this helps.
Initialize two variables. One for a TextEditingController and one for the text value.
TextEditingController controller = TextEditingController();
String display = '';
Give your TextField a controller.
TextField(controller:controller);
In your button, set onPressed to change display text to the controller text.
FlatButton(
child: Text("Show"),
onPressed()=> setState((){display = controller.text;});
),
Then where you want to show the text, set the text string to display.
Text(display);
I would advice you to learn the basics of flutter first before asking these kinds of questions. This can be simply achieved through using TextEditingController and setState(). Simply define a controller for your TextField and then call setState() when your button is pressed. Note that you have to be on a StatefulWidget since calling setState() rebuilds the UI.
Create a TextEditingController and string above the #override Widget build:
String displayName="";
final myController = TextEditingController();
Create a TextField and add assign the controller to it:
TextField(
controller: myController,
);
Call setState() on button pressed:
MaterialButton(
child: Text("Show"),
onPressed: (){
setState(() {
displayName=myController.text;
});
})
Display it using a Text widget:
Text(displayName);
Good Luck!
You can find out how to use TextEditingController here: https://flutter.dev/docs/cookbook/forms/retrieve-input
More about widgets here: https://www.youtube.com/playlist?list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG
I'm attempting to build a text field which triggers certain events which a user types in specific characters. For example, if a user types the '#' symbol, it will automatically trigger an event (in my case open an alert box). I've created a TextEditingController (_basicController) which adds a listener to the field and calls a function which looks for the last character typed into the field and detects matches to the symbol '#':
_detectLatestValue() {
var userInput = _basicController.text;
if(userInput.characters.last == "#"){
print("call to action");
_tagHandleSelect(_basicController.text);
}
}
It seems to print ("call to action") when it's supposed to, however when i call the _tagHandleSelect function (which opens a generic dialogue box), it seems to get stuck in a loop ie each time i close the alert box it opens another.
Here is the full page code:
import 'package:flutter/material.dart';
import 'package:characters/characters.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
home: TextEditingControllerExample(),
);
}
}
class TextEditingControllerExample extends StatefulWidget {
#override
_TextEditingControllerExampleState createState() {
return _TextEditingControllerExampleState();
}
}
class _TextEditingControllerExampleState extends State<TextEditingControllerExample> {
final _basicController = TextEditingController();
#override
void initState() {
super.initState();
_basicController.addListener(detectLatestValue);
}
#override
void dispose() {
_basicController.dispose();
super.dispose();
}
_detectLatestValue() {
var userInput = _basicController.text;
if(userInput.characters.last == "#"){
print("call to action");
_tagHandleSelect(_basicController.text);
}
}
Future<void> _tagHandleSelect(text) async {
return showDialog<void>(
context: context,
builder: (context) {
return AlertDialog(
content: Text(text),
actions: <Widget>[
FlatButton(
child: Text('Close'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
Form(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
child: TextField(
controller: _basicController,
),
],
),
),
],
)
)
),
);
}
}```
Adding a listener to the controller can be a little hard to manage.
If you only want to listen to changes on a specific TextField, you can do something like this:
child: TextField(
onChanged: (value) {
if (value.endsWith("#")) {
print("call to action");
_tagHandleSelect(value);
}
},
),
This way you know onChanged will only be triggered once.
The listener to the TextEditingController will be triggered whenever the controller changes, which can be more than once for every text edit (for example, when the Text is focused or unfocused, it will be triggered).
I hope this can help you and you can develop your idea further!!