How to set the updated text value of button throughout the app - flutter

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

Related

Main app widget: setState call -> build() builds all widgets but screen not updated

App is a simple memory/guessing game with a grid of squares. Floating action button triggers a "New game" dialog, and a Yes response triggers setState() on the main widget. The print() calls show it is building all the Tile widgets in the grid, but as it returns, the old grid values are still showing. Probably done something stupid but not seeing it. Basic code is below. TIA if anyone can see what is missing/invalid/broken/etc.
Main.dart is the usual main() that creates a stateless HomePage which creates a stateful widget which uses this State:
class MemHomePageState extends State<MemHomePage> {
GameBoard gameBoard = GameBoard();
GameController? gameController;
int gameCount = 0, winCount = 0;
#override
void initState() {
super.initState();
gameController = GameController(gameBoard, this);
}
#override
Widget build(BuildContext context) {
if (kDebugMode) {
print("MemHomepageState::build");
}
gameBoard.newGame(); // Resets secrets and grids
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: GridView.count(
crossAxisCount: Globals.num_columns,
children: List.generate(Globals.num_columns * Globals.num_rows, (index) {
int x = index~/Globals.NR, y = index%Globals.NR;
int secret = gameBoard.secretsGrid![x][y];
var t = Tile(x, y, Text('$secret'), gameController!);
gameBoard.tilesGrid![x].add(t);
if (kDebugMode) {
print("Row $x is ${gameBoard.secretsGrid![x]} ${gameBoard.tilesGrid![x][y].secret}");
}
return t;
}),
),
// Text("You have played $gameCount games and won $winCount."),
),
floatingActionButton: FloatingActionButton(
onPressed: () => newGameDialog("Start a new game?"),
tooltip: 'New game?',
child: const Icon(Icons.refresh_outlined),
),
);
}
/** Called from the FAB and also from GameController "won" logic */
void newGameDialog(String message) {
showDialog<void>(
context: context,
barrierDismissible: false, // means the user must tap a button to exit the Alert Dialog
builder: (BuildContext context) {
return AlertDialog(
title: Text("New game?"),
content: Text(message),
//),
actions: <Widget>[
TextButton(
child: const Text('Yes'),
onPressed: () {
setState(() {
gameCount++;
});
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('No'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
}
);
}
The Tile class is a StatefulWidget whose state determines what that particular tile should show:
import 'package:flutter/material.dart';
import 'gamecontroller.dart';
enum TileMode {
SHOWN,
HIDDEN,
CLEARED,
}
/// Represents one Tile in the game
class Tile extends StatefulWidget {
final int x, y;
final Widget secret;
final GameController gameController;
TileState? tileState;
Tile(this.x, this.y, this.secret, this.gameController, {super.key});
#override
State<Tile> createState() => TileState(x, y, secret);
setCleared() {
tileState!.setCleared();
}
}
class TileState extends State<Tile> {
final int x, y;
final Widget secret;
TileMode tileMode = TileMode.HIDDEN;
TileState(this.x, this.y, this.secret);
_unHide() {
setState(() => tileMode = TileMode.SHOWN);
widget.gameController.clicked(widget);
}
reHide() {
print("rehiding");
setState(() => tileMode = TileMode.HIDDEN);
}
setCleared() {
print("Clearing");
setState(() => tileMode = TileMode.CLEARED);
}
_doNothing() {
//
}
#override
Widget build(BuildContext context) {
switch(tileMode) {
case TileMode.HIDDEN:
return ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.teal,
),
onPressed: _unHide,
child: Text(''));
case TileMode.SHOWN:
return ElevatedButton(
onPressed: _doNothing,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
),
child: secret);
case TileMode.CLEARED:
return ElevatedButton(
onPressed: _doNothing,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black12,
),
child: const Icon(Icons.check));
}
}
}
it looks like you are calling the following in your build function. That would cause everything to reset everytime it builds. Perhaps it belongs in init instead?
gameBoard.newGame(); // Resets secrets and grids
The original problem is that the Tile objects, although correctly created and connected to the returned main widget, did not have distinct 'key' values so they were not replacing the originals. Adding 'key' to the Tile constructor and 'key: UniqueKey()' to each Tile() in the loop, solved this problem. It exposed a related problem but is out of scope for this question. See the github link in the OP for the latest version.

Flutter Keyboard show and immediately disappeared at textfied in listview

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),
);
}

Loading icon is still running when I press to go back to main screen (sign-in page)

I am making an app to search for some items on goggle.
While doing this search app shows a loading icon, after search is complete it goes to the second screen.
If I press go back to do another search, loading icon still running.
!isLoading ? FlatButton(
color: Colors.blue,
textColor: Colors.white,
disabledColor: Colors.grey,
disabledTextColor: Colors.black,
padding: EdgeInsets.all(8.0),
splashColor: Colors.blueAccent,
onPressed: () async{
String query = getQuery();
List<Recipe> receitas = await getReceitas(query);
Navigator.push(context, MaterialPageRoute(builder: (context)=>result_screen(receitas)));
setState(() {
isLoading = true;
});
},
child: Text('BUSCAR', style: TextStyle(fontSize: 15.0))):
Center(
child: CircularProgressIndicator(),
),
To solve this problem tried to use global variables, as explained in Global Variables in Dart but it didn't work.
globals.dart
library my_prj.globals;
bool isLoading;
main.dart
import 'globals.dart' as global;
!global.isLoading?FlatButton(...
setState(() {
global.isLoading = true;
});
result_screen.dart
import 'globals.dart' as global;
global.isLoading = false;
...
I can show more parts of my code if necessary.
You don't need global variable to achieve this use life cycle methods(deactivate())
class Temp extends StatefulWidget {
#override
_TempState createState() => _TempState();
}
class _TempState extends State<Temp> {
bool isLoading=false;
void deactivate() { //Life cycle method
isLoading=false;
super.deactivate();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.yellow,
),
);
}
}
This deactivate method will be called when this widget is popped or in normal terms when you navigate to a different page. In the deactivate method I have set isLoading=false, So when
you navigate to the next page isloading becomes false.

How to rebuild a screen if confirm is pressed on bottom sheet?

I have built a home screen with is rendering cards using ListView.builder. These cards have a confirm button which fetches the confirmation status from firestore. When I tap on the confirm button, a bottom sheet appears asking whether I am sure. Once I tap Yes on the bottom sheet, I want the card on homepage to be rebuilt and change the button from confirm to confirm.
I used the setState to change the value at the onPressed event and it is successfully changing it but, the confirm button is not changing to confirmed.
Any leads on how to solve this issue would be really appreciated.
Homepage cards layout
class HomepageCards extends StatefulWidget {
final FirebaseUser user;
final Map cardDetails;
HomepageCards({#required this.user, this.cardDetails});
#override
_HomepageCardsState createState() => _HomepageCardsState();
}
class _HomepageCardsState extends State<HomepageCards> {
#override
Widget build(BuildContext context) {
// Confirmation status from firebase about the captain
bool isConfirmed = widget.cardDetails['c'];
final screenHeight = MediaQuery.of(context).size.height;
final screenWidth = MediaQuery.of(context).size.width;
return SingleChildScrollView(
padding: EdgeInsets.fromLTRB(screenHeight / 60, screenHeight / 90,
// UI Code here......
Container(
height: screenHeight / 80,
),
// Confirm Button checking condition and building UI accordingly
isConfirmed == true
? captainConfirmed(context, isConfirmed) // if confirmed then different button style widget
: confirmAndCancelButton(context, isConfirmed), //if not confirmed then show confirm and cancel button in the card
],
),
// Some UI
);
}
}
Once clicking on cancel, the bottom sheet:
Widget confirmCaptainBookingBottomSheet(
BuildContext context, bool isConfirmed) {
final screenHeight = MediaQuery.of(context).size.height;
final screenWidth = MediaQuery.of(context).size.width;
showModalBottomSheet(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Container(
// UI Code
Text(
'Do you want to confirm?',
style: TextStyle(
color: black.color,
fontSize: headSize.fontSize,
),
),
child: FlatButton(
child: Text(
'YES',
style: TextStyle(
color: cyan.color,
fontSize: headSize.fontSize),
),
onPressed: () {
print(isConfirmed);
setState(() {
// change the value of is confirmed which is used to build different buttons in the UI as shown in the above code
isConfirmed = true;
});
print(isConfirmed);
Navigator.pop(context);
}),
),
child: FlatButton(
child: Text(
'NO',
style: TextStyle(
color: cyan.color,
fontSize: headSize.fontSize),
),
onPressed: () {
Navigator.pop(context);
}),
),
});
}
You can create a function in _HomepageCardsState class which change state of isConfirmed and pass that function to widget where you want change the state. Then on onPressed of yes just give that function. it will change state of isConfirmed in _HomepageCardsState widget so you can see captainConfirmed widget.
I am leaving small demo which simulates how you can do that in your case.
I hope following code clear your idea.
class DeleteWidget extends StatefulWidget {
#override
_DeleteWidgetState createState() => _DeleteWidgetState();
}
class _DeleteWidgetState extends State<DeleteWidget> {
bool isConfirmed = false;
changeconfirmed() {
setState(() {
isConfirmed = !isConfirmed;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: isConfirmed
? Home1()
: Home2(
function: changeconfirmed,
),
)),
);
}
}
class Home1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Text("confirmed Widget"),
);
}
}
class Home2 extends StatelessWidget {
final Function function;
Home2({this.function});
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text("press"),
onPressed: function,
);
}
}

Flutter popped screen retains values when called again

I have a ScreenA with 2 buttons. Button 1 pushes ScreenB(snapshot) and is received as ScreenB(this.snapshot). Button 2 pushes ScreenB(null). Depending on wether snapshot is null or not in ScreenB, different methods are triggered in ScreenB initState.
Now, If I first press Button 2, i.e. ScreenB(null), snapshot is indeed null in ScreenB. But If I press Button1, i.e. ScreenB(snapshot), then pop ScreenB, then press Button2, I would expect snapshot to be null in ScreenB, but it's not.
Am I missing something here? Appreciate some enlightment.
Test Code added as requested:
ScreenA:
import 'screen_b.dart';
import "package:flutter/material.dart";
import 'mock_data.dart';
List < MockData > snapshot = List < MockData > ();
class ScreenA extends StatelessWidget {
#override
Widget build(BuildContext context) {
snapshot.add(MockData("1", "title", "description"));
return Scaffold(
appBar: AppBar(
title: Text("ScreenA"),
centerTitle: true,
),
body: Padding(
padding: const EdgeInsets.all(12.0),
child: ListView(
children: < Widget > [
Center(child: FlatButton(
child: Text("Goto ScreenB with null", style: TextStyle(fontSize: 20.0, color: Colors.blue)),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => ScreenB(null)));
},
), ),
SizedBox(height: 20.0), Center(child: FlatButton(
child: Text("Goto ScreenB with data", style: TextStyle(fontSize: 20.0, color: Colors.blue)),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => ScreenB(snapshot[0])));
},
), ),
],
),
), );
}
}
ScreenB:
import "package:flutter/material.dart";
import 'mock_data.dart';
bool editing = false;
class ScreenB extends StatefulWidget {
ScreenB(this._snapshot);
final MockData _snapshot;
#override
_ScreenBState createState() => _ScreenBState();
}
class _ScreenBState extends State < ScreenB > {
#override
void initState() {
super.initState();
if (widget._snapshot != null) {
editing = true;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ScreenB"),
centerTitle: true,
),
body: Padding(
padding: const EdgeInsets.all(30.0),
child: Center(child: Text("Is Editing: $editing ",
style: TextStyle(fontSize: 20.0, color: Colors.red)),
),
), );
}
}
MockData Class:
class MockData {
String id;
String title;
String description;
MockData(this.id, this.title, this.description);
}
To test:
1. run ScreenA and tap button "Goto ScreenB with null". Text on ScreenB shows "Is Editing: false" as expected.
Hit back arrow on appBar to pop ScreenB, then tap "Goto ScreenB with data". Text on ScreenB shows "Is Editing: true" as expected.
Repeat 1 and now Text on ScreenB shows "Is Editing: true" as NOT expected.
Is the snaphot that you mentioned here of type AsyncSnapshot , if yes then the data snapshot is never null, the data field of snapshot is what is null
snapshot.data == null
Let me know if this helps.
Ended up doing:
#override
void dispose() {
editing = false;
super.dispose();
}
in ScreenB. Still puzzled though why the variable keeps the last value, even after the screen has been popped.