How to change "setState()" when using GetX in Flutter? - state

I have a login screen code like below that has a text button that changes the state of the Login button to Signup or reverse, and want to rewrite it to use GetX library. But I don't know how?
enum AuthMode { Signup, Login }
class AuthenticationScreen extends StatelessWidget {
const AuthenticationScreen({Key? key}) : super(key: key);
AuthMode _authMode = AuthMode.Login;
#override
Widget build(BuildContext context) {
final GlobalKey<FormState> _formKey = GlobalKey();
void _switchAuthMode() {
if (_authMode == AuthMode.Login) {
setState(() {
_authMode = AuthMode.Signup;
= });
_controller!.forward();
} else {
setState(() {
_authMode = AuthMode.Login;
});
_controller!.reverse();
}
}
return Scaffold(
body: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 400),
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
,
TextButton(
child: Text(
'${_authMode == AuthMode.Login ? 'SIGNUP' : 'LOGIN'} '),
onPressed: _switchAuthMode,
style: TextButton.styleFrom(
padding:
const EdgeInsets.symmetric(horizontal: 30.0, vertical: 4),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
textStyle: TextStyle(color: Theme.of(context).primaryColor),
),
],
),
),
),
);
}
}
I tried some changes like transferring Authmode to the auth_controller file that extends GetxController and add obs after AuthMode _authMode = AuthMode.Login; and try to use obx(()=>) inside the _switchAuthMode() but didn't work.

Try like this:
final authMode= Rx<AuthMode>(AuthMode.Login);
And then on your switchAuthMode method:
authMode.value = AuthMode.Signup; // without setState
And finally, wrap the Text widget with Obx:
Obx(()=> Text('${authMode.value == AuthMode.Login ? 'SIGNUP' : 'LOGIN'} ')
And you can actually make your widget a StatelessWidget.

Related

flutter SingleChildScrollView remove item

I have the following code where I generate a list of items(data is taken from Firebase). I would like to implement a functionality to remove items but I don't know how to access the list and how to remove items:
class _MyOfferState extends State<MyOffer> {
List<Widget> items = [];
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
...
body: SingleChildScrollView(
child: Column(
children: [
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('Offers')
builder: (BuildContext context, snapshot) {
snapshot.data.docs.forEach((element) {
element.get('items').forEach((item) {
String _name = element['name'];
String _category = item['category'];
items.add(offer(name, category, context,...));
});
}
);
}
return new Column(
children: List.unmodifiable(() sync* {
yield* items;
}()),
);
},
),
}
}
This is a dynamic class where I have GestureDetector. The item should be deleted when a user clicks on the it.
dynamic offer(name, category, context,) {
return GestureDetector(
child: Container(
child: Row(
children: [
Text(name),
Text(category),
],
),
),
),
onTap: () {
// remove item should be here
},
);
}
Removing the offer from within itself is not the best practice but you can accomplish it in a number of ways. The first I can think of is to pass a function that removes it when creating the offer like this:
items.add(offer(name, category, context,..., () {
setState(() {
FirebaseFirestore.instance
.collection('Offers')
.doc(element['id'])
.delete();
items.remoev(index);
});
}));
You'll need to create the index beforehand and increase it each time but I don't recommend doing it.
The way I would done do this is change the offer to be:
dynamic offer(name, category, context,) {
return Container(
child: Row(
children: [
Text(name),
Text(category),
],
),
);
}
And when creating the offer wrap it in the GestureDetector like this:
items.add(GestureDetector(
child: offer(name, category, context,...)),
onTap: () {
setState(() {
FirebaseFirestore.instance
.collection('Offers')
.doc(element['id'])
.delete();
items.remoev(index);
});
},
);
You'll have to do the same thing with the index but I consider it a better approach since the child has no power over the parent and can't change its state which is a good practice.
you need to pass index of item and delete by index:
int index = 0;
snapshot.data.docs.forEach((element) {
element.get('items').forEach((item) {
String _name = element['name'];
String _category = item['category'];
items.add(offer(index, name, category, context,...));
index++;
});
Widget offer(int index, string name, string category, BuildContext context,) {
return GestureDetector(
child: Container(
child: Row(
children: [
Text(name),
Text(category),
],
),
),
),
onTap: () {
// remove item should be here
items.removeAt(index);
setState((){});
},
);
}
}
);
}
return new Column(
children: List.unmodifiable(() sync* {
yield* items;
}()),
);
Your list is getting build by Stream data the one you provided to your StreamBuilder, do create new list you need to change Stream value, I suggest to keep FirebaseFirestore.instance.collection('Offers') instance in a stream and modify the stream.
class _MyOfferState extends State<MyOffer> {
List<Widget> items = [];
StreamController _controller = StreamController();
#override
void initState() {
super.initState();
_controller.addStream( FirebaseFirestore.instance
.collection('Offers').snapshots());
}
// dont forgot to close stream
#override
void dispose() {
_controller.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
...
body: SingleChildScrollView(
child: Column(
children: [
StreamBuilder<QuerySnapshot>(
stream: _controller.stream,
builder: (BuildContext context, snapshot) {
snapshot.data.docs.forEach((element) {
element.get('items').forEach((item) {
String _name = element['name'];
String _category = item['category'];
items.add(offer(name, category, context,(){
// remove function is here
snapshot.data.docs.removeWhere((e) => e.id == element.id);
_controller.add(snapshot.data);
});
});
}
);
}
return new Column(
children: List.unmodifiable(() sync* {
yield* items;
}()),
);
},
),
}
}
Also pass onTap to your widget function
dynamic offer(name, category, context, onTap) {
return GestureDetector(
child: Container(
child: Row(
children: [
Text(name),
Text(category),
],
),
),
),
onTap: onTap,
);
}

Flutter property values of a Map all treated as strings

I have a list like this a = [{'one': 'one', 'two': null, 'three': [{'four': 'four'}]}]
I send it to a function to use it in a post request which in the body should receive a Map, so what I did was this to a[0], the problem is that I get this error The getter 'length' was called on null
I start to review and it treats all the property values as if they were Strings, even the nested list 'three': [{'four': 'four'}], I have tried to send the post in this way http.post (url, body: (recurrence [0] as Map)) but it has not worked, it always gives me the same error, even if in the body I put the properties by hand in the body: {'new property': a [0] [' tres']}, how should one act to solve this problem? Thank you very much for your help
Code:
void _ordersGet() async {
await http.get(url).then((value) {
setState(() {
orders = jsonDecode(value.body);
}
}
orders is sent to a new widget: orderList(orders)
orderList is a listView
ListView.builder(
shrinkWrap: true,
primary: false,
itemCount: orders.length,
itemBuilder: (orders, index) {
return return Card(
elevation: 5,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(orders[index]['facts']),
SizedBox(
width: 4,
),
Text('Cantidad : '),
Text(orders[index]['ITEMS'][0]['jeans']),
SizedBox(
width: 4,
),
IconButton(
onPressed: () => _reorderData(context, orders[index]),
icon: Icon(
Icons.replay_outlined,
color: Theme.of(context).accentColor,
)),
],
),
);
},
);
_reorderData is a function that make a get request, the info in shipped to ReorderModal
ReorderModal it only shows the information and has a button
void _reorderData(BuildContext ctx, order) async {
var data;
var url = 'serverAddress/${order['facts']}';
await http.get(url).then((value) {
data = jsonDecode(value.body);
data[0]['CORPORATION'] = order['corporation'];
showModalBottomSheet(
context: ctx,
builder: (_) {
return ReorderModal(data);
});
}).catchError((onError) {});
}
class ReorderModal extends StatelessWidget {
final List data;
ReorderModal(this.data);
void orderSend(orderInfo) async {
var url = 'serverAddress';
await http.post(url, body: orderInfo[0]).then((value) {
print(jsonDecode(value.body));
}).catchError((onError) {
print(onError);
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10),
child: Column(children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: Column(
children: [
ElevatedButton(
onPressed: () {
orderSend(data);
//print(data);
},
child: Text('ONE Click'))
]),
);
}
}
when i press the ONE Click button execute the function orderSend, orderSend make a post request and the problem described above
This is the simplified code, I know it must be something very simple, but it is giving me a lot of work to solve

Unable to use the .map function in Flutter

So I am following along a book about Flutter App Development where I was tasked to implement a ToDoMenuItem class and create a list in it.
class TodoMenuItem {
final String title;
final Icon icon;
TodoMenuItem({this.title, this.icon});
List<TodoMenuItem> foodMenuList = [
TodoMenuItem(title: 'Fast Food', icon: Icon(Icons.fastfood)),
TodoMenuItem(title: 'Remind Me', icon: Icon(Icons.add_alarm)),
TodoMenuItem(title: 'Flight', icon: Icon(Icons.flight)),
TodoMenuItem(title: 'Music', icon: Icon(Icons.audiotrack)),
];
}
Then I was tasked to map it to a PopUpMenuButtonWidget using an itemBuilder. Here is the class I wrote for it.
class PopupMenuButtonWidget extends StatelessWidget
implements PreferredSizeWidget {
const PopupMenuButtonWidget({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.lightGreen.shade100,
height: preferredSize.height,
width: double.infinity,
child: Center(
child: PopupMenuButton<TodoMenuItem>(
icon: Icon(Icons.view_list),
onSelected: ((valueSelected) {
print('valueSelected: ${valueSelected.title}');
}),
itemBuilder: (BuildContext context) {
return foodMenuList.map((TodoMenuItem todoMenuItem) {
return PopupMenuItem<TodoMenuItem>(
value: todoMenuItem,
child: Row(
children: <Widget>[
Icon(todoMenuItem.icon.icon),
Padding(
padding: EdgeInsets.all(8.0),
),
Text(todoMenuItem.title),
],
),
);
}).toList();
},
),
),
);
}
#override // implement preferredSize
Size get preferredSize => Size.fromHeight(75.0);
}
However, it returns an error at this line.
return foodMenuList.map((TodoMenuItem todoMenuItem) {
And the error says
Undefined name 'foodMenuList'.
Try correcting the name to one that is defined, or defining the name.
How can I 'map' the foodMenuList list to the widget?
Your foodMenuList is declared in todoMenuItem class, while you try to refer to it like it would be a part of PopupMenuButtonWidget (you are doing this.foodMenuList in context of PopupMenuButtonModget)
You could create instantiate an instance of TodoMenuList in PopupMenuButtonWidget and then use it.
final TodoMenuItem _todoMenu = TodoMenuItem();
Widget build(BuildContext context) {
...
// Someplace where you need to use the list
_todoMenu.foodMenuList
...
}

Flutter list of Text: How to fix the style of all list items

I am new to flutter and to the concept of Object orientation in general. I am building a list of Text to be used with a CupertinoPicker in flutter, I want to use the same style for all the list items but I don't want to keep repeating the lines and each time specifying the text style.
For example, see the list of car manufacturers below:
import 'package:flutter/material.dart';
TextStyle kStyle = TextStyle(color: Colors.white, fontWeight: FontWeight.w900);
List<Text> manufacturers = [
Text('Toyota', style: kStyle,),
Text('VolksWagen', style: kStyle,),
Text('Nissan', style: kStyle,),
Text('Renault', style: kStyle,),
Text('Mercedes', style: kStyle,),
Text('BMW', style: kStyle,)
];
You see the list items in manufacturers list can get so long with more cars, can I use a class to tell flutter that my style is fixed to kstyle for all the items without explicitly writing style: kstyle for every single line?
Basically We can use DefaultTextStyle widget
Final Result
CupertinoPicker Widget
Common Column Widget
1. The Problem is we need to use CupertinoPicker
which in the library, it is defined as
final Widget result = DefaultTextStyle(
style: CupertinoTheme.of(context).textTheme.pickerTextStyle,
child: Stack(
2. Solution : Override Theme
Therefore We are required to defined its style at the very beginning definition of our app
const TextStyle kStyle = TextStyle(
color: Colors.blue,
fontWeight: FontWeight.w900,
);
class FlutterApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Cupertino Picker',
home: ListViewScreen(),
theme: ThemeData(
cupertinoOverrideTheme: CupertinoThemeData( // <---------- this
textTheme: CupertinoTextThemeData(
pickerTextStyle: kStyle,
),
),
),
);
}
}
A. Full Working Code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(FlutterApp());
}
const TextStyle kStyle = TextStyle(
color: Colors.blue,
fontWeight: FontWeight.w900,
);
class FlutterApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Cupertino Picker',
home: ListViewScreen(),
theme: ThemeData(
cupertinoOverrideTheme: CupertinoThemeData( // <---------- this
textTheme: CupertinoTextThemeData(
pickerTextStyle: kStyle,
),
),
),
);
}
}
class ListViewScreen extends StatelessWidget {
final List<Text> manufacturers = [
Text('Toyota'),
Text('VolksWagen'),
Text('Nissan'),
Text('Renault'),
Text('Mercedes'),
Text('BMW')
];
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text("Select Car"),
),
child: Container(
height: 200,
child: CupertinoPicker(
itemExtent: 50,
onSelectedItemChanged: (int index) {
print(index);
},
children: manufacturers,
),
),
);
}
}
B. [Optional] Simple use of Default Text Style
List<Text> manufacturers = [
Text('Toyota'),
Text('VolksWagen'),
Text('Nissan'),
Text('Renault'),
Text('Mercedes'),
Text('BMW')
];
const TextStyle kStyle = TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
);
class CarList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DefaultTextStyle(
style: kStyle,
child: Column(
children: manufacturers,
),
);
}
}
You can create a new widget:
class MyTextWidget extends StatelessWidget {
final String text;
const MyTextWidget({Key key, this.text}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text(text,style: TextStyle(color: Colors.white, fontWeight: FontWeight.w900),);
}
}
and use it in your list
List<Text> manufacturers = [
MyTextWidget('Toyota'),
MyTextWidget('VolksWagen'),
MyTextWidget('Nissan'),
MyTextWidget('Renault'),
MyTextWidget('Mercedes'),
MyTextWidget('BMW')
];
If you want to change the font in all the application, you must change it from MaterialApp like this:
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
textTheme: TextTheme(
//Use the appropriate TextStyle
),
),);
}
You can create one method for all Text widgets:
Text styledText(String text) => Text(text, style: TextStyle(color: Colors.white, fontWeight: FontWeight.w900));
and use it in your list:
List<Text> manufacturers = [
styledText('Toyota'),
styledText('VolksWagen'),
styledText('Nissan'),
styledText('Renault'),
styledText('Mercedes'),
styledText('BMW')
];
You can also create a list of strings
List<String> manufacturers = [
'Toyota',
'VolksWagen',
'Nissan',
'Renault',
'Mercedes',
'BMW',
];
And use tis method when you iterate through your list.
Or you can create a class instead of the method:
class CustomStyledText extends StatelessWidget {
final String text;
const CustomStyledText(this.text, {Key key}) : super(key: key);
TextStyle get _style => TextStyle(color: Colors.white, fontWeight: FontWeight.w900);
#override
Widget build(BuildContext context) => Text(text, style: _style);
}
You can create an extension on the Text widget and use that:
Create an extension:
// extension
extension on Text {
// method to apply style
applyStyle(TextStyle textStyle) {
return Text(
this.data,
style: textStyle,
);
}
}
Use the extension method on the Text widget:
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
...List.generate(
manufacturers.length,
// call the applyStyle method on the Text widget
(index) => manufacturers[index].applyStyle(kStyle),
).toList(),
],
),
);

How to send a List object through a callback function in Flutter?

What I've already done
I have a stateful widget that generates a ListView on screen.
class AppListView extends StatefulWidget {
final ValueChanged onChange;
final List<MatchList> matchList;
final ValueChanged finalBetList;
AppListView({this.onChange, this.matchList, this.finalBetList});
#override
_AppListViewState createState() => _AppListViewState();
}
class _AppListViewState extends State<AppListView> {
int counter = 0;
List<MatchList> betList = List<MatchList>();
I have a Home Screen that calls this Stateful Widget. In here I am using a callback function (onChange) to get the counter value from the widget. Everything is working perfect.
Stack(children: [
AppListView(
matchList: matchList,
//callback function brings the counter value from ListView class
onChange: (value) {
setState(() {
counter = value;
});
},
),
Positioned(
child: Padding(
padding: const EdgeInsets.only(top: 280.0, left: 330.0),
child: Container(
width: 60,
height: 60,
child: FloatingActionButton(
onPressed: () {
counter > 0
? Navigator.pushNamed(context, BetScreen.id)
: print('you shall not pass');
},
child: Text(
counter.toString(),
style: kTextStyleAppBarTitle,
),
),
),
),
)
])
What is the problem
But when I am trying to call that widget from another screen with a similar callback function(finalBetList), I got "The method 'call' was called on null. Receiver: null" error. Actually everything that I do is the same as the other example that works fine. I can't find what I'm missing. Is it something about Lists?
class _BetScreenState extends State<BetScreen> {
List<MatchList> betList = List<MatchList>();
int counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(
currentBalance: '\u0024' + '200.00',
userLevel: 'Level 30',
userName: 'Mark',
),
body: Container(
child: AppListView(
finalBetList: (value) {
setState(() {
//counter = value;
betList = value;
//print(betList);
});
},
matchList: betList,
),
),
);
}
}
After Edit
I added the cod of AppListView widget
class AppListView extends StatefulWidget {
final ValueChanged onChange;
final List<MatchList> matchList;
final ValueChanged finalBetList;
AppListView({this.onChange, this.matchList, this.finalBetList});
#override
_AppListViewState createState() => _AppListViewState();
}
class _AppListViewState extends State<AppListView> {
int counter = 0;
List<int> betList = List<int>();
#override
Widget build(BuildContext context) {
children: <Widget>[
AppButton.buildAppButton(
context,
AppButtonType.BETSELECTION,
widget.matchList[index].homeOdds,
kCategoryButtonDimensions,
color: widget.matchList[index].homeSelected
? Colors.yellow
: Colors.white,
onPressed: () {
if (widget.matchList[index].drawSelected ||
widget.matchList[index].awaySelected) {
widget.matchList[index].drawSelected =
false;
widget.matchList[index].awaySelected =
false;
counter--;
//betList part
if (betList.length > 0)
betList
.remove(widget.matchList[index].id);
}
widget.matchList[index].homeSelected =
!widget.matchList[index].homeSelected;
if (widget.matchList[index].homeSelected) {
counter++;
betList.add(widget.matchList[index].id);
} else {
counter--;
if (betList.length > 0)
betList.remove(widget.matchList[index]
.id); //if selected, add to betList to send BetScreen
}
widget.onChange(counter);
print(betList);
widget.finalBetList(betList);
setState(() {});
},
),