The problem here is that the Container doesn't redraw itself after changing the funArg value with the press of a button which should change its height since it's used in its calculation
here is the code :
here is main.dart:
import 'package:flutter/material.dart';
import 'package:sqaure/ui/fun.dart';
Widget rect0;
String rectArg = "20";
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new HomeState();
}
}
class HomeState extends State<Home> {
var list = ["20", "15"];
Widget funTest() {
setState(() {
rectArg = list[1];
rect0 = new Plate(rectArg);
});
}
//final Color primaryColor = Colors.red;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("rect"),
backgroundColor: Colors.red,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Container(
alignment: Alignment.topCenter,
color: Colors.white,
height: 245.0,
child: new Stack(
children: <Widget>[
Center(
child: Padding(
padding: const EdgeInsets.only(left: 55.0),
child: Row(
children: <Widget>[
//plates
rect0 = new Plate(rectArg),
],
),
),
)
],
),
),
new RaisedButton(onPressed: () {
funTest();
debugPrint(rectArg);
})
],
),
);
}
}
and here is fun.dart:
import 'package:flutter/material.dart';
class Plate extends StatefulWidget {
final String funArg2;
#override
State<StatefulWidget> createState() {
return new PlateState(funArg2);
}
[enter image description here][1]
Plate(this.funArg2);
}
class PlateState extends State<Plate> {
String funArg;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(3.0),
child: Container(
alignment: Alignment.center,
color: Colors.redAccent,
height: funArg != "" ? (9.33 * double.parse(funArg) + 45) : 0.0,
width: 29.0,
child: new Text(
funArg,
style: new TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: funArg.length > 4
? 10.0
: funArg.length > 3 ? 14.0 : 19.0,
),
),
));
}
PlateState(this.funArg);
}
as you can see the height of the container is determined by the child text inside.
screenshot
thank you.
Here is a fixed and commented version of your code. Please read the comments!
The main problem is that you defined Plate as a stateful widget, and stored rectArg in the state! PlateState is only initiated once, until you leave the screen, it's not recreated when the parent widget is rebuilt!
Plate actually doesn't have any internal state, so it should be a StatelessWidget. You should always prefer StatelessWidgets. Understanding why is fundamental for Flutter development!
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(home: Home()));
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new HomeState();
}
}
// this seems to be a constant, so can put it outside of the class
// or alternatively inside, with "static const" modifier
const list = ["20", "15"];
class HomeState extends State<Home> {
// stateful variables (things that change over time)
// must be inside of your state class
String rectArg = "20";
// we can return void here!
void funTest() {
setState(() {
// state is modified here. this triggers a rebuild/redraw
// that means the build function is called again
// note that we are only the storing the string value, NOT a widget!
rectArg = list[1];
});
}
// this is called every time you setState
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("rect"),
backgroundColor: Colors.red,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Container(
alignment: Alignment.topCenter,
color: Colors.white,
height: 245.0,
child: new Stack(
children: <Widget>[
Center(
child: Padding(
padding: const EdgeInsets.only(left: 55.0),
child: Row(
children: <Widget>[
// DO NOT SET VARIABLES FROM THE BUILD METHOD!
// this is bad:
// rect0 = new Plate(rectArg),
Plate(
funArg: rectArg,
),
],
),
),
)
],
),
),
new RaisedButton(onPressed: () {
funTest();
debugPrint(rectArg);
})
],
),
);
}
}
// Plate is actually a StatelessWidget because it is not interactive and holds no internal state
// All the data (funArg) is passed in from the parent ==> StatelessWidget
// Always prefer stateless widgets!
// That means the widget is completely rebuilt every time the build() method is called in HomeState
class Plate extends StatelessWidget {
// Use named constructor parameters and call the super constructor!
// you can auto-generate the constructor with Android Studio
const Plate({Key key, this.funArg}) : super(key: key);
final String funArg;
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(3.0),
child: Container(
alignment: Alignment.center,
color: Colors.redAccent,
height: funArg != "" ? (9.33 * double.parse(funArg) + 45) : 0.0,
width: 29.0,
child: new Text(
funArg,
style: new TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: funArg.length > 4 ? 10.0 : funArg.length > 3 ? 14.0 : 19.0,
),
),
),
);
}
}
Just in case that you need a StatefulWidget with internal state that also has constructor parameters set by the parent widget (which is quite common): Inside the build method of your State, use the widget property to access the final fields of your widget:
class ColoredCheckbox extends StatefulWidget {
const ColoredCheckbox({Key key, this.color}) : super(key: key);
// this is passed in from the parent, can change when the parent is rebuilt
final Color color;
#override
State<StatefulWidget> createState() => ColoredCheckboxState();
}
class ColoredCheckboxState extends State<ColoredCheckbox> {
// this is internal state, kept even when the parent is rebuilt
bool checked = false;
// build is called when:
// - you call setState from this widget
// - when the parent widget is rebuilt
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text(checked ? 'X' : '0'),
// use "widget" to access the fields passed in from the parent
color: widget.color,
onPressed: () {
// always call setState when changing internal state
setState(() {
checked = !checked;
});
},
);
}
}
Related
I am trying to rebuild a stateful widget every time a value in my global Singelton is changed but I'm stumped.
My goal is to rebuild my Cart Icon every time the cartSize is changed throughout my app.
I know I need to send out a notification whenever the Singelton cartSize value is changed. and listen for that notification in my stateful widget but how do I do this?
Any help is greatly appreciated.
My Global Singelton
library #######.globals;
import 'package:flutter/material.dart';
class GlobalSingleton extends ChangeNotifier {
static final GlobalSingleton _instance = GlobalSingleton._internal();
// passes the instantiation to the _instance object
factory GlobalSingleton() {
return _instance;
}
//initialize variables in here
GlobalSingleton._internal() {
cartSize = 0;
}
late int cartSize;
}
My stateful Widget
import 'package:######/globals/globals.dart';
import 'package:flutter/material.dart';
class BuildMarketplaceCartIcon extends StatefulWidget {
const BuildMarketplaceCartIcon({Key? key}) : super(key: key);
#override
State<BuildMarketplaceCartIcon> createState() =>
_BuildMarketplaceCartIconState();
}
class _BuildMarketplaceCartIconState extends State<BuildMarketplaceCartIcon> {
CRUDMarketplaceCart localCartData = CRUDMarketplaceCart();
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
},
child: Container(
width: 72,
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Stack(
alignment: Alignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Icon(Icons.shopping_cart),
Text(
'Cart',
overflow: TextOverflow.ellipsis,
),
],
),
Positioned(
top: 0,
right: 0,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
alignment: Alignment.center,
child: Text(
'${globals.GlobalSingleton().cartSize}',
),
),
),
],
),
),
);
}
}
When using ChangeNotifier you need to call notifyListeners when the value changes. You also need to have your widget listen to the ChangeNotifier, so that it knows when to rebuild.
The most common way to go about that is to use the provider package, which includes the ChangeNotifierProvider widget.
Using provider, your code would look something like this:
class GlobalSingleton extends ChangeNotifier {
static final GlobalSingleton _instance = GlobalSingleton._internal();
// passes the instantiation to the _instance object
factory GlobalSingleton() {
return _instance;
}
//initialize variables in here
GlobalSingleton._internal() {
_cartSize = 0;
}
late int _cartSize;
int get cartSize => _cartSize;
void set cartSize(int newCartSize) {
_cartSize = newCartSize;
notifyListeners();
}
}
Here, we update your singleton so that it will call notifyListeners() whenever the cartSize is set.
Next you'll need to update your widget to listen to the changes:
import 'package:######/globals/globals.dart';
import 'package:flutter/material.dart';
class BuildMarketplaceCartIcon extends StatefulWidget {
const BuildMarketplaceCartIcon({Key? key}) : super(key: key);
#override
State<BuildMarketplaceCartIcon> createState() =>
_BuildMarketplaceCartIconState();
}
class _BuildMarketplaceCartIconState extends State<BuildMarketplaceCartIcon> {
CRUDMarketplaceCart localCartData = CRUDMarketplaceCart();
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {},
child: Container(
width: 72,
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Stack(
alignment: Alignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Icon(Icons.shopping_cart),
Text(
'Cart',
overflow: TextOverflow.ellipsis,
),
],
),
Positioned(
top: 0,
right: 0,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
alignment: Alignment.center,
child: ChangeNotifierProvider.value(
value: GlobalSingleton(),
child: Consumer<GlobalSingleton>(
builder: (context, singleton, child) {
return Text(
'${singleton.cartSize}',
);
},
),
),
),
),
],
),
),
);
}
}
Here I put the Provider widget as directly enclosing the Consumer widget - however, if you need the singleton's value in more than one place, you can put the Provider higher up the widget tree so that it's a common ancestor of any Consumer that listens to changes in the singleton.
I have created a custom button widget class with Icon and onTap constructors. I want to use this button in any screen with custom icon and custom function on button tapped.
class ActionButton extends StatelessWidget {
ActionButton({this.icon, this.onPress});
final FaIcon? icon;
final Function? onPress;
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(18.0),
// padding: const EdgeInsets.all(0.2),
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(6.0)),
// padding: const EdgeInsets.all(1.0),
child: IconButton(
onPressed: () {
onPress!;
},
icon: icon!,
color: Colors.black,
),
);
}
}
As you can see, with this widget I can use this ActionButton in any screen with any icon and its action will be any function what I pass as argument.
But when I am using this button on my another page and passing function, function does not get executed:
class ProductDetailsScreen extends StatelessWidget {
final Product? product;
const ProductDetailsScreen({Key? key, this.product}) : super(key: key);
#override
Widget build(BuildContext context) {
Sizes(context).initSize();
final double _h = Sizes.screenHeight;
final double _w = Sizes.screenWidth;
final _theme = Theme.of(context);
final product = ModalRoute.of(context)!.settings.arguments as Product;
void _goBack() {
Navigator.of(context).pop();
print('the page is popped');
}
return Scaffold(
// backgroundColor: _theme.scaffoldBackgroundColor,
body: SafeArea(
child: Stack(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
ActionButton(
icon: FaIcon(
Icons.arrow_back,
),
onPress: () {
_goBack();
}),
ActionButton(
icon: FaIcon(Icons.share),
)
],
),
ListView(
scrollDirection: Axis.vertical,
children: <Widget>[
Container(
height: 60,
child: Image.asset('${product.imageSource}'),
)
],
)
],
),
),
);
}
}
that_ goBack() function is not working
please help me, thanks in advance
In Product Details page you need to pass the onPress parameter as follows:
ActionButton(icon:FaIcon(
Icons.arrow_back,
),onPress:_goBack)`
And in ActionButton class
IconButton( onPressed: () { onPress!(); }, icon: icon!, color: Colors.black, ),
I am facing a strange bug when I try to add a dynamic widget to my app. When press add button screen turns completely white I can not find why it happens.
I use https://www.youtube.com/watch?v=xPW1vtDDlt4 as resource I am really new at Flutter maybe I forget something to add bu I check many times.
Here is my code,
class DynamicWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: new TextField(
decoration: new InputDecoration(hintText: 'Press + to Add Field'),
),
);
}
}
Initialization of the list.
List<DynamicWidget> listDynamic = [];
My function to add widgets to the list.
addDynamic() {
listDynamic.add(new DynamicWidget());
print("addDynamic");
setState(() {});
}
I am not sure but problem might be here,
final testText = Visibility(
child: new Column(
children: <Widget>[
new Flexible(
child: new ListView.builder(
itemCount: listDynamic.length,
itemBuilder: (_, index) => listDynamic[index],
),
),
],
),
);
Here I call my widget which I declare it to variable here.
final body = Container(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
testText,
strPhoto
],
),
),
);
And finally my button.
return Scaffold(
appBar: new AppBar(title: new Text(device_type), centerTitle: true),
drawer: Menu(),
body: body,
floatingActionButton: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
heroTag: null,
child: Icon(
Icons.add,
color: Colors.white,
),
onPressed: () {
addDynamic();
},
),
],
));
Thanks for helping me.
you must create variable Widget and add to Build Context on Scaffold
In the first step, i created a Widget called CustomTextField
import 'package:flutter/material.dart';
class CustomTextField extends StatelessWidget {
final String hint;
final TextEditingController controllers;
CustomTextField(this.hint, this.controllers);
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(top: 2.0, left: 6.0, right: 6.0, bottom: 2.0),
child: Column(
children: <Widget>[
TextField(
decoration: InputDecoration(hintText: hint),
controller: controllers),
SizedBox(height: 4.0),
],
),
);
}
}
this widget give me one text for display on hint and one controller for control textfield
And in the next step, I change the homepage class this way
i have list of Custom TextField (my widget) and display on listview using mapping list of build method
import 'package:flutter/material.dart';
import 'CustomTextField.dart';
class PageTutorial extends StatefulWidget {
#override
_PageTutorialState createState() => _PageTutorialState();
}
class _PageTutorialState extends State<PageTutorial> {
List<CustomTextField> widgets = [
CustomTextField("UserName", TextEditingController())
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: widgets.map<Widget>((widget) => widget).toList(),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
widgets.add(CustomTextField("Password", TextEditingController()));
});
},
child: Icon(Icons.add)),
);
}
}
have fun
I have a quantity that needs to be updated in the parent widget. Quantity needs to be updated when pressing + or - Icon in the child widget. I passed the callback function the the child stateless widget, but it is not working. Instead I get an error saying setstate() or markneedsbuild() called during build.
This is the parent widget
class Wash extends StatefulWidget {
#override
_WashState createState() => _WashState();
}
class _WashState extends State<Wash> {
int quantity = 0;
void updateQuantity(command) {
if (command == 'add') {
setState(() {
quantity++;
});
} else {
setState(() {
quantity--;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: OrderTile(
imgPath: 'shorts',
itemType: 'Shorts',
quantityCallBack: updateQuantity,
),
);
}
This is the child widget
class OrderTile extends StatelessWidget {
OrderTile({this.itemType, this.imgPath, this.quantityCallBack});
final String imgPath;
final String itemType;
final Function quantityCallBack;
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(12.0),
child: Row(
children: <Widget>[
Expanded(
flex: 1,
child: CircleAvatar(
backgroundImage: AssetImage('images/${imgPath}.jpg'),
radius: 30.0,
),
),
Expanded(
flex: 3,
child: _Description(
title: itemType,
),
),
GestureDetector(
onTap: quantityCallBack('add'),
child: Icon(
Icons.add,
size: 24.0,
),
),
SizedBox(
width: 14,
),
Text('1'),
SizedBox(
width: 14,
),
GestureDetector(
onTap: quantityCallBack('remove'),
child: Icon(
Icons.remove,
size: 24.0,
),
),
],
),
);
}
}
Am I doing the right thing for the function call back implementation?
You're calling your callback function in the wrong way inside your onTap callback. Change:
onTap: quantityCallBack('add'),
for
onTap: () => quantityCallBack('add'),
You can only pass a function the way you passed if they have the same type. In this case the onTap is void function() it doesn't have any arguments.
Also, your not passing the updated quantity value to your Text Widget
I have a main dart file and a settings dart file. The settings dart file is responsible for the appearance of the main dart file. Settings dart has a AppTheme class. Upon users typing on this settings page I want the main page to update.
My attempt at this was calling the class and redefining the variables based on user input. Doesnt work whether or not I use setState(). I also tried jus staying on the main page and tried changing the theme onPressed for the settings button. That didnt work either. Iconbutton update doesnt update the state either.
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:resume/settings.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Title',
theme: ThemeData.light(),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
double buttonMargin = MediaQuery.of(context).size.width / 10;
double screenWidth = MediaQuery.of(context).size.width;
double screenHeight = MediaQuery.of(context).size.height;
return MaterialApp(
home: Scaffold(
backgroundColor: AppTheme().backgroundMain,
body: SafeArea(
child: Padding(
padding: const EdgeInsets.fromLTRB(0, 20, 0, 0),
child: Column(
children: <Widget>[
Container(
height: (MediaQuery.of(context).size.height / 2.25),
child: Column(children: <Widget>[
Container(
margin: EdgeInsets.all(10),
child: Text(AppTheme().name,
style: TextStyle(
fontFamily: 'Pacifico',
fontSize: screenWidth / 8.57142857143,
color: AppTheme().nameTextColor,
fontWeight: FontWeight.bold)),
),
]),
),
Container(
height: screenHeight / 2.3,
alignment: Alignment.bottomCenter,
child: ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
Container(
margin: EdgeInsets.fromLTRB(
buttonMargin * 1.5,
buttonMargin * 3,
buttonMargin * 1.5,
buttonMargin * 3),
height: MediaQuery.of(context).size.height / 2,
width: MediaQuery.of(context).size.width / 2,
child: RaisedButton(
color: AppTheme().backgroundSecondary,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.settings,
color: Colors.white,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Settings',
style: TextStyle(color: Colors.white),
),
)
],
),
onPressed: () {
setState(() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return MySettings();
}),
);
},
);
},
),
),
],
),
),
],
),
),
),
),
);
}
}
settings.dart
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
void settings() {
runApp(MySettings());
}
class AppTheme {
var backgroundMain = Colors.red;
var backgroundSecondary = Colors.teal;
var backgroundAvatar = Colors.white;
var nameTextColor = Colors.white;
var professionTextColor = Colors.red[100];
var contactTextColor = Colors.teal;
var testPrint = print("hi");
String name = 'John Doe';
String nameFont = 'Pacifico';
String professionFont = 'roboto';
}
class MySettings extends StatefulWidget {
#override
MySettingsState createState() => MySettingsState();
}
class MySettingsState extends State<MySettings> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor: AppTheme().backgroundMain,
title: Text('Settings'),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
setState(() {
AppTheme().backgroundMain = Colors.yellow;
print(AppTheme().name);
Navigator.pop(context);
});
},
),
),
backgroundColor: Colors.teal,
body: SafeArea(
child: ListView(
children: <Widget>[
Container(
child: TextField(
onChanged: (text) {
setState(() {
AppTheme().name = text;
});
},
),
),
Container(
child: TextField(),
),
],
)),
),
);
}
}
1 Make sure to properly import
Flutter/Dart Static variables lost / keep getting reinitialized
2 You cannot alter the variables in the class. You must create an object and alter the object.
i.e I was doing
Class MyClass {
var myVariable = someValue
}
MyClass.myVariale = aDifferentValue
This did not update MyClass
What I needed to do worked once I created an object
var myClassObject = new Myclass();
myClassObject.myVariable = aDifferentValue
Now I just alter and call on myClassObject.