For the application, I create the functionality of editing todo item. Item has two fields: title and description. When you click on an item, it opens a modal where you can edit it. How to correctly write a function to the save button so that it saves the changes and displays them?
My Provider:
class ListModel extends ChangeNotifier {
List<EventModel> eventList = [];
void addEventToList() {
EventModel eventModel = EventModel(
title: 'Event title ${eventList.length + 1}',
detail: 'Event text ${eventList.length + 1}',
id: '${eventList.length + 1}',
);
eventList.add(eventModel);
notifyListeners();
}
EventModel? getEvent(String? id) {
return eventList.firstOrNullWhere((event) => event.id == id);
}
}
My modal window:
class EditEventBottomSheet extends StatelessWidget {
final EventModel event;
const EditEventBottomSheet({Key? key, required this.event}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: 300,
color: Colors.amber,
child: Center(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Change Event',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
TextFormField(
initialValue: event.title,
),
const SizedBox(height: 10),
TextFormField(
initialValue: event.detail,
),
const SizedBox(height: 10),
ElevatedButton(
child: const Text('Save Edits'),
onPressed: () {},
),
ElevatedButton(
child: const Text('Close BottomSheet'),
onPressed: () => Navigator.pop(context),
)
],
),
),
),
);
}
}
You should have an EditingControllers for each textField,
so, define the following before your build method
late final titleController = TextEditingController(text: event.title);
late final detailController = TextEditingController(text: event.detail);
and inside your build method
final provider = Provider.of<ListModel>(context);
TextFormField(
controller: titleController,
),
const SizedBox(height: 10),
TextFormField(
controller: detailController,
),
const SizedBox(height: 10),
ElevatedButton(
child: const Text('Save Edits'),
onPressed: () {
provider.editTodoItem(event.id, titleController.text, detailController.text);
},
),
And your editTodoItem should be something like:
void editTodoItem(final String id, final String title, final String details){
//Add a validation if you want, for example title & details must not be empty
if(valid){
final indexOfOld = eventList.map((e) => e.id).toList().indexOf(id);
if (indexOfOld == -1) return;
eventList.removeAt(indexOfOld);
eventList.insert(indexOfOld, EventModel(
title: title,
detail: details,
id: '$indexOfOld',
));
notifyListeners();
}
}
That is one of the ways of editing, (Replace the object)
Also Instead of removing the object and creating a new one with the new values and insert it in old's one index, you can do the following
if(valid){
final event = eventList.firstWhere((e) => e.id == id);
event.title = title;
event.detail = details;
notifyListeners();
}
but to be honest, the second way might not update your UI, It's been a long time since I used providers
Hope this answer helps.
Related
I'm creating a Flutter Widget and when I try to remove an item from the list I'm using, it always removes the last one, I was thinking it could be a Key problem, but nothing suits it, do anyone know how I could solve this?
The code
create_game.dart
import 'package:flutter/material.dart';
import 'package:pontinho/components/custom_input.dart';
class CreateGame extends StatefulWidget {
const CreateGame({super.key});
#override
State<CreateGame> createState() => _CreateGameState();
}
class _CreateGameState extends State<CreateGame> {
List<String> names = [''];
void changeName(int nameIndex, String change) {
setState(() {
names[nameIndex] = change;
});
}
void removeName(int nameIndex) {
print(names);
print(nameIndex);
setState(() {
names.removeAt(nameIndex);
});
}
ListView createNamesInput() {
return ListView.builder(
itemCount: names.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return ListTile(
key: ObjectKey(index),
title: CustomInput(
key: ObjectKey(index),
labelText: "Nome",
onChanged: (String changed) => changeName(index, changed),
text: names[index],
onRemoved: () => removeName(index),
),
);
},
);
// return names
// .asMap()
// .entries
// .map((el) => CustomInput(
// key: ObjectKey('${el.key}'),
// labelText: "Nome",
// onChanged: changeName,
// index: el.key,
// text: names[el.key],
// onRemoved: removeName,
// ))
// .toList();
}
void addName() {
setState(() {
names.add('');
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: GestureDetector(
onTap: (() => Navigator.pop(context)),
child: const Icon(
Icons.arrow_back,
color: Colors.black,
size: 40,
),
),
backgroundColor: Colors.white,
titleTextStyle: const TextStyle(
color: Colors.black,
fontSize: 20,
),
title: const Text("CRIE SEU JOGO"),
),
body: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 16,
),
// child: createNamesInput(),
child: Column(
children: [
createNamesInput(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: addName,
child: Row(
children: const [
Icon(Icons.add),
Text('Adicionar Jogador'),
],
),
),
],
),
),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: () => print('Iniciar!'),
child: const Text('Iniciar!'),
),
)
],
),
),
);
}
}
custom_input.dart
import 'package:flutter/material.dart';
typedef OneArgumentCallback = void Function(String changed);
class CustomInput extends StatefulWidget {
final OneArgumentCallback onChanged;
final VoidCallback onRemoved;
final String labelText;
final String text;
const CustomInput({
super.key,
required this.onChanged,
required this.labelText,
required this.text,
required this.onRemoved,
});
#override
State<CustomInput> createState() => _CustomInputState();
}
class _CustomInputState extends State<CustomInput> {
late final TextEditingController inputController;
#override
void initState() {
super.initState();
inputController = TextEditingController(text: widget.text);
}
void changeContent(String value) {
widget.onChanged(
value,
);
}
#override
Widget build(BuildContext context) {
return TextFormField(
key: widget.key,
controller: inputController,
textDirection: TextDirection.ltr,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: widget.labelText,
suffixIcon: IconButton(
onPressed: () => widget.onRemoved(),
icon: const Icon(
Icons.close,
color: Colors.red,
),
),
),
autocorrect: false,
onChanged: (value) => changeContent(value),
);
}
}
Indeed it is a key issue, you have to create a combined key that must be unique for each item, I merged the index with names[index],
CustomInput(
key: ObjectKey('$index:${names[index]}'),
labelText: "Nome",
onChanged: (String changed) => changeName(index, changed),
text: names[index],
onRemoved: () => removeName(index),
),
note that if you try this code alone the textfield will lose focus because the key has changed, this will be solved by removing the setState inside the onChange
void changeName(int nameIndex, String change) {
names[nameIndex] = change;
}
here you don't need setState because the UI will be updated by default when you are typing in the textfield
I hope I made it clear
I was thinking it could be a Key problem
That's correct; You need to use names[index] as the value for your Key:
ListTile(
key: ObjectKey(names[index]),
title: CustomInput(
I am working on creating a signup process with multiple screens. For now I have first name and age screen. I have the following usermodel. The idea is whenever I update one of the field, it triggers rebuild of the signup-screen, so that the next item in sign-up is shown. I have 2 questions:
Can I pass around the setter from the user model, like I did below. Or is there a better way.
I am getting the following error when I click the 'Next_na' button inorder to add the first name
LateInitializationError: Field 'firstName' has not been initialized.
Thank you for your help in advance!
class UserModel extends ChangeNotifier {
late String firstName;
late int userAge;
int indexer = 0;
set addFName(String firstName) {
firstName = this.firstName;
indexer = indexer + 1;
notifyListeners();
}
String get fName {
return firstName;
}
set addUAge(int userAge) {
userAge = this.userAge;
indexer = indexer + 1;
notifyListeners();
}
int get uAge {
return userAge;
}
}
The signup screen is as follows
#override
Widget build(BuildContext context) {
return Scaffold(
body: ChangeNotifierProvider(
create: (context) => UserModel(),
child: Center(
child: Padding(
padding: const EdgeInsets.all(18.0),
child: Consumer<UserModel>(builder: (context, user, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"Sign Up",
style: TextStyle(fontSize: 24),
textAlign: TextAlign.center,
),
const Divider(
height: 30,
endIndent: 20,
indent: 20,
thickness: 1.5,
color: Colors.grey,
),
const SizedBox(
height: 30,
),
if (user.indexer == 0)
FirstNameWidget(
user: user,
)
else
FirstNameWidget(
user: user,
)
],
);
}),
)),
));
}
and the 'firstNameWidget' that shows the first name field and the 'next' button is as follows:
class FirstNameWidget extends StatelessWidget {
FirstNameWidget({Key? key, required this.user}) : super(key: key);
final UserModel user;
final firstNameController = TextEditingController();
#override
Widget build(BuildContext context) {
return Form(
//key: _firstNameFormKey,
child: Column(
children: [
TextFormField(
keyboardType: TextInputType.text,
controller: firstNameController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter First Name',
),
//autovalidateMode: AutovalidateMode.onUserInteraction,
),
const SizedBox(
height: 20,
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.8,
child: ElevatedButton(
onPressed: () {
print(firstNameController.text);
commitFirstName(fName: firstNameController.text, user: user);
//user.addFName = firstNameController.text;
},
child: const Text('Next_na'),
),
),
],
),
);
}
}
void commitFirstName({required String fName, required UserModel user}) {
user.addFName = fName;
}
You did a mistake in your setter:
firstName = this.firstName;
You assign the class member (which has not been initialized yet) to your firstName param which comes from the TextEditingController.
If you turn this around it should work:
this.firstName = firstName;
I have a code that is responsible for filtering by certain categories (I shortened it for ease of reading). When opening the filter window, the user sees these category names ('Select a brand', 'Select a operation system', 'Select a color' etc).
Next, the user can open the category (initially, the dropdown list is in the closed position.), and select the parameters from the drop-down list (and click the apply button). The next time you open the filter window, the checkboxes in front of the parameters remain, but the drop-down list collapses.
Tell me how to do it: if in any category there are options marked with a checkmark, so that the drop-down list will be open the next time the window with filters is opened.
class FilterDialog extends StatefulWidget {
final void Function(Map<String, List<String>?>) onApplyFilters;
final Map<String, List<String>?> initialState;
const FilterDialog({
Key? key,
required this.onApplyFilters,
this.initialState = const {},
}) : super(key: key);
#override
State<FilterDialog> createState() => _FilterDialogState();
}
class _FilterDialogState extends State<FilterDialog> {
// Temporary storage of filters.
Map<String, List<String>?> filters = {};
bool needRefresh = false;
// Variable for the ability to hide all elements of filtering by any parameter.
bool isClickedBrand = false;
List manufacturer = [];
#override
void initState() {
super.initState();
filters = widget.initialState;
}
// A function to be able to select an element to filter.
void _handleCheckFilter(bool checked, String key, String value) {
final currentFilters = filters[key] ?? [];
if (checked) {
currentFilters.add(value);
} else {
currentFilters.remove(value);
}
setState(() {
filters[key] = currentFilters;
});
}
// Building a dialog box with filters.
#override
Widget build(BuildContext context) {
return SimpleDialog(
// Window title.
title: const Text('Filters',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.w600,
)),
contentPadding: const EdgeInsets.all(16),
// Defining parameters for filtering.
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Here and in subsequent Column, there will be a definition of parameters for filtering,
// a title, the ability to hide/show the items of list
Column(children: [
InkWell(
onTap: () async {
manufacturer = await getManufacturerOptions();
setState(() {
isClickedBrand = !isClickedBrand;
});
},
child: Row(children: [
Text('Select a brand'.toString(),
style: const TextStyle(
fontSize: 18,
)),
const Spacer(),
isClickedBrand
? const Icon(Icons.arrow_circle_up)
: const Icon(Icons.arrow_circle_down)
])),
!isClickedBrand
? Container()
: Column(
children: manufacturer
.map(
(el) => CustomCheckboxTile(
value: filters['manufacturer']?.contains(el) ??
false,
label: el,
onChange: (check) =>
_handleCheckFilter(check, 'manufacturer', el),
),
)
.toList())
]),
const SizedBox(
height: 5,
),
// Building a button to apply parameters.
const SizedBox(
height: 10,
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
widget.onApplyFilters(filters);
needRefresh = true;
},
child:
const Text('APPLY', style: TextStyle(color: Colors.black)),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.grey),
)),
// Building a button to reset parameters.
const SizedBox(
height: 5,
),
ElevatedButton(
onPressed: () async {
setState(() {
filters.clear();
});
widget.onApplyFilters(filters);
},
child: const Text('RESET FILTERS',
style: TextStyle(color: Colors.black)),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.grey),
)),
],
),
],
);
}
}
For example: the user clicks on the filter box, selects the brands to search for, and clicks the apply button. My task is that the next time the user opens the filter window, the categories with active checkboxes (in this example, the brand) are in an expanded state
The concept is, you need to check filter data with while opening dialog, To simplify the process I am using ExpansionTile. You can check this demo and customize the behavior and look.
Run on dartPad, Click fab to open dialog and touch outside the dialog to close this.
class ExTExpample extends StatefulWidget {
ExTExpample({Key? key}) : super(key: key);
#override
State<ExTExpample> createState() => _ExTExpampleState();
}
class _ExTExpampleState extends State<ExTExpample> {
// you can use map or model class or both,
List<String> filter_data = [];
List<String> brands = ["Apple", "SamSung"];
List<String> os = ["iOS", "Android"];
_showFilter() async {
await showDialog(
context: context,
builder: (c) {
// you can replace [AlertDialog]
return AlertDialog(
content: StatefulBuilder(
builder: (context, setSBState) => SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ExpansionTile(
title: const Text("Brand"),
/// check any of its's item is checked or not
initiallyExpanded: () {
// you can do different aproach
for (final f in brands) {
if (filter_data.contains(f)) return true;
}
return false;
}(),
children: [
...brands.map(
(brandName) => CheckboxListTile(
value: filter_data.contains(brandName),
title: Text(brandName),
onChanged: (v) {
if (filter_data.contains(brandName)) {
filter_data.remove(brandName);
} else {
filter_data.add(brandName);
}
setSBState(() {});
//you need to reflect the main ui, also call `setState((){})`
},
),
),
],
),
ExpansionTile(
title: const Text("select OS"),
/// check any of its's item is checked or not
initiallyExpanded: () {
// you can do different aproach
for (final f in os) {
if (filter_data.contains(f)) return true;
}
return false;
}(),
children: [
...os.map(
(osName) => CheckboxListTile(
value: filter_data.contains(osName),
title: Text(osName),
onChanged: (v) {
if (filter_data.contains(osName)) {
filter_data.remove(osName);
} else {
filter_data.add(osName);
}
setSBState(() {});
//you need to reflect the main ui, also call `setState((){})`
},
),
),
],
),
],
),
),
),
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FloatingActionButton(
onPressed: () {
_showFilter();
},
),
),
);
}
}
Whenever you open filter the isClickedBrand is False so it won't showed you a list. So the solution is :
After selecting option from list, change the state of isClickedBrand state. I mean if it's true then it will show the list otherwise show container.
Hope you get my point.
I am Developing Cart Page for e com application. backend is firebase real time database. In cart Page Price Details is calculating when the page load. But when user change the quantity of cart item, It is going to update the cart. But the problem is Cant Calculate the Price Details On this time. When I reopen the page, Price Details is Calculating According to new Cart Quantity. But How can I can do that without re opening the Page
1.This is my cart_page.dart file
//this cart is show when user select my cart From the navigation drawer
import 'package:flutter/material.dart';
import 'package:buy_app_flutter/main.dart';
import 'package:buy_app_flutter/pages/notification_page.dart';
import 'package:buy_app_flutter/pages/search_page.dart';
import 'package:buy_app_flutter/componets/cart_products.dart';
import 'BuyNowDeliveryDetailsPage.dart';
import 'HomePage.dart';
class CartPage extends StatefulWidget {
#override
_CartPageState createState() => _CartPageState();
}
class _CartPageState extends State<CartPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: new DrawerCodeOnly(),
appBar: new AppBar(
elevation: 0.1,
backgroundColor: Colors.orangeAccent,
title: Text("My Cart"),
actions:<Widget> [
new IconButton(icon: Icon(Icons.search,color: Colors.white,), onPressed: (){
showSearch(context: context, delegate: DataSearch());
} ),
new IconButton(icon: Icon(Icons.notifications,color: Colors.white,), onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => new NotificationPage() ));
} ),
],
),
body:new Cart_products(),
bottomNavigationBar:new Container(
color: Colors.white,
child: Row(
children:<Widget> [
Expanded(child: ListTile(
title: new Text("Total:"),
subtitle:new Text("Rs 00.00") ,
)),
Expanded(
child: new MaterialButton(onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => new BuyNowDeliveryDetailsActivity() ));
},
child: new Text("Check Out"),
color: Colors.orangeAccent,
)
)
],
),
) ,
);
}
}
2.This is cart_procucts.dart file
import 'package:buy_app_flutter/DBModels/CartModel.dart';
import 'package:buy_app_flutter/DBModels/ProductModel.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
class Cart_products extends StatefulWidget {
#override
_Cart_productsState createState() => _Cart_productsState();
}
class _Cart_productsState extends State<Cart_products> {
//firebase database reference to cart===============================
final Cartref= FirebaseDatabase.instance.reference().child("UserData").child(FirebaseAuth.instance.currentUser.uid).child("Cart");
List<CartModel> Products_on_the_cart=List();
///=============================================firebase end
bool _progressController=true;
//variables to cal total
int TotalItemPrice=0;
int SavedAmount=0;
int DeliveryPrice=0;
int TotalAmount=0;
#override
initState(){
_getCartList(); //call function to get cart from firebase
super.initState();
}
///display the cart Items one by one===============================
#override
Widget build(BuildContext context) {
if(_progressController==true) //reading database
{
return Center(child: new CircularProgressIndicator());
}
else{ //db read end=========
if(Products_on_the_cart.isNotEmpty)
{
_calTotalPrice();
//===============================================if not empty the cart
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListView.separated(
itemBuilder: (context, index){
return Single_cart_product(
cart_product_key: Products_on_the_cart[index].productKey,
cart_product_name: Products_on_the_cart[index].ProductTitle,
cart_prod_picture: Products_on_the_cart[index].Img,
cart_prod_price: Products_on_the_cart[index].Price,
cart_prod_cutted_price: Products_on_the_cart[index].CuttedPrice,
cart_prod_qty: Products_on_the_cart[index].qty,
cart_product_status: Products_on_the_cart[index].Status,
);
},
//end of item builder
separatorBuilder:(context,index){
return Row();
},
itemCount: Products_on_the_cart.length,
shrinkWrap: true,
),
Divider(),
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text("PRICE DETAILS",style: TextStyle(fontWeight: FontWeight.bold,color: Colors.grey),),
),
Divider(),
///start of items total price row===========================
Padding(
padding: const EdgeInsets.only(left: 8.0,right: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("price (${Products_on_the_cart.length} items)",style: TextStyle(fontWeight: FontWeight.normal,color: Colors.black),),
Text("Rs ${TotalItemPrice}/-",style: TextStyle(fontWeight: FontWeight.normal,color: Colors.black),),
],
),
),
///end of items total price row============================
///start of delivery price row==================================
Padding(
padding: const EdgeInsets.only(left: 8.0,top: 8.0,right: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Delivery",style: TextStyle(fontWeight: FontWeight.normal,color: Colors.black),),
Text("Rs ${DeliveryPrice}/-",style: TextStyle(fontWeight: FontWeight.normal,color: Colors.green),),
],
),
),
///end of delivery price row============================
Divider(),
///Start of total amount row==================================
Padding(
padding: const EdgeInsets.only(left: 8.0,right: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Total Amount",style: TextStyle(fontWeight: FontWeight.bold,color: Colors.black),),
Text("Rs ${TotalAmount}/-",style: TextStyle(fontWeight: FontWeight.bold,color: Colors.black),),
],
),
),
///end of total amount row===========================================
Divider(),
Padding(
padding: const EdgeInsets.only(left: 8.0,right: 8.0),
child: Text("You saved Rs ${SavedAmount}/- on this order",style: TextStyle(fontWeight: FontWeight.normal,color: Colors.green),),
),
],
);
//end of if not empty the cart===========================================
}
else
{
return Center(child: Text("Your cart is empty !"));
}
}
}
///end of displaying cart items one by one====================================
_getCartList()
async {
//get cart data from firebase==========
try{
await Cartref.once().then((DataSnapshot snapshot) {
var data = snapshot.value;
if(data==null) //if the no cart items found hide progress
{
setState(() {
_progressController=false;
});
}
Products_on_the_cart.clear();
data.forEach((key,value)
async {
CartModel model = new CartModel(
date: value["date"],
time: value["time"],
productKey: value["productKey"],
qty: value["qty"],
);
//use Pref(product ref) to get details from products record
try{
//Product details ref and list
final Pref= FirebaseDatabase.instance.reference().child("Product");
List<ProductModel> product =List();
//====================================
await Pref.child(model.productKey).once().then((DataSnapshot snap) {
// print('Product : ${snap.value}');
model.ProductTitle=snap.value["ProductTitle"];
model.Img=snap.value["img1"];
model.Price=snap.value["Price"];
model.DPrice=snap.value["DPrice"];
model.CuttedPrice=snap.value["CuttedPrice"];
model.Status=snap.value["Status"];
});
}
catch(e)
{
print("Error In try block for get data from real time db ");
print(e);
}
finally
{
setState(() {
_progressController=false;
});
}
//========================================================end of get product details
setState(() {
Products_on_the_cart.add(model);
});
}
);
});
}
catch(e)
{
print(e);
}
// end of get cart data from firebase
}
///function for calculate total price
_calTotalPrice(){
TotalItemPrice=0;
DeliveryPrice=0;
TotalAmount=0;
SavedAmount=0;
Products_on_the_cart.forEach((item){
//set total item price===========
TotalItemPrice=TotalItemPrice+int.parse(item.Price)*int.parse(item.qty);
//set delivery price==========
if (item.DPrice=="FREE")
{
//nothing
}
else
{
if (int.parse(item.DPrice)>DeliveryPrice)
{
DeliveryPrice=int.parse(item.DPrice);
}
}
//set Total amount=======
TotalAmount=TotalItemPrice+DeliveryPrice;
//set saved amount
SavedAmount=SavedAmount + (int.parse(item.CuttedPrice)-int.parse(item.Price))*int.parse(item.qty);
});
}
/// end of calculate total price function
}
///from here design the cart item display ==========================================
class Single_cart_product extends StatefulWidget {
final cart_product_key;
final cart_product_name;
final cart_prod_picture;
final cart_prod_price;
final cart_prod_cutted_price;
String cart_prod_qty;
final cart_product_status;
Single_cart_product({
this.cart_product_key,
this.cart_product_name,
this.cart_prod_picture,
this.cart_prod_price,
this.cart_prod_cutted_price,
this.cart_prod_qty,
this.cart_product_status,
});
#override
_Single_cart_productState createState() => _Single_cart_productState();
}
class _Single_cart_productState extends State<Single_cart_product> {
///==========================start of Cart Item Display Design==================================================================
#override
Widget build(BuildContext context) {
var cartQty=int.parse(widget.cart_prod_qty);
return Card(
child: Column(
children: [
ListTile(
///===============START LEADING SECTION ===============================
leading: FadeInImage.assetNetwork(
placeholder: 'assets/close_box.png',
image:widget.cart_prod_picture,
height: 80.0,
width: 80.0,
fit: BoxFit.contain,
),
///=======END OF LEADING SECTION=======================================
/// =========== TITLE SECTION =========================================
title: new Text(widget.cart_product_name),
///====END OF TITLE SECTION============================================
///========== SUBTITLE SECTION ==================================
subtitle: new Column(
children:<Widget> [
//Row inside the column
new Row(
children: <Widget> [
// =============this section is for the Product price =====
new Container(
alignment: Alignment.topLeft,
child: new Text("\Rs.${widget.cart_prod_price}",
style: TextStyle(
fontSize:16.0,
fontWeight: FontWeight.bold,
color: Colors.black
),
),
),
SizedBox(width: 4,),
//==========this section is for the cutted price of the product=========
Padding(
padding: const EdgeInsets.all(5.0),
child: new Text("\Rs.${widget.cart_prod_cutted_price}",
style: TextStyle(
fontSize:16.0,
fontWeight: FontWeight.normal,
decoration: TextDecoration.lineThrough,
color: Colors.grey
),
),
),
//end of cutted price of the product
],
),
],
),
///=====END OF SUBTITLE SECTION============================================
///======START OF TRAILING SECTION==========================================
trailing:
Column(
children:
<Widget>[
Expanded(child: new IconButton(
padding: const EdgeInsets.all(0.0),
icon: Icon(Icons.arrow_drop_up), onPressed: (){
cartQty++;
print(cartQty);
_updateCartQty(widget.cart_product_key,cartQty.toString()); //call to update cart qty
setState(() {
widget.cart_prod_qty=cartQty.toString();
});
})),
Padding(
padding: const EdgeInsets.all(0.0),
child: new Text(cartQty.toString()),
),
Expanded(child: new IconButton(
padding: const EdgeInsets.all(0.0),
icon: Icon(Icons.arrow_drop_down), onPressed: (){
if(cartQty>1)
{
cartQty--;
print(cartQty);
_updateCartQty(widget.cart_product_key,cartQty.toString()); //call to update cart qty
setState(() {
widget.cart_prod_qty=cartQty.toString();
});
}
})),
],
),
///==END OF TRAILING======================================================================
),
//REMOVE BTN AND STATUS
Row(
children: [
new IconButton(
icon: Icon(Icons.delete),
color:Colors.red ,
onPressed: (){}),
Padding(
padding: const EdgeInsets.all(4.0),
child: new Text(widget.cart_product_status,style: TextStyle(color: Colors.orangeAccent)),
),
],
)
//END OF REMOVE BTN SND STATUS
],
),
);
}
///==========================end of Cart Item Display Design==================================================================
/// function for update cart qty
_updateCartQty(String Pkey,String Qty){
try {
final Cartref = FirebaseDatabase.instance.reference()
.child("UserData")
.child(FirebaseAuth.instance.currentUser.uid)
.child("Cart");
Cartref.child(Pkey).child("qty").set(Qty);
}
catch(Ex)
{
Fluttertoast.showToast(
msg: Ex.message,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0);
}
}
}
3.After that I need to view the calculated total amount in Bottom of the cart page
Updating product quantity value in Cart_products class:
You need to create a variable final ValueChanged<int> onProdQty; in Single_cart_product class and then where the quantity value changes use the callback like this widget.onProdQty?.call(here Pass the new quantity value). Also one last step is that in class Cart_products where you are calling Single_cart_product() add this in argument onProdQty: (value) => setState(() { Products_on_the_cart[index].qty = value; }),. By doing this whenever the callback is called the setstate would run in Cart_products class and wherever Products_on_the_cart[index].qty is used it would get updated.
Updating product quantity value in CartPage class:
Note: I'm not sure if these steps would work corectly as I have limited information about your project. But you will surely learn how to pass data around using callbacks:
Follow steps of Updating product quantity value in Cart_products
class and then continue.
Add final ValueChanged<int> onProdQty; in Cart_products class and
then change Single_cart_product() argument in class Cart_products
like this:
Single_cart_product(onProdQty: (value) => setState((){
Products_on_the_cart[index].qty = value;
widget.onProdQty?.call(value); //add this
}),)
Then add List<CartModel> Products_on_the_cart=List(); in the
_CartPageState class in cart_page.dart and at last in the body: Cart_products() make these changes:
Cart_products(onProdQty: (value) => setState((){
Products_on_the_cart[index].qty = value;
}),)
Now use Products_on_the_cart[index].qty in bottomNavigationBar.
Please let me know if you have any further queries.
I am having a problem with SharedPreferences and multiple Modules I am generating on a Form using ListView.builder
The form is basically asking for some parents details and their childs details - by default the form assumes the parent has one child, but more can be added by clicking a button. The ChildModule has the ability to "close" but when "re-opened" the data doesn't persist, hence using SharedPreferences, it works fine with one Child, but once a second child is added it seems to be creating multiple Instances of SharedPreferences.
I have cut out everything to show what I am trying to achieve. NOTE this is being used as a Web App if it matters.
Oh and ChildModule needs to have its own state because it has a widget which requires it (not shown)
ENQUIRY FORM
final GlobalKey<FormBuilderState> _enquiryFormKey = GlobalKey<FormBuilderState>();
class EnquiryForm extends StatefulWidget {
static List<int> numberOfChildren = [1];
#override
_EnquiryFormState createState() => _EnquiryFormState();
}
class _EnquiryFormState extends State<EnquiryForm> {
int defaultNumberOfChildren = 1;
removeModule(){
setState(() {});
}
#override
Widget build(BuildContext context) {
return FormBuilder(
key: _enquiryFormKey,
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: CustomTextField(
label: 'Parent First Name',
isRequired: true,
),
),
),
//ChildModuleList
ListView.builder(
shrinkWrap: true,
itemCount: EnquiryForm.numberOfChildren.length,
itemBuilder: (context,int index){
return ChildModule(EnquiryForm.numberOfChildren[index], removeModule);
}
),
SizedBox(height: 20,),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
ThemedButton(
onPressed: (){
setState(() {
defaultNumberOfChildren++;
EnquiryForm.numberOfChildren.add(defaultNumberOfChildren);
});
},
child: Text(
'Add Additional Child',
style: TextStyle(color: Colors.white),)
),
SizedBox(width: 10,),
ThemedButton(
onPressed: (){},
child: Text('Enquire Now', style: TextStyle(color: Colors.white),))
],
)
],
));
}
}
CHILD MODULE
class ChildModule extends StatefulWidget {
final int number;
final Function() callback;
ChildModule(this.number,this.callback);
#override
_ChildModule createState() => _ChildModule();
}
class _ChildModule extends State<ChildModule> {
SharedPreferences childModuleData;
String firstName;
bool loading = true;
bool isOpen;
#override
void initState() {
print('this module number is ${widget.number}');
_spInstance();
isOpen = true;
super.initState();
}
Future<void> _spInstance() async {
if(childModuleData == null && widget.number == 1) {
childModuleData = await SharedPreferences.getInstance();
print('got instance');
} else {
print('broken');
print(childModuleData);
};
String _testValue = childModuleData.getString('Child First Name');
if(_testValue == null){
childModuleData.setString('Child First Name', '');
loading = false;
} else {
childModuleData.clear();
_spInstance();
}
}
#override
Widget build(BuildContext context) {
return loading ? Loading() : Column(
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
if (isOpen == false) {
isOpen = true;
} else {
isOpen = false;
}
});
},
child: Container(
height: 40,
padding: EdgeInsets.only(left: 10, right: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
color: Colors.blue[50],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
'Child Details',
style: TextStyle(color: Colors.blue[300], fontSize: 16),
),
Row(
children: <Widget>[
isOpen
? Icon(
Icons.arrow_drop_down,
size: 30,
)
: Transform.rotate(
angle: math.pi / 2,
child: Icon(
Icons.arrow_drop_down,
size: 30,
),
),
widget.number > 1
? IconButton(icon: Icon(Icons.clear), onPressed: () async {
await FormFunctions().removeModule(widget.number, EnquiryForm.numberOfChildren);
widget.callback();
})
: Container(),
],
),
],
),
),
),
AnimatedContainer(
duration: Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
padding: EdgeInsets.fromLTRB(10, 5, 10, 5),
height: isOpen ? null : 0,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
CustomTextField(
label: fieldFirstName,
isRequired: true,
initalValue: childModuleData.getString(fieldFirstName) ?? '',
onChanged: (value){
childModuleData.setString(fieldFirstName, value);
},
),
],
),
],
),
),
],
);
}
}
CONSOLE ERROR AFTER SECOND MODULE IS CREATED
Launching lib/main.dart on Chrome in debug mode...
Syncing files to device Chrome...
Debug service listening on ws://127.0.0.1:58490/EkMAy9CGY74=
Debug service listening on ws://127.0.0.1:58490/EkMAy9CGY74=
this module number is 1
got instance
Instance of 'SharedPreferences'
null
this module number is 2 //Number 2 Module is created
broken
null
null
TypeError: Cannot read property 'getString' of null
at child_module._ChildModule.new._spInstance$ (http://localhost:58433/packages/webenrol/shared/widgets/day_button.dart.lib.js:3704:47)
at _spInstance$.next (<anonymous>)
at runBody (http://localhost:58433/dart_sdk.js:43121:34)
at Object._async [as async] (http://localhost:58433/dart_sdk.js:43149:7)
at child_module._ChildModule.new.[_spInstance] (http://localhost:58433/packages/webenrol/shared/widgets/day_button.dart.lib.js:3694:20)
at child_module._ChildModule.new.initState (http://localhost:58433/packages/webenrol/shared/widgets/day_button.dart.lib.js:3689:24)
at framework.StatefulElement.new.[_firstBuild] (http://localhost:58433/packages/flutter/src/widgets/widget_span.dart.lib.js:41219:58)
at framework.StatefulElement.new.mount (http://localhost:58433/packages/flutter/src/widgets/widget_span.dart.lib.js:12605:24)
at framework.SingleChildRenderObjectElement.new.inflateWidget (http://localhost:58433/packages/flutter/src/widgets/widget_span.dart.lib.js:11420:16)
in your case the concern is at the level of recording data in shared preferences. one solution would be to add the widget number in the key to save like this (await SharedPreferences.getInstance()).setString("Your Key${widgetNumber}");
and edit your function _spInstance
class ChildModule extends StatefulWidget {
final int number;
final Function() callback;
ChildModule(this.number,this.callback);
#override
_ChildModule createState() => _ChildModule();
}
class _ChildModule extends State<ChildModule> {
SharedPreferences childModuleData;
...
Future<void> _spInstance() async {
if(childModuleData == null) {
childModuleData = await SharedPreferences.getInstance();
print('got instance');
}
String _testValue = childModuleData.getString('Child First Name${widget.number}');
//NB: do not clear data on chlidModuleData any more.
...
}
...
}