I was making a simple cart app, it did well but cart count not showing when app is closed and reopened again.
I am using provider and calls fetchCartProducts() method when the app is opened. It calls fine. but cart badge widget itemcount is not changing at first time. only shows 0 at first time.
Future<void> fetchCartProducts() async {
final dataList = await DBHelper.getData('cart_food');
//convert dataList to _cartItems
final entries = dataList
.map((item) => CartModel(
item['id'],
item['price'].toDouble(),
item['productName'],
item['quantity'],
))
.map((cart) => MapEntry(cart.id, cart));
_cartItems = Map<String, CartModel>.fromEntries(entries);
print('inside fetchcart');
}
class HomeScreen extends StatefulWidget
{
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen>
{
Future<List<FoodItem>> _foodItems;
var _isInit = true;
#override
void initState() {
super.initState();
_foodItems = ApiService.getFoodItems();
Provider.of<CartProvider>(context, listen: false).fetchCartProducts();
setState(() {});
}
#override
void didChangeDependencies()
{
if (_isInit) {
Provider.of<CartProvider>(context).fetchCartProducts();
_isInit = false;
setState(() {});
}
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
final cart = Provider.of<CartProvider>(context, listen: false);
return Scaffold(
appBar: AppBar(
title: const Text('Food Cart'),
actions: [
//this is not updating when the app is closed and opened again.
Consumer<CartProvider>(
builder: (_, cartprovider, ch) => Badge(
child: ch,
value: cartprovider.itemCount.toString(),
),
child: IconButton(
icon: Icon(Icons.shopping_cart),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) {
return CartScreen();
}),
);
},
),
),
],
),
body: FutureBuilder<List<FoodItem>>(
future: _foodItems,
builder: (conext, snapshot) => !snapshot.hasData
? const Center(
child: CircularProgressIndicator(),
)
: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
FoodItem foodItem = snapshot.data[index];
return ListTile(
title: Text(foodItem.productName),
subtitle: Text(foodItem.variant),
trailing: IconButton(
onPressed: () {
cart.addToCart(
foodItem.storeid.toString(),
foodItem.productName,
1,
foodItem.price,
);
setState(() {});
},
icon: const Icon(Icons.shopping_cart),
),
);
},
),
),
);
}
}
otherwise when item added to cart, it working fine. the data loss when reopened. how to get total count when the app starts?
In order to rebuild Consumer you need to call notifyListeners() inside your CartProvider
Add notifyListeners() to your fetchCartProducts() after assigning the value to _cartItems = Map<String, CartModel>.fromEntries(entries);
Future<void> fetchCartProducts() async {
final dataList = await DBHelper.getData('cart_food');
//convert dataList to _cartItems
final entries = dataList
.map((item) => CartModel(
item['id'],
item['price'].toDouble(),
item['productName'],
item['quantity'],
))
.map((cart) => MapEntry(cart.id, cart));
_cartItems = Map<String, CartModel>.fromEntries(entries);
notifyListeners(); // <------- this line
print('inside fetchcart');
}
Related
In my app the user is able to store favorite items on a different page in the bottom navigation bar. My problem is that the page does not refresh properly. If you add a favorite it gets displayed only when restarting the app.
If the favorites page is in the same widget hierarchy of the respective bottomnavitem the function works fine.
https://pastebin.com/nZ2jrLqK
class Favorites extends StatefulWidget {
const Favorites({Key? key}) : super(key: key);
#override
_FavoritesState createState() => _FavoritesState();
}
class _FavoritesState extends State<Favorites> {
// ignore: prefer_typing_uninitialized_variables
var database;
List<Mechanism> people = <Mechanism>[];
Future initDb() async {
database = await openDatabase(
join(await getDatabasesPath(), 'person_database.db'),
onCreate: (db, version) {
return db.execute(
"CREATE TABLE person(id INTEGER PRIMARY KEY, name TEXT, height TEXT, mass TEXT, hair_color TEXT, skin_color TEXT, eye_color TEXT, birth_year TEXT, gender TEXT)",
);
},
version: 1,
);
getPeople().then((value) {
setState(() {
people = value;
});
});
}
Future<List<Mechanism>> getPeople() async {
final Database db = await database;
final List<Map<String, dynamic>> maps = await db.query('person');
return List.generate(maps.length, (i) {
return Mechanism(
id: maps[i]['id'],
name: maps[i]['name'] as String,
);
});
}
#override
void initState() {
super.initState();
initDb();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: backGround,
appBar: AppBar(
backgroundColor: appbarColor,
title: const Text("Favorites"),
),
body: ListView.builder(
itemCount: people.length,
itemBuilder: (context, index) {
var person = people[index];
return ListTile(
title: Text(
person.name,
style: const TextStyle(color: titleColor),
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
MechanismDtl(mechanism: person, id: index)),
);
},
);
}),
);
}
}
Edit: page where the user can store the items
class MarkFavs extends StatefulWidget {
const MarkFavs({Key key}) : super(key: key);
#override
_MarkFavsState createState() => _MarkFavsState();
}
class _MarkFavsState extends State<MarkFavs> {
TextEditingController searchController = TextEditingController();
List<People> shownList = <People>[
People(name: 'Test', id: 1),
People(name: 'Test2', id: 2),
People(name: 'Test3', id: 3)
];
List<People> initialData = <People>[
People(name: 'Test', id: 1),
People(name: 'Test2', id: 2),
People(name: 'Test3', id: 3)
];
void queryPeople(String queryString) {
if (kDebugMode) {
print("queryString = $queryString");
}
setState(() {
shownList = initialData.where((string) {
if (string.name.toLowerCase().contains(queryString.toLowerCase())) {
return true;
} else {
return false;
}
}).toList();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: backGround,
appBar: AppBar(
backgroundColor: appbarColor,
title: const Text('Detail'),
),
body: Column(
children: <Widget>[
TextButton.icon(
label: const Text('Favorites'),
icon: const Icon(
Icons.storage,
color: titleColor,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const Favorites()),
);
},
),
Expanded(
child: PeopleList(
people: shownList,
),
),
],
),
);
}
}
class PeopleList extends StatelessWidget {
final List<People> people;
const PeopleList({Key key, this.people}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: people.length,
itemBuilder: (context, index) {
var person = people[index];
var name = person.name;
return ListTile(
title: Text(
name,
style: const TextStyle(color: titleColor),
),
onTap: () {
person.id = index;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
MechanismDtl(mechanism: person, id: index)),
);
},
);
},
);
}
}
Maybe not the most efficient method, but if you provide a passback to the Favourites class from the parent widget you can call a setState in the parent widget (assuming the parent widget reloads the database).
class Favorites extends StatefulWidget {
const Favorites({Key? key, this.passback}) : super(key: key);
final Function passback;
#override
_FavoritesState createState() => _FavoritesState();
}
Then the passback would look like:
passback() {
setState(){
//Reload db
}
}
And pass it into Favourite (does not work with named routes AFAIK)
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Favourites(passback: passback)),
);
Then just call passback when the user adds the item to their favourites.
Solved it quite ugly but it is working. If you know a better way please let me know!
class Favorites extends StatefulWidget {
const Favorites({Key? key}) : super(key: key);
#override
_FavoritesState createState() => _FavoritesState();
}
class _FavoritesState extends State<Favorites> {
// ignore: prefer_typing_uninitialized_variables
var database;
List<TestItems> items = <TestItems>[];
Future initDb() async {
database = await openDatabase(
join(await getDatabasesPath(), 'person_database.db'),
onCreate: (db, version) {
db.execute(
"CREATE TABLE person(id INTEGER PRIMARY KEY, name TEXT)",
);
},
version: 1,
);
getItems().then((value) {
setState(() {
items = value;
});
});
}
Future<List<TestItems>> getItems() async {
final Database db = await database;
final List<Map<String, dynamic>> maps = await db.query('person');
return List.generate(maps.length, (i) {
return TestItems(
id: maps[i]['id'],
name: maps[i]['name'] as String,
);
});
}
Future<void> deleteDB(int id) async {
final db = await database;
await db.delete(
'person',
where: "id = ?",
whereArgs: [id],
);
}
#override
void initState() {
super.initState();
initDb();
}
#override
Widget build(BuildContext context) {
// Updates the page every time the build method gets called
initDb().then((value) {
setState(() {
items = value;
});
});
return Scaffold(
appBar: AppBar(
title: const Text("Favorites"),
),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
var item = items[index];
return ListTile(
title: Text(
item.name,
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ItemDtl(items: item, id: index)),
);
},
trailing: IconButton(
color: Colors.red,
icon: const Icon(Icons.delete_forever_rounded),
onPressed: () {
deleteDB(item.id).then((value) {
getItems().then((value) {
setState(() {
items = value;
});
});
});
},
),
);
}));
}
}
I want to display a list from downloading the data from firestore. The download is successful (the full list can be printed) but somehow it cannot be displayed. Simply nothing is shown when I use the ListView.builder and ListTile. Pls help what is the problem of my code. Great thanks.
class DownloadDataScreen extends StatefulWidget {
#override
List<DocumentSnapshot> carparkList = []; //List for storing carparks
_DownloadDataScreen createState() => _DownloadDataScreen();
}
class _DownloadDataScreen extends State<DownloadDataScreen> {
void initState() {
super.initState();
readFromFirebase();
}
void readFromFirebase() async {
await FirebaseFirestore.instance
.collection('carpark')
.get()
.then((QuerySnapshot snapshot) {
snapshot.docs.forEach((DocumentSnapshot cp) {
widget.carparkList.add(cp);
//to prove data are successfully downloaded
print('printing cp');
print(cp.data());
print(cp.get('name'));
});
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(
'Car Park',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
centerTitle: true,
),
body: SafeArea(
child: Column(
children: [
Expanded(
flex: 9,
child: Container(
child: ListView.builder(
itemCount: widget.carparkList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(widget.carparkList[index].get('name')),
subtitle: Text(
widget.carparkList[index].get('district')),
onTap: () {
},
);
},
),
),
),
],
),
),
);
}
}
create the list in state add it to the top line of the initState method List carparkList = [];
class DownloadDataScreen extends StatefulWidget {
_DownloadDataScreen createState() => _DownloadDataScreen();
}
class _DownloadDataScreen extends State<DownloadDataScreen> {
List<DocumentSnapshot> carparkList = []; //List for storing carparks
void initState() {
super.initState();
readFromFirebase();
}
void readFromFirebase() async {
await FirebaseFirestore.instance
.collection('carpark')
.get()
.then((QuerySnapshot snapshot) {
snapshot.docs.forEach((DocumentSnapshot cp) {
widget.carparkList.add(cp);
//to prove data are successfully downloaded
print('printing cp');
print(cp.data());
print(cp.get('name'));
});
});
}
I am trying to learn flutter and building a small "shopping list app". For this purpose I save the state of my shopping list to the sharedPreferences for later use. This way I was able to restore the same list after closing and opening the app again, but only after "triggering a rebuild"(?) by starting to type something in a text field, using the following code:
class _ItemChecklistState extends State<ItemChecklist> {
final List<ShoppingItem> _items = [];
final _itemNameController = TextEditingController();
final _amountController = TextEditingController()..text = '1';
final Map<int, bool> checkedMap = new Map();
bool _isComposing = false;
...
#override
Widget build(BuildContext context) {
// calling the method to "preload" my state from the shared preferences
_loadPrefs();
return Scaffold(
appBar: AppBar(
title: Text('Shopping List'),
actions: <Widget>[
IconButton(
onPressed: () => _removeCheckedItems(),
icon: Icon(Icons.remove_done)),
IconButton(
icon: const Icon(Icons.remove_circle_outline),
tooltip: 'Remove all items',
onPressed: () => _removeAllItems(),
),
],
),
body: Column(children: [
Flexible(
child: ListView.builder(
itemBuilder: (_, int index) => _items[index],
padding: EdgeInsets.all(8.0),
itemCount: _items.length,
),
),
Divider(height: 1.0),
Container(child: _buildTextComposer())
]));
}
...
// the method I use to "restore" my state
void _loadPrefs() async {
String key = 'currentItemList';
SharedPreferences prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey(key)) { return; }
_items.clear();
checkedMap.clear();
Map stateAsJson = jsonDecode(prefs.getString(key));
final itemsKey = 'items';
final checkedMapKey = 'checkedMap';
List items = stateAsJson[itemsKey];
Map checkedMapClone = stateAsJson[checkedMapKey];
for (Map item in items){
ShoppingItem newItem = ShoppingItem(
id: item['id'],
name: item['name'],
amount: item['amount'],
removeFunction: _removeItemWithId,
checkedMap: checkedMap,
saveState: _saveListToSharedPrefs,
);
_items.add(newItem);
checkedMap.putIfAbsent(newItem.id, () => checkedMapClone[newItem.id.toString()]);
}
}
...
}
Now at this point loading the state and setting the lists works fine, so _items list is updated correctly, as well as the checkedMap, but the ListView does not contain the corresponding data. How can I for example "trigger a rebuild" immediatlly or make sure that the "first" build of the ListView already contains the correct state?
Thanks :)
You have to use FutureBuilder when your UI depends on a async task
Future<List<ShoppingItem>> _getShoppingItems;
#override
void initState() {
_getShoppingItems = _loadPrefs();
super.initState();
}
#override
Widget build(BuildContext context) {
FutureBuilder<List<ShoppingItem>>(
future: _getShoppingItems,
builder: (context, snapshot) {
// Data not loaded yet
if (snapshot.connectionState != ConnectionState.done) {
return CircularProgressIndicator();
}
// Data loaded
final data = snapshot.data;
return ListView(...);
}
}
);
More info : https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
I'm working on a food delivery app I've tried to make an increment decrement system of a particular product in a list. At the start it works i.e the counter increases but a bit after the counter automatically return to 0 without any button press. I don't know why it's happening
Below is the code I'm trying
This is the class
class ItemData {
final String itemName;
final String itemPrice;
final String image;
int counter = 0;
bool isAdded = false;
ItemData({this.itemName, this.itemPrice, this.image});
}
This is the function for getting data from url
Future<List<ItemData>> _getProducts() async {
var data = await http
.get("https://orangecitycafe.in/app_configs/products_display.php");
var jsonData = json.decode(data.body);
List<ItemData> details = [];
for (var p in jsonData) {
ItemData detail = ItemData(
itemName: p["product_name"],
itemPrice: p["product_price"],
image: p["product_image"]);
details.add(detail);
}
return details;
}
This is the code for fetched products inside future builder
Widget _myCart() {
return FutureBuilder(
future: _getProfile(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(snapshot.data[index].itemName),
leading: Image.network("https://www.orangecitycafe.in/" +
snapshot.data[index].image),
trailing: snapshot.data[index].isAdded
? Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(Icons.remove),
onPressed: () {
setState(() {
if (snapshot.data[index].counter > 0) {
snapshot.data[index].counter--;
}
});
},
color: Colors.green,
),
Text(snapshot.data[index].counter.toString()),
IconButton(
icon: Icon(Icons.add),
color: Colors.green,
onPressed: () {
setState(() {
snapshot.data[index].counter++;
});
},
),
],
)
: RaisedButton(
onPressed: (){
setState(() {
snapshot.data[index].isAdded = true;
});
},
child: Text("Add"),
),
);
},
);
} else {
return Container();
}
},
);
}
The rest is working but only when I increase the counter it increases and after sometime it automatically returns to 0
You can copy paste run full code below
You can use the following way to use Future in FutureBuilder to avoid setState cause FutureBuilder rebuild again.
Detail reason https://github.com/flutter/flutter/issues/11426#issuecomment-414047398
didUpdateWidget of the FutureBuilder state is being called every time a rebuild is issued. This function checks if the old future object is different from the new one, and if so, refires the FutureBuilder.
To get past this, we can call the Future somewhere other than in the build function. For example, in the initState, and save it in a member variable, and pass this variable to the FutureBuilder.
code snippet
Future<List<ItemData>> _future;
...
#override
void initState() {
_future = _getProducts();
super.initState();
}
...
Widget _myCart() {
return FutureBuilder(
future: _future,
working demo
full code
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class ItemData {
final String itemName;
final String itemPrice;
final String image;
int counter = 0;
bool isAdded = false;
ItemData({this.itemName, this.itemPrice, this.image});
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Future<List<ItemData>> _future;
Future<List<ItemData>> _getProducts() async {
var data = await http
.get("https://orangecitycafe.in/app_configs/products_display.php");
var jsonData = json.decode(data.body);
List<ItemData> details = [];
for (var p in jsonData) {
ItemData detail = ItemData(
itemName: p["product_name"],
itemPrice: p["product_price"],
image: p["product_image"]);
details.add(detail);
}
return details;
}
#override
void initState() {
_future = _getProducts();
super.initState();
}
Widget _myCart() {
return FutureBuilder(
future: _future,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(snapshot.data[index].itemName),
leading: Image.network("https://www.orangecitycafe.in/" +
snapshot.data[index].image),
trailing: snapshot.data[index].isAdded
? Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(Icons.remove),
onPressed: () {
setState(() {
if (snapshot.data[index].counter > 0) {
snapshot.data[index].counter--;
}
});
},
color: Colors.green,
),
Text(snapshot.data[index].counter.toString()),
IconButton(
icon: Icon(Icons.add),
color: Colors.green,
onPressed: () {
setState(() {
snapshot.data[index].counter++;
});
},
),
],
)
: RaisedButton(
onPressed: () {
setState(() {
snapshot.data[index].isAdded = true;
});
},
child: Text("Add"),
),
);
},
);
} else {
return Container();
}
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: _myCart());
}
}
I'm trying to make a splash screen where the user chooses a city, with each city having its own API via url_items variable to access its data to populate the ListViews in the second screen.
When I call the data in the second screen, via http.Response response = await http.get(url_items); I get an error Getter not found: url_items
How do I do the Getter properly?
class Splash extends StatefulWidget {
_SplashState createState() => _SplashState();
}
class _SplashState extends State<Splash> {
String dropdownValue = 'NY';
String city = 'NY';
String url_items = 'https://ny.com/items';
String url_stores = 'https://ny.com/stores';
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
ListTile(
title: DropdownButton<String>(
value: dropdownValue,
onChanged: (String newValue) {
setState(() {
dropdownValue = newValue;
city = newValue;
if (city == 'NY'){url_items = 'https://ny.com/items';} else {url_items = 'https://chicago.com/items';}
});
},
items: <String>['NY', 'Chicago'].map<DropdownMenuItem<String>>(
(String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}
).toList(),
),
),
RaisedButton(
child: Text('View Items'),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => Items(url_items: url_items, url_stores: url_stores, city: city)
),
);
},
),
],
),
);
}
}
class Items extends StatelessWidget {
var url_items="";
var url_stores="";
var city="";
Items({Key key, this.url_items, this.url_stores, this.city}) : super(key: key);
static Future<List<Item>> getItems() async {
http.Response response = await http.get(url_items);
String data = response.body;
List collection = json.decode(data);
Iterable<Item> _items = collection.map((_) => Item.fromJson(_));
return _items.toList();
}
Stream<List<Item>> get itemListView => Stream.fromFuture(getItems());
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: itemListView,
builder: (BuildContext context, AsyncSnapshot<List<Item>> snapshot) {
List<Item> items = snapshot.data;
return ListView.separated(
itemBuilder: (BuildContext context, int index) {
Item item = items[index];
return ListTile(
title: Html(data: item.name),
subtitle: Html(data: item.userName),
onTap: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => ItemDetail(item.name, item.userName..),
),
);
},
);
},
separatorBuilder: (context, index) => Divider(),
);
}
}
),
);
}
}
Instance variables/members cannot be accessed from a static method. so try changing
static Future<List<Item>> getItems() async {...}
to
Future<List<Item>> getItems() async {...}