Я изучаю флаттер и работаю над приложением для фиктивных магазинов в целях обучения бэкэнду. В моей базе данных реального времени Firebase «isFavorite» устанавливается как «истина». Но после обновления экрана «Icons.favorite» устанавливается как «Icons.favorite_border». Он должен быть заштрихован. Следовательно, «Icons.favorite» является правильным значком для него, поскольку он «истинен» в Firebase. Это отнимает у меня много времени, но я хочу исправить это, несмотря ни на что.
зависимости (pubspec.yaml):
dependencies:
flutter:
sdk: flutter
provider: ^6.0.5
intl: ^0.18.0
http: ^0.13.5
основной.дротик:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './screens/cart_screen.dart';
import './providers/cart.dart';
import './providers/product.dart';
import './screens/products_overview_screen.dart';
import './screens/product_detail_screen.dart';
import './providers/products_provider.dart';
import '../providers/orders.dart';
import './screens/orders_screen.dart';
import './screens/user_product_screen.dart';
import './screens/edit_product_screen.dart';
import './screens/auth_screen.dart';
import './providers/auth.dart';
final MaterialColor primaryColor = MaterialColor(
0xFFEBEDF3,
<int, Color>{
50: Color(0xFFF3F4F7),
100: Color(0xFFE6E8EC),
200: Color(0xFFD0D4DD),
300: Color(0xFFB8BDC9),
400: Color(0xFFA0A6B2),
500: Color(0xFF878E9C),
600: Color(0xFF767D8C),
700: Color(0xFF636D7A),
800: Color(0xFF505C68),
900: Color(0xFF3D4A56),
},
);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (ctx) => Auth(),
),
ChangeNotifierProxyProvider<Auth, ProductsProvider>(
//update: (ctx, auth, previousProducts) => ProductsProvider(auth.token),
create: (ctx) => ProductsProvider(
'',
'',
[],
),
update: (ctx, auth, previousProducts) => ProductsProvider(
auth.token!,
auth.userId,
previousProducts == null ? [] : previousProducts.items),
),
ChangeNotifierProvider(
create: (ctx) => Cart(),
),
ChangeNotifierProxyProvider<Auth, Orders>(
//update: (ctx, auth, previousProducts) => ProductsProvider(auth.token),
create: (ctx) => Orders('', []),
update: (ctx, auth, previousOrders) => Orders(auth.token!,
previousOrders == null ? [] : previousOrders.orders),
),
],
child: Consumer<Auth>(
builder: (ctx, auth, _) => MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSwatch(primarySwatch: primaryColor)
.copyWith(
secondary: Colors.blueGrey,
),
fontFamily: 'Lato',
),
home: auth.isAuth ? ProductsOverviewScreen() : AuthScreen(),
routes: {
ProductDetailScreen.routeName: (ctx) => ProductDetailScreen(),
CartScreen.routeName: (ctx) => CartScreen(),
OrdersScreen.routeName: (ctx) => OrdersScreen(),
UserProductScreen.routeName: (ctx) => UserProductScreen(),
EditProductScreen.routeName: (ctx) => EditProductScreen(),
}),
));
}
}
Вот мой product_overview_screen.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../widgets/app_drawer.dart';
import '../screens/cart_screen.dart';
import '../widgets/badge.dart';
import '../widgets/products_grid.dart';
import '../providers/cart.dart';
import '../providers/products_provider.dart';
enum FilterOptions {
Favorites,
All,
}
class ProductsOverviewScreen extends StatefulWidget {
@override
State<ProductsOverviewScreen> createState() => _ProductsOverviewScreenState();
}
class _ProductsOverviewScreenState extends State<ProductsOverviewScreen> {
var _showOnlyFavorites = false;
var _isInit = true;
var _isLoading = false;
// @override
void initState() {
//
// Future.delayed(Duration.zero).then((_) {
// Provider.of<ProductsProvider>(context).fetchAndSetProduct();
// });
super.initState();
}
@override
void didChangeDependencies() {
if (_isInit) {
setState(() {
_isLoading = true;
});
Provider.of<ProductsProvider>(context).fetchAndSetProduct().then((_) {
setState(() {
_isLoading = false;
});
});
}
_isInit = false;
// TODO: implement didChangeDependencies
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
// final productsData = Provider.of<ProductsProvider>(context, listen: false);
return Scaffold(
backgroundColor: Colors.grey[100],
appBar: AppBar(
title: const Text('MyShop'),
actions: [
PopupMenuButton(
onSelected: (FilterOptions selectedValue) {
setState(() {
if (selectedValue == FilterOptions.Favorites) {
_showOnlyFavorites = true;
} else {
_showOnlyFavorites = false;
}
});
},
itemBuilder: (_) => const [
PopupMenuItem(
child: Text('Only Favorites'),
value: FilterOptions.Favorites,
),
PopupMenuItem(
child: Text('Show All'),
value: FilterOptions.All,
),
],
icon: const Icon(
Icons.more_vert,
),
),
Consumer<Cart>(
builder: (_, cart, ch) => Badge(
child: ch as Widget,
value: cart.itemCount.toString(),
color: Colors.blueGrey,
key: UniqueKey(),
),
child: IconButton(
icon: Icon(
Icons.shopping_cart,
),
onPressed: () {
Navigator.of(context).pushNamed(CartScreen.routeName);
},
),
),
],
),
drawer: AppDrawer(),
body: _isLoading
? Center(
child: CircularProgressIndicator(),
)
: ProductsGrid(_showOnlyFavorites),
);
}
}
Мой виджет product_item.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../screens/product_detail_screen.dart';
import '../providers/product.dart';
import '../providers/cart.dart';
import '../providers/auth.dart';
class ProductItem extends StatelessWidget {
// final String id;
// final String title;
// final String imageUrl;
// ProductItem(this.id, this.title, this.imageUrl);
@override
Widget build(BuildContext context) {
final product = Provider.of<Product>(context, listen: false);
final cart = Provider.of<Cart>(context, listen: false);
final authData = Provider.of<Auth>(context, listen: false);
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: GridTile(
child: GestureDetector(
onTap: () {
Navigator.of(context).pushNamed(
ProductDetailScreen.routeName,
arguments: product.id,
);
},
child: Image.network(
product.imageUrl,
fit: BoxFit.cover,
),
),
footer: GridTileBar(
backgroundColor: Colors.black87,
leading: Consumer<Product>(
builder: (ctx, product, _) => IconButton(
icon: Icon(
product.isFavorite ? Icons.favorite : Icons.favorite_border,
color: Theme.of(context).colorScheme.secondary,
),
onPressed: () {
product.toggleFavoriteStatus(authData.token!, authData.userId);
},
),
),
title: Text(
product.title,
textAlign: TextAlign.center,
),
trailing: IconButton(
icon: Icon(
Icons.shopping_cart,
color: Theme.of(context).colorScheme.secondary,
),
onPressed: () {
cart.addItem(
product.id, product.title, product.quantity, product.price);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text(
'Added Item',
textAlign: TextAlign.center,
),
duration: const Duration(seconds: 2),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
cart.removeSingleItem(product.id);
},
),
));
},
),
),
),
);
}
}
Моя модель продукта
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
class Product with ChangeNotifier {
final String id;
final String title;
final String description;
final double price;
final String imageUrl;
bool isFavorite;
Product({
required this.id,
required this.title,
required this.description,
required this.price,
required this.imageUrl,
this.isFavorite = false,
});
void _setFavValue(bool newValue) {
isFavorite = newValue;
notifyListeners();
}
int? get quantity => null;
Future<void> toggleFavoriteStatus(String? token, String userId) async {
final oldStatus = isFavorite;
isFavorite = !isFavorite;
final url = Uri.parse(
'https://shopapp-bd154-default-rtdb.firebaseio.com/userFavorites/$userId/$id.json?auth=$token');
try {
final response = await http.put(url,
body: json.encode({
'isFavorite': isFavorite,
}));
if (response.statusCode >= 400) {
_setFavValue(oldStatus);
}
} catch (error) {
_setFavValue(oldStatus);
}
notifyListeners();
}
bool get isFavoriteState {
return isFavorite;
}
}
И, наконец, мой products_provider.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shop_app/models/http_exeception.dart';
import './product.dart';
class ProductsProvider with ChangeNotifier {
List<Product> _items = [
// Product(
// id: 'p1',
// title: 'Red Shirt',
// description: 'A red shirt - it is pretty red!',
// price: 29.99,
// imageUrl:
// 'https://cdn.pixabay.com/photo/2016/10/02/22/17/red-t-shirt-1710578_1280.jpg',
// ),
// Product(
// id: 'p2',
// title: 'Trousers',
// description: 'A nice pair of trousers.',
// price: 59.99,
// imageUrl:
// 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e8/Trousers%2C_dress_%28AM_1960.022-8%29.jpg/512px-Trousers%2C_dress_%28AM_1960.022-8%29.jpg',
// ),
// Product(
// id: 'p3',
// title: 'Yellow Scarf',
// description: 'Warm and cozy - exactly what you need for the winter.',
// price: 19.99,
// imageUrl:
// 'https://live.staticflickr.com/4043/4438260868_cc79b3369d_z.jpg',
// ),
// Product(
// id: 'p4',
// title: 'A Pan',
// description: 'Prepare any meal you want.',
// price: 49.99,
// imageUrl:
// 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/14/Cast-Iron-Pan.jpg/1024px-Cast-Iron-Pan.jpg',
// ),
];
// var _showFavoritesOnly = false;
final String authToken;
final String userId;
ProductsProvider(this.authToken, this.userId, this._items);
List<Product> get items {
// if (_showFavoritesOnly) {
// return _items.where((prodItem) => prodItem.isFavorite).toList();
// }
return [..._items];
}
List<Product> get favoriteItems {
return _items.where((prodItem) => prodItem.isFavorite).toList();
}
Product findById(String id) {
return _items.firstWhere((prod) => prod.id == id);
}
Future<void> fetchAndSetProduct() async {
var url = Uri.parse(
'https://shopapp-bd154-default-rtdb.firebaseio.com/products.json?auth=$authToken');
try {
final response = await http.get(url);
final extractedData = json.decode(response.body) as Map<String, dynamic>;
if (extractedData == null) {
return;
}
url = Uri.parse(
'https://shopapp-bd154-default-rtdb.firebaseio.com/userFavorites/$userId.json?auth=$authToken');
final favoriteResponse = await http.get(url);
final favoriteData = json.decode(favoriteResponse.body);
final List<Product> loadedProducts = [];
extractedData.forEach((prodId, prodData) {
loadedProducts.add(Product(
id: prodId,
title: prodData['title'] ?? '',
description: prodData['description'] ?? '',
price: prodData['price'] ?? 0.0,
imageUrl: prodData['imageUrl'] ?? '',
isFavorite: favoriteData == null
? false
: (favoriteData[prodId] ?? false) is bool
? favoriteData[prodId]
: false,
));
});
_items = loadedProducts;
notifyListeners();
} catch (error) {
rethrow;
}
}
Future<void> addProduct(Product product) async {
try {
final url = Uri.parse(
'https://shopapp-bd154-default-rtdb.firebaseio.com/products.json?auth=$authToken');
final response = await http.post(
url,
body: json.encode({
'title': product.title,
'description': product.description,
'imageUrl': product.imageUrl,
'price': product.price,
}),
);
final newProduct = Product(
id: json.decode(response.body)['name'],
title: product.title,
description: product.description,
imageUrl: product.imageUrl,
price: product.price);
// _items.add(value);
_items.add(newProduct);
notifyListeners();
} catch (error) {
print(error);
throw (error);
}
}
Future<void> updateProduct(String id, Product newProduct) async {
final prodIndex = _items.indexWhere((prod) => prod.id == id);
if (prodIndex >= 0) {
final url = Uri.parse(
'https://shopapp-bd154-default-rtdb.firebaseio.com/products/$id.json?auth=$authToken');
await http.patch(
url,
body: json.encode({
'title': newProduct.title,
'description': newProduct.description,
'price': newProduct.price,
'imageUrl': newProduct.imageUrl,
}),
);
_items[prodIndex] = newProduct;
notifyListeners();
} else {
print('...');
}
}
Future<void> deleteProduct(String id) async {
final url = Uri.parse(
'https://shopapp-bd154-default-rtdb.firebaseio.com/products/$id.json?auth=$authToken');
final existingProductIndex = _items.indexWhere((prod) => prod.id == id);
Product? existingProduct = _items[existingProductIndex];
_items.removeAt(existingProductIndex);
notifyListeners();
final response = await http.delete(url);
if (response.statusCode >= 400) {
_items.insert(existingProductIndex, existingProduct);
notifyListeners();
throw HttpException('Could not delete product.');
}
existingProduct = null;
}
// updateToken(String? token, _items) {}
// void showFavoritesOnly() {
// _showFavoritesOnly = true;
// notifyListeners();
// }
// void showAll() {
// _showFavoritesOnly = false;
// notifyListeners();
// }
}
Структура моей базы данных firebase:
Так в чем именно проблема? это только значок, который показывает неправильный значок? или это не правильные данные? если вы делаете debugPrint(isFavorite.toString()) это правда или ложь?
Может быть в раздаче. Потому что, как я уже сказал в моей Firebase, он установлен как «истина», поэтому он должен быть заштрихованным сердцем (указывает, что продукт помечен как любимый) вместо значка сердца с рамкой. Эта строка -> product.isFavorite? Icons.favorite : Icons.favorite_border, <- я думаю, это правильно в product_item.dart.
в вашем products_provider попробуйте изменить выборку «isFavorite» на это: isFavorite: favoriteData == null ? false : FavoriteData[prodId] как логическое значение? ?? ЛОЖЬ,
получение этой ошибки: Необработанное исключение: тип «_InternalLinkedHashMap<String, dynamic>» не является подтипом типа «bool?» в литой форме
Итак, что я сделал, так это то, что я сначала проверил значение «favoriteData», прежде чем передать его в «isFavorite». Я все еще новичок в исключении нулевого указателя или нулевых проверках безопасности.
Future<void> fetchAndSetProduct() async {
var url = Uri.parse(
'https://shopapp-bd154-default-rtdb.firebaseio.com/products.json?auth=$authToken');
try {
final response = await http.get(url);
final extractedData = json.decode(response.body) as Map<String, dynamic>;
if (extractedData == null) {
return;
}
url = Uri.parse(
'https://shopapp-bd154-default-rtdb.firebaseio.com/userFavorites/$userId.json?auth=$authToken');
final favoriteResponse = await http.get(url);
final favoriteData = json.decode(favoriteResponse.body);
final List<Product> loadedProducts = [];
extractedData.forEach((prodId, prodData) {
var isFavorite = false;
if (favoriteData != null && favoriteData[prodId] != null) {
isFavorite = favoriteData[prodId]['isFavorite'] is bool &&
favoriteData[prodId]['isFavorite'];
}
loadedProducts.add(Product(
id: prodId,
title: prodData['title'] ?? '',
description: prodData['description'] ?? '',
price: prodData['price'] ?? 0.0,
imageUrl: prodData['imageUrl'] ?? '',
isFavorite: isFavorite,
));
});
print(json.decode(favoriteResponse.body));
_items = loadedProducts;
notifyListeners();
} catch (error) {
rethrow;
}
}
Возможно, где-то в коде перестраивается пользовательский интерфейс, в результате чего приведенный ниже блок работает неправильно. ведущий: Consumer<Product>( builder: (ctx, product, _) => IconButton( icon: Icon( product.isFavorite ? Icons.favorite : Icons.favorite_border, color: Theme.of(context).colorScheme.secondary, ) , onPressed: () { product.toggleFavoriteStatus(authData.token!, authData.userId); }, ), ),