Widget don't rebuild with riverpod when changing state from child - flutter

I am using riverpod to build my app and i am struggling to build a simple add to favorite feature. I have a list of products and a child Consumer widget that is a card with add to favorite button.
When I change the state of the products from the child cards the ui don't rebuild.
here is the git repository https://github.com/nisa10880/riverpod_difficulties
Consumer(
builder: (context, watch, child) =>
watch(getProductsFutureProvider).when(
data: (p) {
final products = watch(productListStateProvider).state;
return ListView.builder(
itemCount: products.length,
itemBuilder: (BuildContext context, int index) =>
ProductCard(product: products[index]));
},
loading: () => CircularProgressIndicator(),
error: (e, s) => Text(e)))
final productServiceProvider = Provider<ProductService>((ref) {
return ProductService();
});
class ProductService {
ProductService();
Future<List<ProductModel>> getAllProducts() async {
await Future.delayed(Duration(seconds: 1));
return [
ProductModel(id: '1', title: 'product 1', isFavorite: false),
ProductModel(id: '2', title: 'product 2', isFavorite: false),
ProductModel(id: '3', title: 'product 3', isFavorite: false),
ProductModel(id: '4', title: 'product 4', isFavorite: false)
];
}
}
final productListStateProvider = StateProvider<List<ProductModel>>((ref) {
return [];
});
final getProductsFutureProvider =
FutureProvider.autoDispose<List<ProductModel>>((ref) async {
ref.maintainState = true;
final evtentService = ref.watch(productServiceProvider);
final products = await evtentService.getAllProducts();
ref.read(productListStateProvider).state = products;
return products;
});
class ProductCard extends ConsumerWidget {
final ProductModel product;
const ProductCard({
#required this.product,
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context, ScopedReader watch) {
return Card(
child: Column(
children: <Widget>[
Text(product.title),
product.isFavorite
? IconButton(
icon: Icon(Icons.favorite),
color: Colors.red,
onPressed: () {
product.isFavorite = !product.isFavorite;
},
)
: IconButton(
icon: Icon(Icons.favorite_border),
color: Colors.black54,
onPressed: () {
product.isFavorite = !product.isFavorite;
},
)
],
));
}
}

The problem is you're updating an inner value of ProductModel instead of the state (the state is the List<ProductModel>) so it won't trigger a rebuild, you either try something like this:
onPressed: () {
List<ProductModel> productList = context.read(productListStateProvider).state;
int index = productList.indexWhere((e) => e.id == product.id);
productList[index].isFavorite = !product.isFavorite;
context.read(productListStateProvider).state = productList;
},
or you try to use a ChangeNotifierProvider so you can call notifyListeners() when you feel you made a change to a value

Related

using ChangeNotifier to add items into empty list

I already have data stored on firestore , but now i'd like to add this data into an empty list using ChangeNotifier , similar to this example -https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple .
so below i'd like to add data stored on firebase into the _cart list , with the method I tried below I got the error The argument type 'List<Menu>' can't be assigned to the parameter type 'Menu'(would like to know why?) , it also contains the ui for where i'd like to map the data from cart list into the dialog sheet:
class AddList extends ConsumerWidget {
const AddList({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final menuAsync = ref.watch(menuProvider);
final model = ref.read(cartProvider);
return Scaffold(
appBar: AppBar(
title: const Text("menu"),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.black,
onPressed: () async {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text("cart"),
content: Column(
children: const [
...model.cart.map((item) => Text(item.mealName)),
],
),
);
});
},
),
body: menuAsync.when(
data: (menu) => Column(
children: menu
.map(
(e) => Card(
child: ListTile(
title: Text(e.mealName),
subtitle: Text(e.price),
trailing: IconButton(
onPressed: () {
model.addProduct(menu);//where im getting error
},
icon: const Icon(Icons.add)))),
)
.toList(),
),
error: (e, s) => Center(child: Text("$e")),
loading: () => const Center(child: CircularProgressIndicator())),
);
}
}
below im using changenotifier to modify the cart list:
final cartProvider =
ChangeNotifierProvider<CartNotifier>((ref) => CartNotifier());
class CartNotifier extends ChangeNotifier {
final List<Menu> _cart = [];
List<Menu> get cart => _cart;
void addProduct(Menu menu) {
_cart.add(menu);
notifyListeners();
}
void removeProduct(Menu menu) {
_cart.remove(menu);
notifyListeners();
}
}
how im reading data from firestore :
final menuProvider = StreamProvider<List<Menu>>(
(ref) => ref.read(addMealRespositoryProvider).menuSearchStream);
Stream<List<Menu>> get menuSearchStream =>
_firestore.collection("menu").snapshots().map(
(event) => event.docs.map((e) => Menu.fromFirestore(e)).toList(),
);
snippet of my data model:
class Menu {
String mealName;
String price;
Menu({
required this.mealName,
required this.price,
});
Map<String, dynamic> toMap() {
return {
"mealName": mealName,
"price": price, },
factory Menu.fromFirestore(DocumentSnapshot doc) {
final map = doc.data() as Map<String, dynamic>;
return Menu(
mealName: map["mealName"] ?? '',
price: map["price"] ?? '',
);
}
}

Updating a page every time I revisit the page in BottomNavigationbar

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

Icon value not updating with provider and sqflite in flutter

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

Why do I have to perform a hot restart to see the changes in my database in Flutter?

I'm learning Flutter and I'm trying to use the SQFLite package to persist data on the device. Everything works perfect but there's one problem, every time I add a new element or I upgrade it, I need to restart my app to see the changes and I don't know why, there are no errors or anything.
I uploaded it to github so you can try it, maybe is something in my emulator or something I dont know.
https://github.com/Rodrigogzmn6/sql_demo
or you can see part of the complete project here
Home Page
import 'package:flutter/material.dart';
import 'package:sql_demo/models/product.dart';
import 'package:sql_demo/pages/add_product_page.dart';
import 'package:sql_demo/pages/update_product_page.dart';
import 'package:sql_demo/pages/view_product_page.dart';
import 'package:sql_demo/services/dbhelper.dart';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
DbHelper helper;
#override
void initState() {
super.initState();
helper = DbHelper();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Page'),
),
body: FutureBuilder(
future: helper.getProducts(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
Product product = Product.fromMap(snapshot.data[index]);
return ListTile(
contentPadding: const EdgeInsets.all(16.0),
title: Text(
'${product.name} - \$${(product.price).toString()}'),
subtitle: Text(product.description),
trailing: Column(
children: [
Expanded(
child: IconButton(
icon: Icon(
Icons.delete,
color: Colors.red,
),
onPressed: () {
setState(() {
helper.deleteProduct(product.id);
});
}),
),
SizedBox(
height: 20.0,
),
Expanded(
child: IconButton(
icon: Icon(
Icons.edit,
color: Colors.blue,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UpdateProductPage(
product: product,
)),
);
}),
),
],
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailsPage(
product: product,
)));
},
);
});
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.add,
color: Colors.white,
),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => AddProductPage()));
}),
);
}
}
Creating Item page
import 'package:flutter/material.dart';
import 'package:sql_demo/models/product.dart';
import 'package:sql_demo/services/dbhelper.dart';
class AddProductPage extends StatefulWidget {
#override
_AddProductPageState createState() => _AddProductPageState();
}
class _AddProductPageState extends State<AddProductPage> {
String name, description;
num price;
DbHelper helper;
//Instantiate Helper
#override
void initState() {
super.initState();
helper = DbHelper();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Add new product"),
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
TextFormField(
decoration: InputDecoration(
hintText: 'Enter product name',
labelText: 'Product name',
),
onChanged: (value) {
setState(() {
name = value;
});
},
),
SizedBox(height: 16),
TextFormField(
decoration: InputDecoration(
hintText: 'Enter product description',
labelText: 'Product description',
),
onChanged: (value) {
setState(() {
description = value;
});
},
),
SizedBox(height: 16),
TextFormField(
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: 'Enter product price',
labelText: 'Product price',
),
onChanged: (value) {
setState(() {
price = double.parse(value);
});
},
),
SizedBox(height: 16),
RaisedButton(
child: Text('Save'),
onPressed: () {
Product product = Product({
'name': name,
'description': description,
'price': price,
});
setState(() {
helper.createProduct(product);
Navigator.pop(context);
});
},
),
],
),
);
}
}
Database operations
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:sql_demo/models/product.dart';
class DbHelper {
//Creates a singleton class (it only creates once)
static final DbHelper _singleton = DbHelper._internal();
factory DbHelper() => _singleton;
DbHelper._internal();
//Creating an object of type database
static Database _database;
//Initializing or creating the database named mydb.db
//Creating a products table with necessary fields
Future<Database> initDatabase() async {
//If this class was never instaciated, we will execute this method, otherwise return
if (_database != null) return _database;
String path = join(await getDatabasesPath(), 'mydb.db');
_database = await openDatabase(
path,
version: 1,
onCreate: (Database db, int v) async {
await db.execute(
'CREATE TABLE Products (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, price REAL)');
},
);
return _database;
}
//Select all products
Future<List> getProducts() async {
Database db = await initDatabase();
return await db.query('Products');
}
//Create new product
Future<int> createProduct(Product product) async {
Database db = await initDatabase();
print('Product added');
return await db.insert(
'Products',
product.toMap(),
);
}
//Update product
Future<int> updateProduct(Product product) async {
Database db = await initDatabase();
return await db.update(
'Products',
product.toMap(),
where: 'id=?',
whereArgs: [product.id],
);
}
//Delete a product
Future<int> deleteProduct(int productID) async {
Database db = await initDatabase();
return await db.delete('Products', where: 'id=?', whereArgs: [productID]);
}
}
You can copy paste run full code below
You can await Navigator.push to AddProductPage and then call setState(() {});
code snippet
onPressed: () async {
await Navigator.push(context,
MaterialPageRoute(builder: (context) => AddProductPage()));
setState(() {});
}),
working demo
full code
import 'package:flutter/material.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'dart:convert';
class Product {
int _id;
String _name;
String _description;
double _price;
//Getters because the attributes are private
int get id => _id;
String get name => _name;
String get description => _description;
double get price => _price;
//Constructor
Product(dynamic obj) {
_id = obj['id'];
_name = obj['name'];
_description = obj['description'];
_price = obj['price'];
}
//Named objet (takes a Map as parameter)
Product.fromMap(Map<String, dynamic> data) {
_id = data['id'];
_name = data['name'];
_description = data['description'];
_price = data['price'];
}
//Method that converts the object into a map
Map<String, dynamic> toMap() {
return {
'id': _id,
'name': _name,
'description': _description,
'price': _price,
};
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
DbHelper helper;
#override
void initState() {
super.initState();
helper = DbHelper();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Page'),
),
body: FutureBuilder(
future: helper.getProducts(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
Product product = Product.fromMap(snapshot.data[index]);
return ListTile(
contentPadding: const EdgeInsets.all(16.0),
title: Text(
'${product.name} - \$${(product.price).toString()}'),
subtitle: Text(product.description),
trailing: Column(
children: [
Expanded(
child: IconButton(
icon: Icon(
Icons.delete,
color: Colors.red,
),
onPressed: () {
setState(() {
helper.deleteProduct(product.id);
});
}),
),
SizedBox(
height: 20.0,
),
Expanded(
child: IconButton(
icon: Icon(
Icons.edit,
color: Colors.blue,
),
onPressed: () {
/*Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UpdateProductPage(
product: product,
)),
);*/
}),
),
],
),
onTap: () {
/*Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailsPage(
product: product,
)));*/
},
);
});
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.add,
color: Colors.white,
),
onPressed: () async {
await Navigator.push(context,
MaterialPageRoute(builder: (context) => AddProductPage()));
setState(() {});
}),
);
}
}
class AddProductPage extends StatefulWidget {
#override
_AddProductPageState createState() => _AddProductPageState();
}
class _AddProductPageState extends State<AddProductPage> {
String name, description;
num price;
DbHelper helper;
//Instantiate Helper
#override
void initState() {
super.initState();
helper = DbHelper();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Add new product"),
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
TextFormField(
decoration: InputDecoration(
hintText: 'Enter product name',
labelText: 'Product name',
),
onChanged: (value) {
setState(() {
name = value;
});
},
),
SizedBox(height: 16),
TextFormField(
decoration: InputDecoration(
hintText: 'Enter product description',
labelText: 'Product description',
),
onChanged: (value) {
setState(() {
description = value;
});
},
),
SizedBox(height: 16),
TextFormField(
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: 'Enter product price',
labelText: 'Product price',
),
onChanged: (value) {
setState(() {
price = double.parse(value);
});
},
),
SizedBox(height: 16),
RaisedButton(
child: Text('Save'),
onPressed: () {
Product product = Product({
'name': name,
'description': description,
'price': price,
});
setState(() {
helper.createProduct(product);
Navigator.pop(context);
});
},
),
],
),
);
}
}
class DbHelper {
//Creates a singleton class (it only creates once)
static final DbHelper _singleton = DbHelper._internal();
factory DbHelper() => _singleton;
DbHelper._internal();
//Creating an object of type database
static Database _database;
//Initializing or creating the database named mydb.db
//Creating a products table with necessary fields
Future<Database> initDatabase() async {
//If this class was never instaciated, we will execute this method, otherwise return
if (_database != null) return _database;
String path = join(await getDatabasesPath(), 'mydb.db');
_database = await openDatabase(
path,
version: 1,
onCreate: (Database db, int v) async {
await db.execute(
'CREATE TABLE Products (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, price REAL)');
},
);
return _database;
}
//Select all products
Future<List> getProducts() async {
Database db = await initDatabase();
return await db.query('Products');
}
//Create new product
Future<int> createProduct(Product product) async {
Database db = await initDatabase();
print('Product added');
return await db.insert(
'Products',
product.toMap(),
);
}
//Update product
Future<int> updateProduct(Product product) async {
Database db = await initDatabase();
return await db.update(
'Products',
product.toMap(),
where: 'id=?',
whereArgs: [product.id],
);
}
//Delete a product
Future<int> deleteProduct(int productID) async {
Database db = await initDatabase();
return await db.delete('Products', where: 'id=?', whereArgs: [productID]);
}
}
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: HomePage(),
);
}
}
Hot reload: Hot reload feature quickly compile the newly added code in our file and sent the code to Dart Virtual Machine. If you are using States in your application then Hot Reload preservers the States so they will not update on Hot Reload our set to their default values.
And, initState() does not rebuild after hot reload as its state is preserved. So, you have to perform hot restart to see database changes:
#override
void initState() {
super.initState();
helper = DbHelper();
}
When stateful widget is build, you have helper, but after you hot reload, the helper will be same as initState only called hot restart or you launch the widget. So, helper variable is preserved. Hence, you have to hot restart.
Links that might help you: Understanding Flutter hot reload and hot restart the simplest way | Refs

Flutter Passing Data:: Getter not found

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 {...}