I am building a quiz app which has a homepage that allows you to select the subject you want to be quizzed on and then displays the questions. However, when I run my code, it doesn't display this homepage and instead displays the questions that should come if the last button was pressed. Here are the relevant snippets of my code:
[main.dart]
import 'package:flutter/material.dart';
import './page.dart';
import './s_button.dart';
class _MyAppState extends State<MyApp> {
List subjects = ["biology", "chemistry", "physics"];
bool PageIndex = true;
String selected_subject = "";
void changePage(s) {
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
selected_subject = s;
PageIndex = false;
});
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.pink[100],
body: PageIndex ? Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Quiz App", style: TextStyle(fontSize: 24)),
SizedBox(height: 30),
Text("Select a subject"),
SizedBox(height: 40),
...subjects.map((sub){
return SubjectButton(pageHandler: changePage, subject: sub);
})
]
),
)
: QuizPage(selected_subject)
)
);
}
}
[s_button.dart]
import 'package:flutter/material.dart';
class SubjectButton extends StatelessWidget {
final Function pageHandler;
final String subject;
const SubjectButton({Key? key, required this.pageHandler, required this.subject}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: [
Container(
width: 120,
height: 60,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.pink,
elevation: 5,
),
onPressed: pageHandler(subject),
child: Text(subject)
)
),
SizedBox(height: 20)
],
);
}
}
When I run this however, QuizPage() is displayed with the question for physics, which is the last button as per my initial list. Somehow, my PageIndex is being set to false and my selected_subject is being set to "physics" before I even have a chance to click on the buttons. What is going wrong?
onPressed: pageHandler(subject) means, while building the widget, it will be called.
To call on runtime use
onPressed:()=> pageHandler(subject),
use
onPressed:()=> pageHandler(subject),
instead of
onPressed: pageHandler(subject),
Check the onPressed line, you're directly executing the function and assigning the return to the onPressed but not binding it.
onPressed: pageHandler(subject),
You may meant to do the following:
onPressed: () => pageHandler(subject),
Like this, it won't get automatically executed at first :)
Related
I've been banging my head against this for several days now, so I really hope somebody can shed some light.
I need a custom button that looks a certain way and runs simple animations on itself (really simple, like cross-fading to a different color and then back upon being pressed). I have written such a button, and it suits me perfectly in terms of looks and behavior.
But! In order for the animations to work, I had to derive my button from the StatefulWidget class. The problem is this: no matter what I do, I can't get the page to rebuild the button anew, with updated parameters.
I have implemented a simple on/off switch with two buttons to show what I mean. On this page I have two buttons: "Drop anchor" and "Retract anchor". I want only one button to be enabled at any given time. Pressing one of the buttons should disable it and enable the other one. But that doesn't happen!
I have put some text on the screen to illustrate that the page does indeed update on setState(). The text is governed by the same variable the buttons are. But for some reason Flutter updates the text, but not the buttons.
I've tried just about every solution I could find here.
Here's what I've tried:
I've added keys to my buttons and button's child. It helped with getting the AnimatedSwitch to do what I needed, but not with UI rebuilds.
I've tried dispatching notifications and calling setState() upon receiving them. No effect.
I've put a floating button to manually call setState() on the page to make sure it's called from the page widget, and not inside the button state. No effect.
I've tried wrapping the whole app in an AppBuilder, as was suggested on one of the threads here, and rebuilding the whole app on the press of the buttons, which works even for changing the Theme of the app. But it doesn't update my stateful buttons... Duh!
I've implemented the same example using ordinary stateless buttons, and it works exactly as I expect it to. So I'm about 75% sure the problem is that my custom button has its own state.
What else can I try?
Thank you for reading!
My example page code:
class _MyHomePageState extends State<MyHomePage> {
bool anchorIsDown = false;
void anchorUp() {
anchorIsDown = false;
setState(() {});
}
void anchorDown() {
anchorIsDown = true;
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
flex: 3,
child: Container(
child: Center(
child: anchorIsDown ? Text('Anchor is down') : Text('Anchor is up'),
)
),
),
Flexible(
flex: 1,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
StatefulButton(
keyString: 'anchor_drop',
opacity: 0.2,
visible: true,
onPressedColor: Colors.greenAccent,
onPressed: !anchorIsDown ? anchorDown : null,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Drop anchor')
],
),
),
Expanded(child: SizedBox(width: 0, height: 0)),
StatefulButton(
keyString: 'anchor_retract',
opacity: 0.2,
visible: true,
onPressedColor: Colors.greenAccent,
onPressed: anchorIsDown ? anchorUp : null,
child: Text('Retract anchor'),
),
],
),
),
),
Flexible(
flex: 3,
child: Container(),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () { setState(() {}); },
),
);
}
}
My stateful button class:
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:math';
class StatefulButton extends StatefulWidget {
late final String keyString;
late final bool visible;
late final double? opacity;
late final VoidCallback? onPressed;
late final Color? onPressedColor;
late final Widget child;
StatefulButton(
{required this.keyString,
this.visible = true,
this.opacity = 0.05,
this.onPressedColor,
this.onPressed,
required this.child})
: super(key: Key(keyString));
#override
State createState() => StatefulButtonState();
}
class StatefulButtonState extends State<StatefulButton> {
late final bool visible;
late double? opacity;
VoidCallback? onPressed;
late final Color? onPressedColor;
late final Widget child;
bool isAnimating = false;
#override
void initState() {
visible = widget.visible;
opacity = widget.opacity;
onPressed = widget.onPressed;
onPressedColor = widget.onPressedColor;
child = widget.child;
isAnimating = false;
super.initState();
}
void onPressedWrapper() async {
isAnimating = true;
setState(() {});
if (onPressed != null) onPressed!();
await Future.delayed(Duration(milliseconds: 200));
isAnimating = false;
setState(() {});
}
Widget buildChildContainer() {
if (!isAnimating)
return Container(
key: Key(widget.keyString + '_normalstate'),
color: Theme.of(context).buttonColor,
child: Center(
child: child,
),
);
return Container(
key: Key(widget.keyString + '_pressedstate'),
decoration: BoxDecoration(
color: onPressedColor,
borderRadius: BorderRadius.all(Radius.circular(3.0))
),
child: Center(
child: child,
),
);
}
//If the button is enabled, it should provide tap feedback.
//This function will build such a button using AnimatedCrossFade
Widget buildEnabledButton() {
return OutlinedButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.all(1)),
side: MaterialStateProperty.all(BorderSide(
color: Theme.of(context).dividerColor,
width: 1,
style: BorderStyle.solid)),
enableFeedback: false,
minimumSize: MaterialStateProperty.all(Size(150, 50)),
elevation: MaterialStateProperty.all(4.0),
shadowColor: MaterialStateProperty.all(Theme.of(context).shadowColor),
overlayColor: MaterialStateProperty.all(Colors.transparent),
backgroundColor:
MaterialStateProperty.all(Theme.of(context).buttonColor)),
onPressed: onPressed == null ? onPressed : onPressedWrapper,
child: AnimatedSwitcher(
duration: Duration(milliseconds: 200),
child: buildChildContainer(),
switchInCurve: Curves.bounceOut,
switchOutCurve: Curves.easeOut,
),
);
}
Widget buildDisabledButton() {
if (!visible) opacity = 0;
return Stack(
children: [
OutlinedButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.all(1)),
side: MaterialStateProperty.all(BorderSide(
color: Theme.of(context).dividerColor.withOpacity(sqrt(opacity!)),
width: 1,
style: visible ? BorderStyle.solid : BorderStyle.none)),
enableFeedback: false,
minimumSize: MaterialStateProperty.all(Size(1000, 1000)),
elevation: MaterialStateProperty.all(4.0 * opacity!),
shadowColor: visible
? MaterialStateProperty.all(
Theme.of(context).shadowColor.withOpacity(opacity!))
: MaterialStateProperty.all(Colors.transparent),
overlayColor: MaterialStateProperty.all(Colors.transparent),
backgroundColor: visible
? MaterialStateProperty.all(Theme.of(context)
.buttonColor
.withOpacity(sqrt(opacity!) / 2))
: MaterialStateProperty.all(Colors.transparent)),
onPressed: visible ? onPressed : null,
child: buildChildContainer()),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(3)),
color: Theme.of(context).canvasColor.withOpacity(1 - opacity!),
),
child: SizedBox(
child: Center(),
))
],
);
}
#override
Widget build(BuildContext context) {
return Expanded(
child: ((onPressed != null) && visible)
? buildEnabledButton()
: buildDisabledButton());
}
}
I'm doing a project in Flutter in which I'm getting live bit rate using a API and I'm getting my rate but can't display on my screen its say it null..! code below:
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'coin_data.dart';
import 'dart:io' show Platform;
import 'networking.dart';
class PriceScreen extends StatefulWidget {
#override
_PriceScreenState createState() => _PriceScreenState();
}
class _PriceScreenState extends State<PriceScreen> {
BitNetwork bitNetwork = BitNetwork('$BitCoinURL/BTC/USD?apikey=$BitCoinKey');
int bitRate;
void getCurrentBitRate() async {
dynamic bitData = await bitNetwork.getData();
double temp = bitData['rate'];
bitRate = temp.toInt();
print(bitRate);
}
String selectedCurrency = 'USD';`enter code here`
#override
Widget build(BuildContext context) {
getCurrentBitRate();
return Scaffold(
appBar: AppBar(
title: Text('Coin Ticker'),
),`enter code here`
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(18.0, 18.0, 18.0, 0),
child: Card(
color: Colors.lightBlueAccent,
elevation: 5.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
child: Padding(
padding: EdgeInsets.symmetric(vertical: 15.0, horizontal: 28.0),
child: Text(
'1 BTC = $bitRate USD',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0,
color: Colors.white,
),
),
),
),
),
Container(
height: 150.0,
alignment: Alignment.center,
padding: EdgeInsets.only(bottom: 30.0),
color: Colors.lightBlue,
child: Platform.isIOS ? iOSPicker() : androidDropdown()),
],
),
);
}
}
answer in console:
I/flutter (14181): 47131
I/flutter (14181): 47131
I/flutter (14181): 47129
output on screen is = 1 BTC = null USD. => ????
You need to wait for currency loading, wrap your widget to FutureBuilder:
Future<int> getCurrentBitRate() async {
dynamic bitData = await bitNetwork.getData();
double temp = bitData['rate'];
return temp.toInt();
}
// build method
child: FutureBuilder<int>(
future: getCurrentBitRate(),
builder (context, snapshot) {
if (snapshot.hasData) {
final bitRate = snapshot.data;
return Column(
// Your column here.
);
}
return CircularProgressIndicator();
}
),
Also, you can find more information about how to work with async features here and read more about FutureBuilder here.
The problem is you're not awaiting getCurrentBitRate() and you are also calling it in your build method. Only UI code should be in the build method. What I recommend you do is override initState() and call it in there (Still can't await it, but it will be called before build);
#override
initState(){
getCurrentBitRate();
super.initState();
}
This will help with your issue, but it's not the best solution. I recommend looking up tutorials on some external state management system, such as BLoC, Provider and/or RxDart. This will make situations like this much easier to debug.
The bitRate value is null because you are calling it in build function & your method getCurrentBitRate() is an async method, which means that the method will wait to get the value but till then your build method would already finish rendering the widgets with bitRate value still null.
There are multiple ways to fix this but the one I would recommend is as follows:
Call your method getCurrentBitRate() in initState method & remove it from the build function as it is the first method that runs in your widget & use setState so that updated value of bitRate is shown in your widget.
class _PriceScreenState extends State<PriceScreen> {
BitNetwork bitNetwork = BitNetwork('$BitCoinURL/BTC/USD?apikey=$BitCoinKey');
int bitRate;
#override
void initState() {
super.initState();
getCurrentBitRate(); // Call it in initState & use setState
}
void getCurrentBitRate() async {
dynamic bitData = await bitNetwork.getData();
double temp = bitData['rate'];
bitRate = temp.toInt();
print(bitRate);
if (mounted) { // <--- mounted property checks whether your widget is still present in the widget tree
setState((){}); // Will update the UI once the value is retrieved
}
}
String selectedCurrency = 'USD';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Coin Ticker'),
),`enter code here`
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(18.0, 18.0, 18.0, 0),
child: Card(
color: Colors.lightBlueAccent,
elevation: 5.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
child: Padding(
padding: EdgeInsets.symmetric(vertical: 15.0, horizontal: 28.0),
child: Text(
'1 BTC = $bitRate USD',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0,
color: Colors.white,
),
),
),
),
),
Container(
height: 150.0,
alignment: Alignment.center,
padding: EdgeInsets.only(bottom: 30.0),
color: Colors.lightBlue,
child: Platform.isIOS ? iOSPicker() : androidDropdown()),
],
),
);
}
}
It's null because when build() is called, getCurrentBitRate() didn't complete it's job yet.
For those operations FutureBuilder is one of the best widget. It just needs a future, and a builder to declare what to do after the data received.
// CHANGE TO FUTURE STYLE
Future<Int> getCurrentBitRate() async {
dynamic bitData = await bitNetwork.getData();
double temp = bitData['rate'];
bitRate = temp.toInt();
print(bitRate);
return bitRate;
}
Then change build structure to this
// DECLARE A FUTURE FOR getCurrentBitRate()
Future _future;
initState(){
_future = await getCurrentBitRate();
super.initState();
}
#override
Widget build(BuildContext context) {
// getCurrentBitRate(); REMOVE THIS LINE
return FutureBuilder(
future: _future,
builder: (context, snapshot) {
if(snapshot.hasData){
// YOUR DATA IS READY
double temp = snapshot.data['rate'];
// JUST CONTINUE REST OF ORIGINAL CODE BELOW
return Scaffold(
appBar: AppBar(
title: Text('Coin Ticker'),
),
...
}
}
);
I want to have a Settings screen where I can choose a color to be returned to the first screen.
I can't get the first screen to update when the Setting screen is closed.
I'm using the Provider as a change notifier. But I can't see how to trigger the update of the first screen. The third button creates an event which updates the screen, but can this be done automatically?
What am I missing...?
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
Color bgColor = Colors.yellow[100];
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: MyHomeScreen());
}
}
class MyHomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => ColorModel()),
],
child: Consumer<ColorModel>(builder: (context, colorModel, child) {
return Scaffold(
appBar: AppBar(title: Text('Thanks for your help :)')),
body: Container(
constraints: BoxConstraints.expand(),
color: bgColor,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('Change background color on this screen'),
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.green[600],
),
child:
Text('Button1', style: TextStyle(color: Colors.white)),
onPressed: () {
var result = Navigator.push(
context, MaterialPageRoute(builder: (context) => Screen2()));
print('>>> Button1-onPressed completed, result=$result');
},
),
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.green[600],
),
child:
Text('Choose a colour', style: TextStyle(color: Colors.white)),
onPressed: () {
asyncButton(context);
print('>>> Screen1 Button-onPressed completed');
},
),
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.green[600],
),
child:
Text('Now try me', style: TextStyle(color: Colors.white)),
onPressed: () {
colorModel.notifyListeners();
},
),
],
),
),
);
}),
);
}
void asyncButton(BuildContext context) async {
var result = await Navigator.push(
context, MaterialPageRoute(builder: (context) => Screen2()));
print('>>> asyncButton completed: result = $result');
bgColor = result;
}
}
class ColorModel with ChangeNotifier {
void updateDisplay() {
notifyListeners();
}
}
class Screen2 extends StatelessWidget {
int _value;
List<String> names = ['Red', 'Green', 'Blue'];
List<Color> colors = [Colors.red[100], Colors.green[100], Colors.blue[100]];
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => ColorModel()),
],
child: Scaffold(
appBar: AppBar(
toolbarHeight: 80,
backgroundColor: Colors.blue,
title: Center(child: Text('Screen2')),
),
body: Container(
constraints: BoxConstraints.expand(),
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Consumer<ColorModel>(builder: (context, colorModel, child) {
return DropdownButton(
value: _value,
hint: Text("Select a color"),
focusColor: Colors.lightBlue,
onChanged: (int value) {
Navigator.pop(context, colors[value]);
},
items: [
DropdownMenuItem(value: 0, child: Text(names[0])),
DropdownMenuItem(value: 1, child: Text(names[1])),
DropdownMenuItem(value: 2, child: Text(names[2])),
],
);
}),
],
),
),
),
);
}
}
Navigator.push is tricky to use with Provider. It causes a lot of "Could not find the correct Provider above this Navigator Widget" errors. I've explained why in this answer to a related question.
Here's a quick overview of your situation:
Provider Scope
Architecture in question code:
MaterialApp
> provider(Screen A)
> provider(Screen B)
Architecture in solution below:
provider(MaterialApp)
> Screen A
> Screen B
Here's your code sample, shortened up, working with Provider, updating the background color on Page 1 from the Page 2.
I've put comments throughout the code to explain changes.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// - global var removed -
// Color bgColor = Colors.yellow[100];
void main() {
runApp(ProviderApp());
}
class ProviderApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
/// Define your Provider here, above MaterialApp
return ChangeNotifierProvider(
create: (context) => ColorModel(),
child: MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
home: ScreenA()
),
);
}
}
class ScreenA extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Thanks for your help :)')),
body: Container(
constraints: BoxConstraints.expand(),
//
// color: bgColor // - global var removed -
color: Provider.of<ColorModel>(context).bgColor,
// ↑ use your Provider state-stored value here ↑
//
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('Change background color on this screen'),
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.green[600],
),
child: Text('Go Screen B', style: TextStyle(color: Colors.white)),
// Navigator.push returns a Future, must async/await to use return value
onPressed: () async {
var result = await Navigator.of(context).push(
MaterialPageRoute(builder: (context) => ScreenB()));
// note that this context is not Screen A context, but MaterialApp context
// see https://stackoverflow.com/a/66485893/2301224
print('>>> Button1-onPressed completed, result=$result');
},
),
],
),
),
);
}
}
/// This is your state object. Store your state here.
/// Create this once and use anywhere you need. Don't re-create this unless
/// you want to wipe out all state data you were holding/sharing.
class ColorModel with ChangeNotifier {
// color is the state info you want to store & share
Color bgColor = Colors.yellow[100]; // initialized to yellow
/// Update your state value and notify any interested listeners
void updateBgColor(Color newColor) {
bgColor = newColor;
notifyListeners();
}
/// - removed - replaced with updateBgColor ↑
/*void updateDisplay() {
notifyListeners();
}*/
}
class ScreenB extends StatelessWidget {
// all fields in StatelessWidgets should be final
//final int value; // this value isn't needed
final List<String> names = ['Red', 'Green', 'Blue'];
final List<Color> colors = [Colors.red[100], Colors.green[100], Colors.blue[100]];
#override
Widget build(BuildContext context) {
/// Instantiating your model & giving it to Provider to should only happen once per
/// Widget Tree that needs access to that state. e.g. MaterialApp for this solution
/// The state object & Provider below was repeated & has been commented out / removed.
/// This was wiping out any previously stored state and creating a new Provider / Inherited scope
/// to all children.
/*return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => ColorModel()),
],
child: ,
);*/
// - end of duplicate Provider removal -
return Scaffold(
appBar: AppBar(
title: Text('Screen2'),
),
body: Container(
alignment: Alignment.center,
child: Consumer<ColorModel>(builder: (context, colorModel, child) {
return DropdownButton(
//value: value, // this value isn't needed
hint: Text("Select a color"),
onChanged: (int value) {
colorModel.updateBgColor(colors[value]);
Navigator.pop(context, colors[value]);
},
items: [
DropdownMenuItem(value: 0, child: Text(names[0])),
DropdownMenuItem(value: 1, child: Text(names[1])),
DropdownMenuItem(value: 2, child: Text(names[2])),
],
);
}),
),
);
}
}
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
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;
});
},
);
}
}