Flutter, calling a function inside the button - flutter

I am new to flutter and I make some practices. I have a StatelessWidget called doChange and makeChange and one StatefulWidget. This class which is statefulwidget I made child of the home page of the app also. But, I think that it is unnecessary to define here. My purpose in this case is that, I want to change the state of the button make open,make closed and at the same time the text open and close will also change. I think that class changeText has not problem but in makeChange class I have some trouble with creating constructor and function to call into onPress. The states do not change. How can i solve this or is that any way to do this without function ?
class changeText extends StatelessWidget{
final doChange;
changeText({#required this.doChange});
#override
Widget build(BuildContext context){
return Container(
//some codes
//some codes
child: doChange ? Text("open") : Text("close"),
);
}
}
class makeChange extends StatelessWidget{
final changeState;
makeChange({#required this.changeState}); // I want to add constructor here lets say onPressButton
whenPressed(){ // I want to create a function with the that constructor that I have add.
}
#override
Widget build(BuildContext context){
return Container(
//some codes
//
child: Column(
children: [
MaterialButton(
//some codes
//
onPressed: () {} // Here I want to call a function when press the button.
child: changeState ? Text("make open") : Text("make close"),
),
],
),
);
}
}
class Mainarea extends StatefulWidget{
#override
_MainareaState createState() => _mainAreaState();
}
class _MainareaState extends State<Mainarea> {
bool isChange= false;
#override
Widget build(BuildContext context){
return Container(
//some codes
//
child: Column(
children: <Widget>[
changeText(doChange: !this.isChange),
makeChange(changeState: !this.isChange),
],
),
);
}
}

I just added a final Function(bool) callback as a parameter, which can be called inside from the stateless widget, and returns to the calling function. From there you can call setState
class changeText extends StatelessWidget {
final bool doChange;
changeText({#required this.doChange});
#override
Widget build(BuildContext context) {
return Container(
//some codes
//some codes
child: doChange ? Text("open") : Text("close"),
);
}
}
class makeChange extends StatelessWidget {
final bool changeState;
final Function(bool) callback;
makeChange(
{#required
this.changeState,
#required
this.callback}); // You can rename this to onPressed or whatever
#override
Widget build(BuildContext context) {
return Container(
//some codes
//
child: Column(
children: [
MaterialButton(
//some codes
//
onPressed: () => callback( changeState),
child: changeState ? Text("make close") : Text("make open"), //I had to swap these around to make the text correct
),
],
),
);
}
}
class Mainarea extends StatefulWidget {
#override
_MainareaState createState() => _MainareaState();
}
class _MainareaState extends State<Mainarea> {
bool isChange = false;
#override
Widget build(BuildContext context) {
return Container(
//some codes
//
child: Column(
children: <Widget>[
changeText(doChange: !this.isChange),
makeChange(
changeState: !this.isChange,
callback: (bool val) {
setState(() => isChange = val); //this is your function that returns and resetst the value in the parent widget
},
),
],
),
);
}
}

Related

How to pass a GlobalKey through Stateless Widget Children

I'm trying to create a custom menu bar in my app. Right now, the biggest issue I'm having is passing a state for when it's expanded to it's children after a setState occurs.
I thought about inheritance, but from what I've tried all inheritance needs to be in-line. I can't create a widget where the children [] are fed into the constructor on an ad-hoc basis.
My current approach is to use a GlobalKey to update the State of the children widgets being inserted into the StateFul while updating them directly.
The children for my MenuBar are declared as:
List<MenuBarItem> menuItems;
MenuBarItem is an abstract interface class that I intend to use to limit the widgets that can be fed in as menuItems to my MenuBar.
abstract class iMenuItem extends Widget{}
class MenuBarItem extends StatefulWidget implements iMenuItem{
At some iterations of this script, I had a bool isExpanded as part of the iMenuItem, but determined it not necessary.
Here is my code at its current iteration:
My Main:
void main() {
// runApp(MainApp());
//runApp(InherApp());
runApp(MenuBarApp());
}
class MenuBarApp extends StatelessWidget{
#override
Widget build(BuildContext context){
return MaterialApp(
home: Scaffold(
body: MenuBar(
menuItems: [
// This one does NOT work and is where I'm trying to get the
// value to update after a setState
MenuBarItem(
myText: 'Outsider',
),
],
),
),
);
}
}
My Code:
import 'package:flutter/material.dart';
/// Primary widget to be used in the main()
class MenuBar extends StatefulWidget{
List<MenuBarItem> menuItems;
MenuBar({
required this.menuItems,
});
#override
State<MenuBar> createState() => MenuBarState();
}
class MenuBarState extends State<MenuBar>{
bool isExpanded = false;
late GlobalKey<MenuBarContainerState> menuBarContainerStateKey;
#override
void initState() {
super.initState();
menuBarContainerStateKey = GlobalKey();
}
#override
Widget build(BuildContext context){
return MenuBarContainer(
menuItems: widget.menuItems,
);
}
}
class MenuBarContainer extends StatefulWidget{
List<MenuBarItem> menuItems;
late Key key;
MenuBarContainer({
required this.menuItems,
key,
}):super(key: key);
#override
MenuBarContainerState createState() => MenuBarContainerState();
}
class MenuBarContainerState extends State<MenuBarContainer>{
bool isExpanded = false;
#override
void initState() {
super.initState();
isExpanded = false;
}
#override
Widget build(BuildContext context){
List<Widget> myChildren = [
ElevatedButton(
onPressed: (){
setState((){
this.isExpanded = !this.isExpanded;
});
},
child: Text('Push Me'),
),
// This one works. No surprise since it's in-line
MenuBarItem(isExpanded: this.isExpanded, myText: 'Built In'),
];
myChildren.addAll(widget.menuItems);
return Container(
child: Column(
children: myChildren,
),
);
}
}
/// The item that will appear as a child of MenuBar
/// Uses the iMenuItem to limit the children to those sharing
/// the iMenuItem abstract/interface
class MenuBarItem extends StatefulWidget implements iMenuItem{
bool isExpanded;
String myText;
MenuBarItem({
key,
this.isExpanded = false,
required this.myText,
}):super(key: key);
#override
State<MenuBarItem> createState() => MenuBarItemState();
}
class MenuBarItemState extends State<MenuBarItem>{
#override
Widget build(BuildContext context){
GlobalKey<MenuBarState> _menuBarState;
return Row(
children: <Widget> [
Text('Current Status:\t${widget.isExpanded}'),
Text('MenuBarState GlobalKey:\t${GlobalKey<MenuBarState>().currentState?.isExpanded ?? false}'),
Text(widget.myText),
],
);
}
}
/// To give a shared class to any children that might be used by MenuBar
abstract class iMenuItem extends Widget{
}
I've spent 3 days on this, so any help would be appreciated.
Thanks!!
I suggest using ChangeNotifier, ChangeNotifierProvider, Consumer and context.read to manage state. You have to add this package and this import: import 'package:provider/provider.dart';. The steps:
Set up a ChangeNotifier holding isExpanded value, with a setter that notifies listeners:
class MyNotifier with ChangeNotifier {
bool _isExpanded = false;
bool get isExpanded => _isExpanded;
set isExpanded(bool isExpanded) {
_isExpanded = isExpanded;
notifyListeners();
}
}
Insert the above as a ChangeNotifierProvider in your widget tree at MenuBar:
class MenuBarState extends State<MenuBar> {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyNotifier(),
child: MenuBarContainer(
menuItems: widget.menuItems,
));
}
}
After this you can easily read and write the isExpanded value from anywhere in your widget tree under the ChangeNotifierProvider, for example:
ElevatedButton(
onPressed: () {
setState(() {
final myNotifier = context.read<MyNotifier>();
myNotifier.isExpanded = !myNotifier.isExpanded;
});
},
child: Text('Push Me'),
),
And if you want to use this state to automatically build something when isExpanded is changed, use Consumer, which will be notified automatically upon every change, for example:
class MenuBarItemState extends State<MenuBarItem> {
#override
Widget build(BuildContext context) {
return Consumer<MyNotifier>(builder: (context, myNotifier, child) {
return Row(
children: <Widget>[
Text('Current Status:\t${myNotifier.isExpanded}'),
Text(widget.myText),
],
);
});
}
}

Reusing a Widget with Setters

New to Dart/Flutter and unsure if this is the proper way of going about this, but I want to make a class for an ElevatedButton widget that I can use over-and-over and only need to set the text and callback (onPressed) for each instance of ElevatedButton.
For now I'm just trying to get to the point where I can make a list of Widgets (ElevatedButtons) where I set each button text, but am struggling. This is what I have:
class AElevatedButton extends StatefulWidget
{
AElevatedButton({Key? key}) : super(key:key);
#override
State<AElevatedButton> createState() => ElevatedButtonState();
}
class ElevatedButtonState extends State<AElevatedButton>
{
String buttonText = "Button";
void setText(String buttonText) {
setState(() {
this.buttonText = buttonText;
});
}
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(5),
child: ElevatedButton(
onPressed: null,
child: Text(buttonText)
)
);
}
}
In my "home page" State I have below but am not able to access the "setText" method in ElevatedButtonState, which somewhat makes sense as I'm creating a ElevatedButton object and not a ElevatedButtonState. Unsure if there is a way to get the state so I can call the method?
class _MyHomePageState extends State<MyHomePage>
{
AElevatedButton firstButton = new AElevatedButton();
AElevatedButton secondButton = new AElevatedButton();
void initiateButtons()
{
firstButton.setText("Button 1"); <---- error
secondButton.setText("Button 2"); <---- error
}
#override
Widget build(BuildContext context) {
initiateButtons();
return Scaffold(
appBar: AppBar(
title: const Text("Test Buttons")
),
body:
Column(
children: <Widget>[
firstButton,
secondButton
])
);
}
}
It seems that what you're making is more or less a wrapper of an existing widget with your own customization. This is rather common in Flutter, and actually its how a lot of material widgets are implemented, just setting up a bunch of properties with a predefined state.
In your case the correct way to achieve what you want, is to make a new class just as you did, but you don't need to create setters to change the state/attributes of your new widgets. Simply pass them in the constructors of your new class, for example:
class AElevatedButton extends StatelessWidget {
final String text;
final VoidCallback? onTap;
const AElevatedButton({required this.text,this.onTap});
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(5),
child: ElevatedButton(
onPressed: onTap,
child: Text(text)
)
);
}}
Here there is only text and onTap, but you could add more attributes as your necessities change, and to use them is just like any other flutter widget:
class _MyHomePageState extends State<MyHomePage>
{
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Test Buttons")
),
body:
Column(
children: <Widget>[
AElevatedButton(text:"Button 1",onTap:testcallback1),
AElevatedButton(text:"Button 2",onTap:testcallback2)
])
);
}
}
If I missed any point or could clarify something more, do comment.
After some experimenting, I got a solution but unsure if this is the best way:
class AElevatedButton extends StatefulWidget
{
AElevatedButton({Key? key}) : super(key:key);
String buttonText = "Default";
Function() cb = nothingfunc;
#override
State<AElevatedButton> createState() => ElevatedButtonState();
}
class ElevatedButtonState extends State<AElevatedButton>
{
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(5),
child: ElevatedButton(
onPressed: widget.cb,
child: Text(widget.buttonText)
)
);
}
}
And the homepage State:
AElevatedButton firstButton = new AElevatedButton();
AElevatedButton secondButton = new AElevatedButton();
void initiateButtons()
{
firstButton.buttonText = "Button 1";
firstButton.cb = testcallback1;
secondButton.buttonText = "Button 2";
secondButton.cb = testcallback2;
}
Some misc. functions added for testing above:
void nothingfunc()
{
}
void testcallback1()
{
print("Button 1 pressed");
}
void testcallback2()
{
print("Button 2 pressed");
}

Using Type Function and pass it into ElevatedButton onPressed, Flutter

I am new to Flutter + Dart. I basically have class in the following.
First I have a clas called BottomForm where it have build function that returns ElevatedButton problem when I call Function type variable in onPressed I have an issue saying that:
The argument type 'Function' can't be assigned to the parameter type 'void Function()?'.dartargument_type_not_assignable
import 'formbutton.dart';
// Define a corresponding State class.
// This class holds the data related to the Form.
class _MyCustomFormState extends State<MyCustomForm> {
// Create a text controller and use it to retrieve the current value
// of the TextField.
final email = TextEditingController();
final password = TextEditingController();
void _logIn() {
print("Logged In.");
}
#override
void dispose() {
// Clean up the controller when the widget is disposed.
email.dispose();
password.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: TextFormField(
autocorrect: true,
controller: email,
),
),
ButtonForm(_logIn, "Hello"),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text(email.text),
);
});
},
tooltip: "Show me the value",
child: Icon(Icons.text_fields),
),
);
}
}
//Define a Custom Widget
class MyCustomForm extends StatefulWidget {
#override
_MyCustomFormState createState() => _MyCustomFormState();
}
Than I have a problem in the main class for our Button . When I pass the Function functionApply;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class ButtonForm extends StatelessWidget {
final Function functionApply;
final String textButton;
ButtonForm(this.functionApply, this.textButton);
#override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
child: ElevatedButton(
child: Text(this.textButton),
onPressed: this.functionApply, // I have a problem here!!
),
);
}
}
onPressed is a type of VoidCallback
typedef VoidCallback = void Function()
So instead of using
final Function functionApply;
use
final VoidCallback functionApply;
So your ButtonForm will be
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class ButtonForm extends StatelessWidget {
final VoidCallback functionApply;
final String textButton;
ButtonForm(this.functionApply, this.textButton);
#override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
child: ElevatedButton(
child: Text(textButton),
onPressed: functionApply, // Problem Solved!!
),
);
}
}
Try this:
ElevatedButton(
child: Text(this.textButton),
onPressed: () {
functionApply();
},
)
Give the return type of your function. If you don't give any return type then by default the return type will be dynamic. But onPressed function 's return type is void. So just change the function deceleration and it will work nicely.
final void Function() functionApply;

What is the right way using the build context outside the build function

Lets say I'm enter my named route page and get the arguments in the build function.
Now my widget is state full widget and i want to make api call with the arguments in order to set the state of my widget.
I'm using future Builder to load the api when the page is loading, so i have to create Future and equal him to the api func right?
but i cant do it inside the build it will call it unlimited times, so i send it as props to an other widget but really i should create widget just in order to send my context values?
class GameScreen extends StatefulWidget {
GameScreen({Key key}) : super(key: key);
#override
_GameScreenState createState() => _GameScreenState();
}
class _GameScreenState extends State<GameScreen> {
Fixture fixture;
Future setFIxture(externalId) async {
final response =
await FixturesService().getLiveFixtureByExternalId(externalId);
setState(() {
fixture = response;
});
}
#override
Widget build(BuildContext context) {
final GameScreenArguments args = ModalRoute.of(context).settings.arguments;
Future initScreen;
initScreen = setFIxture(args.externald);
return RoutePage(
child: Loader(
future: initScreen,
succeed: Container(
height: 223,
width: double.infinity,
child: Column(
children: [
Column(
children: [
Text(""),
Row(
children: [
Text(""),
Text(""),
],
),
Column(
children: [
// TeamImage(),
Column(
children: [
Text(""),
Text(""),
],
),
// TeamImage(),
],
)
],
),
Column(
children: [
Text(""),
],
)
],
),
),
),
);
}
}
my loader widget:
class Loader extends StatefulWidget {
final Future future;
final Widget succeed;
Loader({Key key, this.future, this.succeed}) : super(key: key);
#override
_LoaderState createState() => _LoaderState();
}
class _LoaderState extends State<Loader> {
Future _getTaskAsync;
final spinkit = SpinKitFadingCircle(
color: Colors.black,
size: 40,
);
Future fetchData() async {
try {
await widget.future;
return true;
} catch (e) {
return e;
}
}
#override
void initState() {
_getTaskAsync = fetchData();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _getTaskAsync,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return widget.succeed;
} else if (snapshot.hasError) {
return Text("error");
} else {
return spinkit;
}
},
);
}
}
Yes you have to create another widget. But maybe use a dependency injection solution to make it simpler to inject objects into the widget tree (Riverpod is good i heard). Store the GameScreen args inside a shared state above in the widget tree.

Flutter UI doesn't update when custom widget is used

I have a Flutter where I display a list of elements in a Column, where the each item in the list is a custom widget. When I update the list, my UI doesn't refresh.
Working sample:
class Test extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return TestState();
}
}
class TestState extends State<Test> {
List<String> list = ["one", "two"];
final refreshKey = new GlobalKey<RefreshIndicatorState>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(40),
child: Row(
children: <Widget>[
Container(
child: FlatButton(
child: Text("Update"),
onPressed: () {
print("Updating list");
setState(() {
list = ["three", "four"];
});
},
)
),
Column(
children: list.map((s) => ItemView(s)).toList(),
)
],
),
)
);
}
}
class ItemView extends StatefulWidget {
String s;
ItemView(this.s);
#override
State<StatefulWidget> createState() => ItemViewState(s);
}
class ItemViewState extends State<ItemView> {
String s;
ItemViewState(this.s);
#override
Widget build(BuildContext context) {
return Text(s);
}
}
When I press the "Update" button, my list is updated but the UI is not. I believe this has something to do with using a custom widget (which is also stateful) because when I replace ItemView(s) with the similar Text(s), the UI updates.
I understand that Flutter keeps a track of my stateful widgets and what data is being used, but I'm clearly missing something.
How do I get the UI to update and still use my custom widget?
You should never pass parameters to your State.
Instead, use the widget property.
class ItemView extends StatefulWidget {
String s;
ItemView(this.s);
#override
State<StatefulWidget> createState() => ItemViewState();
}
class ItemViewState extends State<ItemView> {
#override
Widget build(BuildContext context) {
return Text(widget.s);
}
}