Reusing a Widget with Setters - flutter

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");
}

Related

flutter can't access static variable

I want to access a static variable in a stateful widget in a flutter.
but it does not work.
and someone said that is a private widget and I can't access it.
so how can I access the variable isCollapsed in the below code:
class BottomNavBar extends StatefulWidget {
final int activeTabIndex;
const BottomNavBar({Key? key, required this.activeTabIndex})
: super(key: key);
#override
_BottomNavBarState createState() => _BottomNavBarState();
}
class _BottomNavBarState extends State<BottomNavBar> {
static var isCollapsed = false;
#override
Widget build(BuildContext context) {
Scaffold(
body: SlidingUpPanel(
controller: _pc,
panelBuilder: (sc) {
if (isCollapsed == false) _pc.hide();
if (isCollapsed == true) _pc.show();
return Container(
child: Center(child: Text("Panel")),
);
},
body: Text("something");
), }
}
I want to change isCollapsed in another class,
when clicked on the icon, isCollapsed changes to true.
class _PlayerPreviewState extends State<PlayerPreview> {
#override
Widget build(BuildContext context) {
final String play = 'assets/icons/play.svg';
var pageWidth = MediaQuery.of(context).size.width;
return Padding(
padding: EdgeInsets.only(top: pageWidth * .04),
child: IconButton(
onPressed: () {
},
icon: SvgPicture.asset(play),
),
);}}
can anyone help me please how can I do it?
Yes, _BottomNavBarState is private because it starts with underscore_. if you want to make it public, remove _ from name and it will be like
#override
BottomNavBarState createState() => BottomNavBarState();
}
class BottomNavBarState extends State<BottomNavBar> {
static var isCollapsed = false;
and use anywhere like BottomNavBarState.isCollapsed, but I should prefer state-management while the variable has effects on state.
For more about
state-management
dart

Flutter, calling a function inside the button

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
},
),
],
),
);
}
}

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);
}
}

Flutter Bloc Design - Inform pages about changes

is there a best practice for this? (Im using this Todo example since its easier to explain my problem here)
TodoOverviewPage (Shows all todos)
TodoAddPage (Page to add todos)
Each page has an own Bloc.
Steps:
From the TodoOverviewPage I navigate wuth pushNamed to TodoAddPage.
In TodoAddPage I add several Todos.
Using the Navigation Back Button to go back to TodoOverviewPage
Question: How should I inform TodoOverviewPage that there are new Todos?
My approaches which Im not sure if this is the right way.
Solutions:
Overwriting the Back Button in TodoAddPage. To add a "refresh=true" property.
Adding the Bloc from TodoOverviewPage to TodoAddPage. And setting the State to something that the TodoOverviewPage will reload todos after building.
Thank you for reading.
EDIT1:
Added my temporary solution till I find something which satisfies me more.
You can achieve by different way
InheritedWidget
ValueCallback in TodoAddPage
For Example:
class Item {
String reference;
Item(this.reference);
}
class _MyInherited extends InheritedWidget {
_MyInherited({
Key key,
#required Widget child,
#required this.data,
}) : super(key: key, child: child);
final MyInheritedWidgetState data;
#override
bool updateShouldNotify(_MyInherited oldWidget) {
return true;
}
}
class MyInheritedWidget extends StatefulWidget {
MyInheritedWidget({
Key key,
this.child,
}): super(key: key);
final Widget child;
#override
MyInheritedWidgetState createState() => new MyInheritedWidgetState();
static MyInheritedWidgetState of(BuildContext context){
return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
}
}
class MyInheritedWidgetState extends State<MyInheritedWidget>{
/// List of Items
List<Item> _items = <Item>[];
/// Getter (number of items)
int get itemsCount => _items.length;
/// Helper method to add an Item
void addItem(String reference){
setState((){
_items.add(new Item(reference));
});
}
#override
Widget build(BuildContext context){
return new _MyInherited(
data: this,
child: widget.child,
);
}
}
class MyTree extends StatefulWidget {
#override
_MyTreeState createState() => new _MyTreeState();
}
class _MyTreeState extends State<MyTree> {
#override
Widget build(BuildContext context) {
return new MyInheritedWidget(
child: new Scaffold(
appBar: new AppBar(
title: new Text('Title'),
),
body: new Column(
children: <Widget>[
new WidgetA(),
new Container(
child: new Row(
children: <Widget>[
new Icon(Icons.shopping_cart),
new WidgetB(),
new WidgetC(),
],
),
),
],
),
),
);
}
}
class WidgetA extends StatelessWidget {
#override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context);
return new Container(
child: new RaisedButton(
child: new Text('Add Item'),
onPressed: () {
state.addItem('new item');
},
),
);
}
}
class WidgetB extends StatelessWidget {
#override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context);
return new Text('${state.itemsCount}');
}
}
class WidgetC extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Text('I am Widget C');
}
}
Temporary solution:
Each (root) Page which has a Bloc now always reloads when build.
The Bloc takes care for caching.
Widget build(BuildContext context) {
final PageBloc pBloc = BlocProvider.of<PageBloc >(context);
bool isNewBuild = true;
return Scaffold(
...
body: BlocBuilder<PageBlocEvent, PageBlocState>(
if (isNewBuild) {
pBloc.dispatch(PageBlocEvent(PageBlocEventType.GETALL));
isNewBuild = false;
return CircularProgressIndicator();
} else {
// Draw data
...
...
}

how to keep the state of my widgets after scrolling?

I'm codeing an app with flutter an i'm haveing problems with the development. I'm trying to have a listview with a custom widget that it has a favourite icon that represents that you have liked it product. I pass a boolean on the constructor to set a variables that controls if the icons is full or empty. When i click on it i change it state. It works awesome but when i scroll down and up again it loses the lastest state and returns to the initial state.
Do you know how to keep it states after scrolling?
Ty a lot <3
Here is my code:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index){
return new LikeClass(liked: false);
},
),
);
}
}
class LikeClass extends StatefulWidget {
final bool liked;//i want this variable controls how heart looks like
LikeClass({this.liked});
#override
_LikeClassState createState() => new _LikeClassState();
}
class _LikeClassState extends State<LikeClass> {
bool liked;
#override
void initState() {
liked=widget.liked;
}
#override
Widget build(BuildContext context) {
return new Container(
child: new Column(
children: <Widget>[
new GestureDetector(
onTap:((){
setState(() {
liked=!liked;
//widget.liked=!widget.liked;
});
}),
child: new Icon(Icons.favorite, size: 24.0,
color: liked?Colors.red:Colors.grey,
//color: widget.liked?Colors.red:Colors.grey,//final method to control the appearance
),
),
],
),
);
}
}
You have to store the state (favorite or not) in a parent widget. The ListView.builder widget creates and destroys items on demand, and the state is discarded when the item is destroyed. That means the list items should always be stateless widgets.
Here is an example with interactivity:
class Item {
Item({this.name, this.isFavorite});
String name;
bool isFavorite;
}
class MyList extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyListState();
}
class MyListState extends State<MyList> {
List<Item> items;
#override
void initState() {
super.initState();
// Generate example items
items = List<Item>();
for (int i = 0; i < 100; i++) {
items.add(Item(
name: 'Item $i',
isFavorite: false,
));
}
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListItem(
items[index],
() => onFavoritePressed(index),
);
},
);
}
onFavoritePressed(int index) {
final item = items[index];
setState(() {
item.isFavorite = !item.isFavorite;
});
}
}
class ListItem extends StatelessWidget {
ListItem(this.item, this.onFavoritePressed);
final Item item;
final VoidCallback onFavoritePressed;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(item.name),
leading: IconButton(
icon: Icon(item.isFavorite ? Icons.favorite : Icons.favorite_border),
onPressed: onFavoritePressed,
),
);
}
}
If you don't have many items in the ListView you can replace it with a SingleChildScrollview and a Column so that the Widgets aren't recycled. But it sounds like you should have a list of items where each item has an isFavourite property, and control the icon based on that property. Don't forget to setState when toggling the favorite.
Other answer are better for your case but this an alternative and can be used if you want to only keep several elements alive during a scroll. In this case you can use AutomaticKeepAliveClientMixin with keepAlive.
class Foo extends StatefulWidget {
#override
FooState createState() {
return new FooState();
}
}
class FooState extends State<Foo> with AutomaticKeepAliveClientMixin {
bool shouldBeKeptAlive = false;
#override
Widget build(BuildContext context) {
super.build(context);
shouldBeKeptAlive = someCondition();
return Container(
);
}
#override
bool get wantKeepAlive => shouldBeKeptAlive;
}
ListView.builder & GridView.builder makes items on demand. That means ,they construct item widgets & destroy them when they going beyond more than cacheExtent.
So you cannot keep any ephemeral state inside that item widgets.(So most of time item widgets are Stateless, but when you need to use keepAlive you use Stateful item widgets.
In this case you have to keep your state in a parent widget.So i think the best option you can use is State management approach for this. (like provider package, or scoped model).
Below link has similar Example i see in flutter.dev
Link for Example
Hope this answer will help for you
A problem with what you are doing is that when you change the liked variable, it exists in the Widget state and nowhere else. ListView items share Widgets so that only a little more than are visible at one time are created no matter how many actual items are in the data.
For a solution, keep a list of items as part of your home page's state that you can populate and refresh with real data. Then each of your LikedClass instances holds a reference to one of the actual list items and manipulates its data. Doing it this way only redraws only the LikedClass when it is tapped instead of the whole ListView.
class MyData {
bool liked = false;
}
class _MyHomePageState extends State<MyHomePage> {
List<MyData> list;
_MyHomePageState() {
// TODO use real data.
list = List<MyData>();
for (var i = 0; i < 100; i++) list.add(MyData());
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new ListView.builder(
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return new LikeClass(list[index]);
},
),
);
}
}
class LikeClass extends StatefulWidget {
final MyData data;
LikeClass(this.data);
#override
_LikeClassState createState() => new _LikeClassState();
}
class _LikeClassState extends State<LikeClass> {
#override
Widget build(BuildContext context) {
return new Container(
child: new Column(
children: <Widget>[
new GestureDetector(
onTap: (() {
setState(() {
widget.data.liked = !widget.data.liked;
});
}),
child: new Icon(
Icons.favorite,
size: 24.0,
color: widget.data.liked ? Colors.red : Colors.grey,
),
),
],
),
);
}
}