I have a list view and want to edit the Tile's title. When user click the edit icon, text widget change to TextField. Once user tap the textfield, keyboard show and immediately disappeared.
May I know what is the issue?
class EditableListTile extends StatefulWidget {
final Favourite favourite;
final Function onChanged;
final Function onTap;
const EditableListTile(
{Key? key,
required this.favourite,
required this.onChanged,
required this.onTap})
: super(key: key);
#override
_EditableListTileState createState() => _EditableListTileState();
}
class _EditableListTileState extends State<EditableListTile> {
Favourite? favourite;
late bool _isEditingMode;
late TextEditingController _titleEditingController;
#override
void initState() {
super.initState();
favourite = widget.favourite;
_isEditingMode = false;
}
#override
Widget build(BuildContext context) {
return ListTile(
onTap: () {
widget.onTap(favourite);
},
leading: leadingWidget,
title: titleWidget,
trailing: tralingButton,
);
}
Widget get leadingWidget {
return SizedBox(
width: 32,
child: FolderIcon(
color: Theme.of(context).iconTheme.color!,
),
);
}
Widget get titleWidget {
if (_isEditingMode) {
_titleEditingController = TextEditingController(text: favourite?.name);
return TextField(
controller: _titleEditingController,
);
} else {
return Text(favourite!.name);
}
}
Widget get tralingButton {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
(favourite?.isDefault == false)
? (_isEditingMode
? IconButton(
icon: const Icon(Icons.check),
onPressed: saveChange,
)
: IconButton(
icon: const Icon(Icons.edit),
onPressed: _toggleMode,
))
: Container(),
_isEditingMode
? IconButton(
icon: const Icon(Icons.cancel_outlined),
onPressed: cancelChange,
)
: Container()
],
);
}
void _toggleMode() {
setState(() {
_isEditingMode = !_isEditingMode;
});
}
void cancelChange() {
setState(() {
_isEditingMode = !_isEditingMode;
});
}
void saveChange() {
favourite!.name = _titleEditingController.text;
_toggleMode();
widget.onChanged(favourite!);
}
}
you get this error because you initialized the TextEdittingController inside the titleWidget. every time the widget rebuild, create a new instance of TextEdittingController.
on top of your clas change it like this
// late TextEditingController _titleEditingController; <== Change This
TextEditingController _titleEditingController = TextEditingController();
in titleWidget change your code to this.
Widget get titleWidget {
if (_isEditingMode) {
_titleEditingController.text = favourite?.name;
Real culprit is key in ListView.seperate. I used key : UniqueKey(). If I change to ValueKey(state.favourites[index]), it is working now.
I used key : UniqueKey() because I have onDismissed but one of the item, want to trigger onDismissed but don't want to dismissed.
Let's say, item is folder name and if user delete the item, delete the folder and delete all the files under that folder. But one folder is system generated and don't want user to delete that folder but let them to delete files. So we call confirmDismiss and tell the user that it is system generated folder and only will delete files.
But list view don't allow. So I found out the UniqueKey is work
around. So edit is importance. So that I take out UniqueKey.
Like Alex Aung's answer above, they're right that the use of UniqueKey() is a problem .
In my case I had an action button that pushed a page route to the navigator. On the content view (GameView) I had a UniqueKey() set and it was responsible for series of issues with input fields downstream.
Any time I set this back to UniqueKey(), any clicking inside a downstream TextFormField causes the Keyboard open then immediately close.
Widget getActionButton() {
return FloatingActionButton(
onPressed: () {
final Account account = Account("test#test.com");
widget.viewModels.gamesModel.load(account);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GamesView(
key: Key("GameView"), // UniqueKey() is a problem here.
account: account,
viewModels: widget.viewModels,
)
)
);
},
tooltip: 'Save Changes',
child: Icon(Icons.save),
);
}
Related
Here goes my code ,
I am using the TextButton to update the order ,But after every change in the dropdown item, onPress is automatically invoked and the function updateOrder is automatically invoked
import 'package:admin/constants/Constants.dart';
import 'package:admin/model/order_model.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
class DetailsScreen extends StatefulWidget {
const DetailsScreen({Key? key}) : super(key: key);
#override
State<DetailsScreen> createState() => _DetailsScreenState();
}
class _DetailsScreenState extends State<DetailsScreen> {
List<String> _dropDownQuantities = [Pending, Confirmed, Rejected, Success];
late OrderModel order;
late String selectedStatus = order.status;
#override
Widget build(BuildContext context) {
order = ModalRoute.of(context)?.settings.arguments as OrderModel;
return Scaffold(
appBar: AppBar(
actions: [],
title: Text("Order Details"),
),
body: Column(children: [
Text(order.id),
DropdownButtonHideUnderline(
child: DropdownButton2<String>(
value: selectedStatus,
items: _dropDownQuantities
.map((e) => DropdownMenuItem<String>(
child: Text(e),
value: e,
))
.toList(),
onChanged: (value) {
setState(() {
selectedStatus = value;
});
},
)),
TextButton(onPressed: updateOrder(order, selectedStatus), child: Text("Confirm")),
]));
}
}
updateOrder(OrderModel order, String selected) {
print("I am executed");
}
So whenever i change the dropDown menu,
I am executed is printed in the console.
Edit:
But when i used the container with InkWell it was working fine. Why not working with TextButton ?
You are directly calling the method on build, you can create an inline anonymous function to handle this.
TextButton(
onPressed: ()=> updateOrder(order, selectedStatus),
child: Text("Confirm")),
onPressed Called when the button is tapped or otherwise activated.
While we use onPressed:method() call on every build, on dropDown onChanged we use setState, and it rebuilds the UI and onPressed:method() call again.
What we need here is to pass a function(VoidCallback) that will trigger while we tap on the button. We provide it like,
onPressed:(){
myMethod();
}
More about TextButton.
For instance: I have a main IconButton using assets/image/button.png as an icon!
so when you click on it, it opens a pop-up window with smaller icons/images using the assets as well and from that pop-up, you can select IconButton to replace the main Image. So if you select one of the pictures from that pop-up it replaces the main Icon to that specific image. How would you implement that if you wanted to use your assets as buttons?
On separate Dart file I have main Image Container:
Container(
width: 100,
padding: EdgeInsets.only(bottom: 50),
child: MainIcon()
),
Code I have tried:
class MainIcon extends StatefulWidget {
const MainIcon({Key? key}) : super(key: key);
#override
State<MainIcon> createState() => _MainIcon();
}
class _MainIcon extends State<MainIcon> {
Widget build(BuildContext context) {
return Container(
child: IconButton(
icon: GetItems(),
iconSize: 150,
onPressed: () {
showShopItems();
},
));
}
//pop-up window for changing icons
Future showShopItems() => showDialog(
context: context,
builder: (context) => AlertDialog(
content: ShopItems(),
backgroundColor: Colors.transparent,
));
//change food icons
Image GetItems() {
if (Bag.isClicked = true) {
return Image.asset('assets/images/bag.png');
}
if (Shampoo.isClicked = true) {
return Image.asset('assets/images/shampoo.png');
}
if (lotion.isClicked == true) {
return Image.asset('assets/images/lotion.png');
} else {
return Image.asset('assets/images/cart.png');
}}}
What each widget button looks like:
class Bag extends StatefulWidget {
static bool isClicked = false;
const Bag({Key? key}) : super(key: key);
#override
State<Bag> createState() => _Bag();
}
class _Bag extends State<Bag> {
Widget build(BuildContext context) {
return Container(
child: IconButton(
icon: Image.asset('assets/images/bag.png'),
iconSize: 70,
onPressed: () {
Bag.isPressed == true;
});
print("bag is clicked");
},
));
}
}
I would try to give each button an id and when clicking on the corresponding button, assign the desired id to the big main button, and then call setState (() {}) to update the widget and it will change its image. Further, I would save this id to the Database and asynchronously load the value every time I log in to the application, you can use Flutter Secure Storage or Shared Preferences as the Database for application settings. If you have something more than just changing the image on the main button, for example, changing the link that the button leads to, then create models, then create a list of models with predefined values and use them.
I want to set the updated text value of button throughout the app, when i click on button its text changes to current time, but when I navigate to other screen, and then come back to the screen where I created a button, it is not showing the updated text.
here is my button widget
String getTime;
//from here i get the current time
void _getTime() {
final String formattedDateTime =
DateFormat('kk:mm:ss a').format(DateTime.now()).toString();
setState(() {
getTime = formattedDateTime;
print("time");
print(getTime);
});
}
String timeInText = "Time in";
Widget _timein() {
//enable- initial case
bool firstCaseFlag = true;
if (getTimeInStatus == false && timeInButtonPressed == true) {
print("i1");
return FlatButton(
color: timeInButtonPressed ? Colors.blue[500] : Colors.blue[200],
textColor: Colors.white,
padding: EdgeInsets.all(15.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(buttonRoundRadius)),
child: Row(children: <Widget>[
Icon(
Icons.timer,
),
Expanded(
child: Text(
timeInText,
textAlign: TextAlign.center,
style: TextStyle(fontSize: textFontSize),
),
),
]),
onPressed: () {
_getTime();
setState(() {
if (firstCaseFlag == true) {
timeInText = getTime; //here i set the button text to current time
timeIn = timeInText;
firstCaseFlag = false;
} else {
}
});
calltimeInApi();
});
Conditions:
There are certain conditions where button will change there state, like i have 2 button namely timein and timeout, initially timein button will be enable to click and timeout will be disable, so if user click on timein button its text change to current time and timeout button will be enable (this is all happening), and if user moved to other screen and come to home screen (where i created timein and timeout buttons) then timein button text should display that time when user click on it.
Problem:
My problem is when I moved to other screen and come to home screen timein button is enabled and not showing the time when i click on it.
please help how i can fix it.
I prefer using statemanagement StateProvider. here is an example just using global variable.
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:stack_overflow/exports.dart';
String buttonText = "Click to set";
///for riverpod
///final buttonState = StateProvider((ref) => "Click to set");
class BaseWidget extends StatefulWidget {
const BaseWidget({Key? key}) : super(key: key);
#override
_BaseWidgetState createState() => _BaseWidgetState();
}
class _BaseWidgetState extends State<BaseWidget> {
void _getTime() {
final String formattedDateTime =
DateFormat('kk:mm:ss a').format(DateTime.now()).toString();
setState(() {
buttonText = formattedDateTime;
print("time");
print(buttonText);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
ElevatedButton(
onPressed: () {
_getTime();
},
child: Text(buttonText),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => NextWidget(),
));
},
child: Text("next"),
),
],
),
);
}
}
class NextWidget extends StatelessWidget {
const NextWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Back"),
),
);
}
}
Use state management like Provider to keep the values and then access anywhere.
Package link: https://pub.dev/packages/provider
Helpful reference: https://flutter.dev/docs/development/data-and-backend/state-mgmt/intro
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 have a button that displays a SnackBar (or toast) before moving to the next page. I have a countdown and after 5 seconds I push Page2.
RaisedButton(
onPressed: () {
_startTimer();
final snackBar = SnackBar(
behavior: SnackBarBehavior.floating,
content: Text(
'Prepare yourself to start in ${widget._current.toString()}!'), // doesn't work here
duration: new Duration(seconds: widget._start),
action: SnackBarAction(
label: widget._current.toString(), // and neither does here
onPressed: () {
// Some code to undo the change.
},
),
);
Scaffold.of(context).showSnackBar(snackBar);
},
child: Text(
"I'm ready",
style: TextStyle(fontSize: 20),
),
),
Nothing to see on the countdown but I'll paste it just in case:
void _startTimer() {
CountdownTimer countDownTimer = new CountdownTimer(
new Duration(seconds: widget._start),
new Duration(seconds: 1),
);
var sub = countDownTimer.listen(null);
sub.onData((duration) {
setState(() {
widget._current = widget._start - duration.elapsed.inSeconds;
});
});
sub.onDone(() {
print("Done");
sub.cancel();
});
}
So if I display the countdown somewhere else (inside a Text for example) it works but it seems that the SnackBar doesn't change its contain, it always get the max number of the countdown.
you need to implement a custom widget with countdown logic in side for the content field of snackbar, like this:
class TextWithCountdown extends StatefulWidget {
final String text;
final int countValue;
final VoidCallback? onCountDown;
const TextWithCountdown({
Key? key,
required this.text,
required this.countValue,
this.onCountDown,
}) : super(key: key);
#override
_TextWithCountdownState createState() => _TextWithCountdownState();
}
class _TextWithCountdownState extends State<TextWithCountdown> {
late int count = widget.countValue;
late Timer timer;
#override
void initState() {
super.initState();
timer = Timer.periodic(Duration(seconds: 1), _timerHandle);
}
#override
void dispose() {
timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
child: Text("[$count] " + widget.text),
);
}
void _timerHandle(Timer timer) {
setState(() {
count -= 1;
});
if (count <= 0) {
timer.cancel();
widget.onCountDown?.call();
}
}
}
That is because the snack bar is built once, when the button is clicked. When the state updates, it rebuilds the widget tree according to the changes. The snack bar initially isn't in the widget tree, so it doesn't update.
Try to use stack and show a snack bar, and then you should be able to manipulate it however you need.
Hope it helps.
Update SnackBar Content
SnackBar content can be updated/rebuilt while it's visible by making its content: widget dynamic.
In the code sample below we're using a ValueNotifier and ValueListenableBuilder to dynamically rebuild the content of the SnackBar whenever ValueNotifier is given a new value.
(There are many ways to to maintain state values & rebuild widgets when it changes, such as RiverPod, GetX, StreamBuilder, etc. This example uses the Flutter native ValueListenableBuilder.)
When running this Flutter page, click the FAB to show the SnackBar, then click on the center text to update the SnackBar's content (multiple times if you like).
Example
Use SnackBarUpdateExample() widget in your MaterialApp home: argument to try this example in an emulator or device.
class SimpleCount {
int count = 0;
}
class SnackBarUpdateExample extends StatelessWidget {
static const _initText = 'Initial text here';
/// This can be "listened" for changes to trigger rebuilds
final ValueNotifier<String> snackMsg = ValueNotifier(_initText);
final SimpleCount sc = SimpleCount();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SnackBar Update'),
),
/// ↓ Nested Scaffold not necessary, just prevents FAB being pushed up by SnackBar
body: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Click FAB to show SnackBar'),
SizedBox(height: 20,),
Text('Then...'),
SizedBox(height: 20,),
InkWell(
child: Text('Click → here ← to update SnackBar'),
onTap: () {
sc.count++;
snackMsg.value = "Hey! It changed! ${sc.count}";
},
), /// When snackMsg.value changes, the ValueListenableBuilder
/// watching this value, will call its builder function again,
/// and update its SnackBar content widget
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () => _showSnackBar(context),
),
),
);
}
void _showSnackBar(BuildContext context) {
var _controller = ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: SnackContent(snackMsg))
);
/// This resets the snackBar content when it's dismissed
_controller.closed.whenComplete(() {
snackMsg.value = _initText;
sc.count = 0;
});
}
}
/// The ValueListenableBuilder rebuilds whenever [snackMsg] changes.
class SnackContent extends StatelessWidget {
final ValueNotifier<String> snackMsg;
SnackContent(this.snackMsg);
#override
Widget build(BuildContext context) {
/// ValueListenableBuilder rebuilds whenever snackMsg value changes.
/// i.e. this "listens" to changes of ValueNotifier "snackMsg".
/// "msg" in builder below is the value of "snackMsg" ValueNotifier.
/// We don't use the other builder args for this example so they are
/// set to _ & __ just for readability.
return ValueListenableBuilder<String>(
valueListenable: snackMsg,
builder: (_, msg, __) => Text(msg));
}
}