I'm trying to make a calculator app using flutter where instead of taking input through the keyboard I want to take input through some buttons. The issue comes when I press a button but it does not display the corresponding data in the Text widget above.
All my classes are stateless except for the first MyApp class, which is Stateful.
I tried by creating a general variable outside all the classes and using that to transfer text from the button class to the display class but that did not work.
The general variable is "_calcText"
class DisplayAnswer extends StatelessWidget {
final String _text;
DisplayAnswer(this._text);
#override
Widget build(BuildContext context) {
return Expanded(
flex: 2,
child: Material(
color: Colors.greenAccent,
child: Ink(
padding: EdgeInsets.all(10.0),
child: Center(
child: Container(
constraints: BoxConstraints.expand(),
decoration: BoxDecoration(border: Border.all(color: Colors.black, width: 5.0), color: Colors.white),
child: Text(_text,style: TextStyle(fontSize: 50.0), textAlign: TextAlign.center,),
),
),
),
),
);
}
}
class NumButtons extends StatelessWidget {
final String _number;
NumButtons(this._number);
#override
Widget build(BuildContext context) {
return FlatButton(
onPressed: () {
_calcText = _calcText + _number;
print(_calcText);
DisplayAnswer(_calcText);
} ,
child: Text(_number.toString(), style: TextStyle(fontSize: 30.0),),
color: Colors.white
);
}
}
I want to display the value of _calcText in the Text widget of DisplayAnswer. I want _calcText to also change as other buttons are clicked, ie; if 2 is clicked Text should only display 2, if 5 is clicked after that it should display 25
The full code is here:
https://drive.google.com/open?id=1C4MLAkjowloicbjBP_uV8BfpPzhz4Yxf
Use Statefull widget insted of StatelessWidget.
Call the setState() method on onPressed function, after adition operation. It will build your widget with a new value.
On DisplayAnswer, you have to make a function to increment the value, than pass this function as parameter to NumButtons.
Pass a callback Function to NumButtons, like:
class NumButtons extends StatelessWidget {
final String _number;
final Function callback;
...
Related
Im new to flutter. I would like to ask a question about my code. I have take a look on youtube and some google tutorial on this inkwell and on tap function to open new class activity on flutter.But the result is, when the image is tapped it open different image screen but they share same class file.
How can I have a separate page for different image click. For example,
I have five image in my flutter carousel slider.
Image 1 will open sliderpage 1. Image 2 will open sliderpage 2 and so on.Means they are on separate page instead of different image open same page but only show different images. Im trying this tutorial but they do have same page but different images displayed after on tap event is called. url https://www.youtube.com/watch?v=l9XOUoJsdy4
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
Widget image_carousel = new Container(
height: 345.0,
child: new Carousel(
boxFit: BoxFit.fill,
images: [
AssetImage('assets/s7.jpg'),
AssetImage('assets/s3.jpg'),
AssetImage('assets/s5.jpg'),
AssetImage('assets/s2.jpg'),
AssetImage('assets/s4.jpg'),
],
autoplay: true,
animationCurve: Curves.fastOutSlowIn,
animationDuration: Duration(milliseconds: 500),
dotColor: Colors.red[50],
dotSize: 4.0,
indicatorBgPadding: 2.0,
),
);
return Scaffold(
body: new Column(
children: <Widget>[
image_carousel,
//grid view
Container(
height:163.0,
child: Products(),
)
],
),
);
}
}
On this code, this code just display carousel image without any event on click is done , I was expecting to have different page routing by on tap event is happen when image assets is clicked and navigate to different pages.
First of all, you need to install carousel_slider, then create two screens:
The first one will contain carousel_slider when you click on the image it will navigate to the second screen and passing image URL of the image you clicked on, To have on tap event you need to wrap you Image widget with GestureDetector
import 'package:flutter/material.dart';
import 'package:carousel_slider/carousel_slider.dart';
import './image_screen.dart';
void main() => runApp(MaterialApp(home: Demo()));
class Demo extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<Demo> {
#override
Widget build(BuildContext context) {
Widget image_carousel = new Container(
height: 345.0,
child: CarouselSlider(
height: 400.0,
items: [
'http://pic3.16pic.com/00/55/42/16pic_5542988_b.jpg',
'http://photo.16pic.com/00/38/88/16pic_3888084_b.jpg',
'http://pic3.16pic.com/00/55/42/16pic_5542988_b.jpg',
'http://photo.16pic.com/00/38/88/16pic_3888084_b.jpg'
].map((i) {
return Builder(
builder: (BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.symmetric(horizontal: 5.0),
decoration: BoxDecoration(color: Colors.amber),
child: GestureDetector(
child: Image.network(i, fit: BoxFit.fill),
onTap: () {
Navigator.push<Widget>(
context,
MaterialPageRoute(
builder: (context) => ImageScreen(i),
),
);
}));
},
);
}).toList(),
));
return Scaffold(
body: new Column(
children: <Widget>[
image_carousel,
],
),
);
}
}
The second screen will contain only the image you clicked on:
import 'package:flutter/material.dart';
class ImageScreen extends StatefulWidget {
final String url;
ImageScreen(this.url);
#override
_MyImageScreen createState() => _MyImageScreen(url);
}
class _MyImageScreen extends State<ImageScreen> {
final String url;
_MyImageScreen(this.url);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ImageScreen'),
),
body: Image.network(url, width: double.infinity));
}
}
I have a widget with a TextField that I'm initializing from a StreamBuilder, trying to use the bloc pattern. A Contact model is coming in through the stream. This is working to initially populate the TextField. My question is about reading the value after the user updates the TextField and then presses the Save button. How do I read the value from the TextField. I've included a simple example of what I'm trying to do.
void getTextValues() {
//???
}
#override
Widget build(BuildContext context) {
return StreamBuilder<Contact>(
stream: bloc.getContact,
builder: (context, contact) {
return Column(
children: <Widget>[
TextField(
controller: TextEditingController(text: contact.data.name),
),
new RaisedButton(
padding: const EdgeInsets.all(8.0),
textColor: Colors.white,
color: Colors.blue,
onPressed: getTextValues,
child: new Text("Save"),
),
],
);
},
);
}
I think I could declare a TextEditingController and assign that to the controller property but I don't see a way to give that an initial value from the StreamBuilder. Am I missing something there?
TextEditingController nameController = TextEditingController();
....
controller: nameController,
I think I could declare a TextEditingController and assign that to the
controller property but I don't see a way to give that an initial
value from the StreamBuilder.
There is a way. Change your builder code to this:
builder: (context, contact) {
nameController.value = TextEditingValue(text: contact.data.name);
return Column(
children: <Widget>[
TextField(
controller: nameController,
),
new RaisedButton(
padding: const EdgeInsets.all(8.0),
textColor: Colors.white,
color: Colors.blue,
onPressed: getTextValues,
child: new Text("Save"),
),
],
);
},
To assign TextEditingController a default value, use
TextEditingController _controller = TextEditingController(text: "Default value");
And to retrieve the value from a controller you can use
_controller.value
I'm trying to test the VoidCallback so I created the main file, that have a function called from a flat button in the widget, which is in a separate file, but did not work.
main.dart
import 'package:flutter/material.dart';
import 'controller_test.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Retrieve Text Input',
home: MyCustomForm(),
);
}
}
// Define a Custom Form Widget
class MyCustomForm extends StatefulWidget {
#override
_MyCustomFormState createState() => _MyCustomFormState();
}
class _MyCustomFormState extends State<MyCustomForm> {
final myController = TextEditingController();
#override
void initState() {
super.initState();
myController.addListener(_printLatestValue);
}
_printLatestValue() {
print("Second text field: ${myController.text}");
}
_test() {
print("hi there");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Retrieve Text Input'),
),
body: Con(_test, myController)
);
}
}
controller_test.dart
import 'package:flutter/material.dart';
class Con extends StatelessWidget {
Con(this.clickCallback, this.tc);
final TextEditingController tc;
final VoidCallback clickCallback;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
onChanged: (text) {
print("First text field: $text");
},
),
TextField(
controller: tc,
),
FlatButton(
onPressed: () => clickCallback,
child: Text("click me"),
)
],
),
);
}
}
When I click the FlatButton in the widget, nothing is happening, I was expecting hi there to be printed
there are two options here.
onPressed: () => fun() is like onPressed argument is an anonymous method that calls fun.
onPressed: fun is like onPressed argument is the function fun.
I just found it in another answer here
I was missing the (), so correct call is:
FlatButton(
onPressed: () => clickCallback(),
child: Text("click me"),
)
You can get callback from stateless widget to your current page by using Voidcallback class.
Just add this custom widget in your current page (widget.build()
function)
DefaultButton(
buttonText: Constants.LOGIN_BUTTON_TEXT,
onPressed: () => validateInputFields(),
size: size,
);
My custom widget class is
class DefaultButton extends StatelessWidget {
DefaultButton({this.buttonText, this.onPressed, this.size});
final String buttonText;
final VoidCallback onPressed;
final Size size;
#override
Widget build(BuildContext context) {
return MaterialButton(
minWidth: size.width,
onPressed: () => onPressed(), //callback to refered page
padding: EdgeInsets.all(0.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(DEFAULT_BORDER_RADIUS),
),
child: Ink(
width: size.width,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: <Color>[
SECONDARY_COLOR_SHADE_LITE,
SECONDARY_COLOR_SHADE_DARK,
],
),
borderRadius: BorderRadius.circular(DEFAULT_BORDER_RADIUS),
),
child: Padding(
padding: EdgeInsets.only(left: 20, right: 20, top: 12, bottom: 12),
child: Text(
buttonText,
style: Theme.of(context).textTheme.button,
textAlign: TextAlign.center,
)),
),
);
}
}
In your callback make sure to call setState if any variables change. I repopulate an list in my provider and then use assign that list to the variable list which I convert into a list of cards. The variable list needs state refreshed to see it.
I made a custom input widget by wrapping Textfield in some widgets and so on. Now I can't reach the onChanged property directly from the custom widget. I tried making a property in my custom widget but couldn't implement it properly. I googled passing variables between widgets and it seems a hard thing to do. Any simple solution?
class Input extends StatelessWidget {
final String text;
final TextInputType type;
final int maxLength;
final bool password;
final String label;
final IconData icon;
final double padding;
final Function onChanged;
final ColorSwatch color;
Input(
{this.type = TextInputType.text,
#required this.text,
this.maxLength,
this.icon,
this.padding = 0.0,
this.password = false,
#required this.onChanged,
this.label,
this.color});
final String value = '';
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(padding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.all(3.0),
child: Text(
text + ":",
style: TextStyle(fontSize: 15.0, color: color),
),
),
Container(
padding: EdgeInsets.all(3.0),
width: 300.0,
child: TextField(
obscureText: password,
decoration: InputDecoration(
labelText: label,
icon: Icon(icon),
),
maxLength: maxLength,
keyboardType: type,
textInputAction: TextInputAction.next,
onChanged: onChanged,
),
),
],
),
);
}
}
You need to provide onChanged and other events in your custom widget, even if you are just providing these to the underlying TextField. In other words, you need to pass the onChanged function down through your custom widget.
For example:
MyAwesomeTextField extends StatelessWidget {
/// Callback for being notified of changes to the text field
/// This should have a proper type, I'm just using Function for simplicity
final Function onChanged;
// Make the onChanged property settable through the constructor
MyAwesomeTextField({this.onChanged});
Widget build(BuildContext context) {
// Construct your widget tree, and pass your onChanged function through
return TextField(onChanged: this.onChanged);
}
}
Then when you're using it, your custom widget will have an onChanged event:
...
MyCustomWidget(onChanged: (value) => print(value) )
...
Creating an example to learn callbacks in Flutter. It's a simple programme to increase the counter onTap of a GestureDetetor But, the callback method is not working. The count increase on hot reload but not on tap. Below is the code with comments.
class BoxState extends State<ChangeBoxState>{
int _counter = 0;
//Callback method changes the state onTap of GestureDetector widget. It is not calling onTap.
increaseCount(){
setState(() {
++_counter;
print(_counter);
});
}
#override
Widget build(BuildContext context) {
// Passing the callback method,"increaseCount()" to stateless class where GestureDetector is defined.
return BoxWidget(onPressed: increaseCount(), counter: _counter,);
}
}
Stateless class:
class BoxWidget extends StatelessWidget{
BoxWidget({this.onPressed, this.counter});
final VoidCallback onPressed;
final int counter;
#override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
decoration: BoxDecoration(color: Colors.blue[500]),
child: Column(
children: <Widget>[Center(child: Text('Hello, world!')),
GestureDetector(
onTap: onPressed, //Passing onPressed to onTap.
child: Container(
margin: const EdgeInsets.only(left:0.0, top:200.0, right:0.0, bottom:0.0),
height: 200.0,
width: 200.0,
decoration: BoxDecoration(
color: Colors.teal[200],
border: Border.all(color: Colors.yellow, width: 10.0, style: BorderStyle.solid),
borderRadius: BorderRadius.all(Radius.circular(20.0)),
),
child: Center(child: Text(counter.toString())),
),
),
],
),
);
}
}
remove the the bracket in increaseCount() because using the bracket you are creating an instance of your VoidCallback and this will run one time only so try this
return BoxWidget(onPressed: increaseCount, counter: _counter,);
You should provide the reference of increaseCount to the onPressed callback.
Here you are assigning increaseCount() (check braces) to the callback which first call increaseCount() function and its return value will be assigned to the onPressed. Thats why it is only incrementing one time on hot reload.
return BoxWidget(onPressed: increaseCount, counter: _counter,);