I am new to flutter so I am trying to understand how to properly make my code more modular. I have a page with buttons on it, and am trying to change the color of the buttons when they are pressed. When I have the buttons coded directly into the page, and change use setState, the buttons change color as they should. But if I move the buttons to their own dart file, they will change colors on pressed, but not change back if a new button is selected. Any advice on where I am going wrong?
Here is the code for one of the buttons:
class DiscoverButton extends StatefulWidget {
DiscoverButton({
Key? key,
required this.pressed,
}) : super(key: key);
late int pressed;
// DiscoverButton(this.pressed);
#override
State<DiscoverButton> createState() => _DiscoverButton();
}
class _DiscoverButton extends State<DiscoverButton> {
// _DiscoverButton(pressed) {}
#override
Widget build(BuildContext context) {
// late bool pressed = false;
return TextButton(
// iconSize: 45,
// color: AppColor.AppDarkBlueColor,
style: TextButton.styleFrom(
foregroundColor: widget.pressed == 0
? AppColor.DarkBlueTextColor
: AppColor.DiscoverPageLightText),
onPressed: () async {
setState(() {
widget.pressed = 0;
});
},
child: const Text(
'Discover',
style: TextStyle(
fontSize: 18,
),
));
}
}
And here is the code where it is being inserted into the page:
bottomNavigationBar: Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: AppColor.AppLightBlueColor),
height: (MediaQuery.of(context).size.height / 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisSize: MainAxisSize.max,
children: [
Column(
// crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
DiscoverButton(pressed: selectedButton)
You can also create your own button.
Example below:
class TextButton extends StatefulWidget {
final Function() onTap;
const TextButton({
Key? key,
required this.onTap,
}) : super(key: key);
#override
State<TextButton> createState() => _TextButtonState();
}
class _TextButtonState extends State<TextButton> {
bool _pressed = false;
_onTapStateChange(pressed) {
setState(() {
_pressed = pressed;
});
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (_) {
_onTapStateChange(true);
},
onTapUp: (_) {
_onTapStateChange(false);
},
onTapCancel: () {
_onTapStateChange(false);
},
onTap: widget.onTap,
child: Text(
"Text button",
style: TextStyle(color: _pressed ? Colors.blue : Colors.black),
),
);
}
}
This text will change of color when being pressed: blue when pressed, black when not pressed.
Related
I have created a two screen ,expense screen and incomescreen with toogle button to switching expense screen to income screen and visa versa..
here I have a textfield for amount..and it's value should be not changed while shifting to another screen...
(I mean if change expense screen to income screen, amount should be not changed in textfield...
here is my main file
class _MyAppState extends State<MyApp> {
bool isexpense=true;
void toogleit()
{
setState(() {
isexpense=!isexpense;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: isexpense==true? ExpenseScreen(ontap: toogleit,txtvalue: '',):IncomeScreen(ontap: toogleit,txtvalue: '',)
);
}
}
and here is expense screen file..not including income screen as both almost same...
(I could make a CustomScreenWidget but don't want to make it....I am given task to solve without customewidget)
here is expense screen code
class ExpenseScreen extends StatefulWidget {
final VoidCallback ontap;
String txtvalue;
ExpenseScreen({Key? key, required this.ontap,required this.txtvalue}) : super(key: key);
#override
State<ExpenseScreen> createState() => _ExpenseScreenState();
}
class _ExpenseScreenState extends State<ExpenseScreen> {
TextEditingController txtcontroller = TextEditingController();
#override
void initState() {
// TODO: implement initState
super.initState();
txtcontroller.text=widget.txtvalue;
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
txtcontroller.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey,
body: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Container(
height: 300,
decoration: kboxdecoration1,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IntrinsicWidth(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 40.0),
child: TextField(
onChanged: (x) {
setState(() {
});
},
controller: txtcontroller,
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 30,
),
decoration: kinputdecoration1.copyWith(
hintText:
txtcontroller.text == '' ? 'Amount' : null),
),
),
),
SizedBox(
height: 10,
),
ElevatedButton(
style: kelevetedbutton1,
onPressed: () {},
child: Text('Save Expense')),
SizedBox(
height: 10,
),
TextButton(
onPressed: widget.ontap, child: Text('Jump To Income')),
],
),
),
),
),
),
);
}
}
You need to update your callback to update txtvalue like this:
class ExpenseScreen extends StatefulWidget {
final Function(String) ontap;
String txtvalue;
ExpenseScreen({Key? key, required this.ontap,required this.txtvalue}) : super(key: key);
#override
State<ExpenseScreen> createState() => _ExpenseScreenState();
}
then on your onPressed do this:
TextButton(
onPressed: (){
widget.ontap(txtcontroller.text);
}, child: Text('Jump To Income'),
),
then in your MainScreen first define a variable and do this:
String txtvalue = '';
void toogleit(String value)
{
setState(() {
txtvalue = value;
isexpense=!isexpense;
});
}
and also change this in your mainscreen :
home: isexpense==true? ExpenseScreen(ontap: toogleit,txtvalue: txtvalue,):IncomeScreen(ontap: toogleit,txtvalue: txtvalue,)
Two ways to do it,
Use constructor, as pointed by #eamirho3ein & pass it along when accessing second screen
Create another Dart file and save the value as static there, for example, create a file ApplicationData and store the value as static for amount. Ensure whenever screen change is happening then store the current amount value in static field for ApplicationData.amount.
Access it anywhere.
I was trying to code a TikTakToe game in FLutter but failed at the point where I tried to make a button which resets the fields.
class TikTakToe extends StatefulWidget {
const TikTakToe({Key? key}) : super(key: key);
#override
State<TikTakToe> createState() => _TikTakToeState();
}
class _TikTakToeState extends State<TikTakToe> {
int currentindex = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.teal[100],
appBar: AppBar(
title: const Text("Tik Tak Toe"),
backgroundColor: Colors.teal[400],
),
...
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
ElevatedButton(
onPressed: () {
setState(() {
gameOver = true;
});
}, child: const Icon(Icons.refresh))
],
),
],
),
),
),
);
}
}
bool gameOver = false;
class Field extends StatefulWidget {
const Field({Key? key, required this.fieldnumber}) : super(key: key);
final int fieldnumber;
#override
State<Field> createState() => _FieldState();
}
class _FieldState extends State<Field> {
String playersign = "";
#override
Widget build(BuildContext context) {
return TextButton(
child: Text(
gameOver ? "" : signlist[widget.fieldnumber],
style: const TextStyle(fontSize: 60),
),
style: TextButton.styleFrom(
shape: const RoundedRectangleBorder(
side: BorderSide(color: Color.fromARGB(255, 88, 221, 208), width: 2),
borderRadius: BorderRadius.all(Radius.zero),
),
enableFeedback: false,
primary: Colors.amber[800],
fixedSize: const Size(120.0, 120.0),
backgroundColor: Colors.white,
),
onPressed: () {
setState(() {
... Winner evaluation
}
}
}
);
},
);
}
}
Now I have the problem that the buttons (Fields) are actually resetting but only after clicking on them and not instant after clicking the reset button In the TikTakToe class.
The only thing that worked was adding
(context as Element).reassamble(); to the onPressed of the Elevated button
setState(() {
gameOver = true;
(context as Element).reassemble();
});
but I got this warning:
The member 'reassemble' can only be used within instance members of subclasses of 'package:flutter/src/widgets/framework.dart'.
Thanks!
Change gameover variable place into _FieldState class.
class Field extends StatefulWidget {
const Field({Key? key, required this.fieldnumber}) : super(key: key);
final int fieldnumber;
#override
State<Field> createState() => _FieldState();
}
class _FieldState extends State<Field> {
String playersign = "";
bool gameover = false;
#override
Widget build(BuildContext context) {
return TextButton(
child: Text(
gameover ? "" : signlist[widget.fieldnumber],
style: const TextStyle(fontSize: 60),
),: const TextStyle(fontSize: 60),
),
You need to put gameOver variable inside of Field widget and dont forget to use camelCase in naming variables, on the other hand you have to pass onPressed to TextButton if you dont want to be clickable just pass onPressed: null
class Field extends StatefulWidget {
const Field({Key key}) : super(key: key);
#override
State<Field> createState() => _FieldState();
}
class _FieldState extends State<Field> {
String playerSign = "";
bool gameOver = false; // put var inside
#override
Widget build(BuildContext context) {
return Column(
children: [
TextButton(
onPressed: null,
child: Text(
gameOver ? "" : signlist[widget.fieldnumber],
style: const TextStyle(fontSize: 60),
),
),
TextButton(
onPressed: () {
setState(() {
setState(() {
gameOver = true;
});
});
},
child: const Text(
"reset",
style: TextStyle(fontSize: 60),
),
),
],
);
}
}
suggest to read this doc
I have a variable that is supposed to be populated when an if statement is true.
The if statement is supposed to be true after a value is updated from a stateful widget when pressed.
The stateful widget is _BudgetCategoryCard and it has a bool that is set true when pressed. After being pressed, the value changes and the color of the card turns green as shown in this line: color: widget.hasBeenPressed ? Colors.green : Colors.white
However, after the value of hasBeenPressed has been set true, this if statement should be true but it isn't
if (budgetCategoryCards[index].getHasBeenPressed()) {
setState(() {
selectedBudget = budgetCategoryCards[index].getBudgetName();
});
}
I'm not sure if there is a better way/practice for retrieving values from a stateful widget or if parts of this code should be re-written for improvement but if anyone could recommend changes that would also be tremendously appreciated.
I tried simplifying the code and apologies for the bad code.
Does anyone know why this variable selectedBudget is not getting populated?
main
import 'package:flutter/material.dart';
import 'package:stackoverflow/home_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
List<String> budgets = ["gas", "food", "clothes"];
return MaterialApp(
home: CreateExpenseCardScreen(budgetsList: budgets),
);
}
}
home page
import 'package:flutter/material.dart';
class CreateExpenseCardScreen extends StatefulWidget {
const CreateExpenseCardScreen({
Key? key,
required this.budgetsList,
}) : super(key: key);
final List<String> budgetsList;
#override
State<CreateExpenseCardScreen> createState() =>
_CreateExpenseCardScreenState();
}
class _CreateExpenseCardScreenState extends State<CreateExpenseCardScreen> {
String selectedBudget = "";
#override
Widget build(BuildContext context) {
List<_BudgetCategoryCard> budgetCategoryCards = List.generate(
widget.budgetsList.length,
(index) {
return _BudgetCategoryCard(
budgetName: widget.budgetsList[index],
hasBeenPressed: false,
);
},
);
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 100),
...List.generate(
budgetCategoryCards.length,
(index) {
// why is if statement never true even after pressing card?
//
// as a result, selectedBudget doesnt get assigned a value
if (budgetCategoryCards[index].getHasBeenPressed()) {
setState(() {
selectedBudget = budgetCategoryCards[index].getBudgetName();
});
}
return budgetCategoryCards[index];
},
),
Padding(
padding: const EdgeInsets.all(16.0),
child: GestureDetector(
onTap: () {
if (selectedBudget.isEmpty) {
// send error
} else {
Navigator.pop(
context,
[
selectedBudget,
],
);
}
},
child: Container(
height: 50,
color: Colors.green,
child: const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
"Create",
style: TextStyle(
fontSize: 18,
color: Colors.white,
),
),
),
),
),
),
),
],
),
),
);
}
}
class _BudgetCategoryCard extends StatefulWidget {
_BudgetCategoryCard({
Key? key,
required this.budgetName,
required this.hasBeenPressed,
}) : super(key: key);
final String budgetName;
bool hasBeenPressed;
bool getHasBeenPressed() {
return hasBeenPressed;
}
String getBudgetName() {
return budgetName;
}
#override
State<_BudgetCategoryCard> createState() => _BudgetCategoryCardState();
}
class _BudgetCategoryCardState extends State<_BudgetCategoryCard> {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: GestureDetector(
onTap: () {
setState(() {
widget.hasBeenPressed = true;
});
},
child: Container(
height: 50,
color: widget.hasBeenPressed ? Colors.green : Colors.white,
child: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
widget.budgetName,
style: TextStyle(
color: widget.hasBeenPressed
? Colors.white
: Colors.black.withOpacity(0.5),
),
),
),
),
),
),
);
}
}
I have column of InkWells. When a button is pressed, it changes color to red from white. The problem is, when another button is pressed, the previous button doesn't change back to white.
How can I solve this?
class NavigationButton extends StatefulWidget {
const NavigationButton({
Key key,
this.title,
}) : super(key: key);
final String title;
#override
_NavigationButtonState createState() => _NavigationButtonState();
}
class _NavigationButtonState extends State<NavigationButton> {
Color _color = Colors.white;
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
InkWell(
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
child: Container(
child: Text(
widget.title,
style: TextStyle(color: Colors.white, fontSize: 25)
)
),
onTap: () {
setState(() {
_color = Colors.red;
});
}
),
SizedBox(width: 10),
Container(
width: 10,
height: 10,
decoration: BoxDecoration(color: _color, shape: BoxShape.circle)
)
]
);
}
}
The menu wuth navigation buttons:
child: Container(
height: 205,
width: 210,
padding: EdgeInsets.only(right: 10),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
for (int index = 0; index < _sectionsName.length; index++)
NavigationButton(title: _sectionsName[index], index: index)
]
)
)
Thanks in advance...
can be solved by taking a boolean variable isSeleted;
For the current situation you will have to take bool in the widget where you have defined the NavigationButton List and wrap navigationButton with gesturedetector.
import 'package:flutter/material.dart';
class NavigationButton extends StatefulWidget {
final buttonColor;
final textColor;
final String title;
const NavigationButton({
Key key,
this.title,
this.buttonColor,
this.textColor,
}) : super(key: key);
#override
_NavigationButtonState createState() => _NavigationButtonState();
}
class _NavigationButtonState extends State<NavigationButton> {
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(width: 10),
Container(
decoration: BoxDecoration(
color: widget.buttonColor,
),
child: Container(
child: Text(
widget.title,
style: TextStyle(
color: widget.textColor,
fontSize: 25,
),
),
),
)
],
);
}
}
class MainScreen extends StatefulWidget {
const MainScreen({Key key}) : super(key: key);
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
// List isSelected = [];
Map _sectionsName = {"one ": false, "two": false};
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: 205,
width: 210,
padding: EdgeInsets.only(right: 10),
child: ListView(
children: _sectionsName.keys
.map<Widget>((item) => GestureDetector(
onTap: () {
_sectionsName.forEach((key, value) {
if (key == item) {
setState(() {
_sectionsName[key] = true;
});
}else{
_sectionsName[key] = false;
}
});
},
child: NavigationButton(
title: item,
buttonColor: _sectionsName[item] == true
? Colors.red
: Colors.white,
textColor: _sectionsName[item] == true
? Colors.white
: Colors.black,
),
))
.toList(),
),
),
);
}
}
When signing in with Google, just before I get to the userInfoPath, the screen goes black and then shows the login page again before moving onto the userInfoPath. I am trying to add the loading widget so it transitions from google login to the userInfoPath nicely but I can either get the loading widget and it stays on that without moving to the UserInfoScreen or I can't get it at all. Help on how to fix this would be great.
Login Screen
class LoginScreen extends StatefulWidget {
#override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
AuthService auth = AuthService();
#override
void initState() {
super.initState();
auth.getUser.then(
(user) {
if (user != null) {
Navigator.pushReplacementNamed(context, SharedStrings.userInfoPath);
}
},
);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: white,
body: Container(
padding: Sizes2.edInsAll,
decoration: const BoxDecoration(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset(Strings.imageLogo,
width: Sizes2.imageWidth,
height: Sizes2.imageHeight,
),
Text(SharedStrings.planetRescue, style: appName, textAlign: TextAlign.center),
Text(Strings.liveSustainablySlogan, style: greySubTxt, textAlign: TextAlign.center),
LoginButton(
text: Strings.loginWithGoogle,
icon: FontAwesomeIcons.google,
color: orangeGrey,
loginMethod: auth.googleSignIn,
),
Container(
margin: Sizes1.onlyBot,
child: FlatButton.icon(
padding: Sizes2.edInsAll,
icon: const Icon(Icons.email, color: Colors.white),
color: orangeGrey,
onPressed: () => changeScreen(context, AuthWidget()),
label: Expanded(
child: Text(Strings.loginWithEmail, style: formalBtnTextSml, textAlign: TextAlign.center),
),
),
),
...
Login Button
class LoginButton extends StatelessWidget {
final Color color;
final IconData icon;
final String text;
final Function loginMethod;
const LoginButton({Key key, this.text, this.icon, this.color, this.loginMethod}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
margin: Sizes1.onlyBot,
child: FlatButton.icon(
padding: Sizes1.edgInAll,
icon: Icon(icon, color: Colors.white),
color: color,
onPressed: () async {
var user = await loginMethod();
if (user != null) {
Navigator.pushReplacementNamed(context, SharedStrings.userInfoPath);
}
...
Loading widget
class Loading extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: white,
child: Center(
child: SpinKitChasingDots(
color: deepOrange,
size: Sizes2.loadSz,
),
),
);
}
}
first of all create these two methods:
void showDialogue(BuildContext context){
showDialog(
context: context,
builder: (BuildContext context) => Loading(),
);
}
void hideProgressDialogue(BuildContext context) {
Navigator.of(context).pop(Loading());}
Then in your LoginButton, Replace your:
onPressed: () async {
var user = await loginMethod();
if (user != null) {
Navigator.pushReplacementNamed(context, SharedStrings.userInfoPath);
}
With:
onPressed: () async {
showDialogue(context);
var user = await loginMethod();
if (user != null) {
hideProgressDialogue(context);
Navigator.pushReplacementNamed(context, SharedStrings.userInfoPath);
}
Let me know if this worked for you :)