|
|
|
|
@ -16,59 +16,44 @@ class GroceryList extends StatefulWidget {
|
|
|
|
|
|
|
|
|
|
class _GroceryListState extends State<GroceryList> {
|
|
|
|
|
List<GroceryItem> _groceryItems = [];
|
|
|
|
|
var _isLoading = true;
|
|
|
|
|
late Future<List<GroceryItem>> _loadedItems;
|
|
|
|
|
String? _error;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
_loadItems();
|
|
|
|
|
_loadedItems = _loadItems();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _loadItems() async {
|
|
|
|
|
Future<List<GroceryItem>> _loadItems() async {
|
|
|
|
|
final url = Uri.https(
|
|
|
|
|
'flutter-prep-dccfe-default-rtdb.firebaseio.com', 'shopping-list.json');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
final response = await http.get(url);
|
|
|
|
|
|
|
|
|
|
if (response.statusCode >= 400) {
|
|
|
|
|
setState(() {
|
|
|
|
|
_error = 'Failed to fetch data. Please try again later.';
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (response.body == 'null') {
|
|
|
|
|
setState(() {
|
|
|
|
|
_isLoading = false;
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final Map<String, dynamic> listData = json.decode(response.body);
|
|
|
|
|
final List<GroceryItem> loadedItems = [];
|
|
|
|
|
for (final item in listData.entries) {
|
|
|
|
|
final category = categories.entries
|
|
|
|
|
.firstWhere(
|
|
|
|
|
(catItem) => catItem.value.title == item.value['category'])
|
|
|
|
|
.value;
|
|
|
|
|
loadedItems.add(GroceryItem(
|
|
|
|
|
id: item.key,
|
|
|
|
|
name: item.value['name'],
|
|
|
|
|
quantity: item.value['quantity'],
|
|
|
|
|
category: category,
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
final response = await http.get(url);
|
|
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
_groceryItems = loadedItems;
|
|
|
|
|
_isLoading = false;
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
setState(() {
|
|
|
|
|
_error = 'Something went wrong. Please try again later.';
|
|
|
|
|
});
|
|
|
|
|
if (response.statusCode >= 400) {
|
|
|
|
|
throw Exception('Failed to fetch grocery items. Please try again later.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (response.body == 'null') {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final Map<String, dynamic> listData = json.decode(response.body);
|
|
|
|
|
final List<GroceryItem> loadedItems = [];
|
|
|
|
|
for (final item in listData.entries) {
|
|
|
|
|
final category = categories.entries
|
|
|
|
|
.firstWhere(
|
|
|
|
|
(catItem) => catItem.value.title == item.value['category'])
|
|
|
|
|
.value;
|
|
|
|
|
loadedItems.add(GroceryItem(
|
|
|
|
|
id: item.key,
|
|
|
|
|
name: item.value['name'],
|
|
|
|
|
quantity: item.value['quantity'],
|
|
|
|
|
category: category,
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
return loadedItems;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _addItem() async {
|
|
|
|
|
@ -107,39 +92,6 @@ class _GroceryListState extends State<GroceryList> {
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
Widget content = const Center(child: Text('No items added yet.'));
|
|
|
|
|
|
|
|
|
|
if (_isLoading) {
|
|
|
|
|
content = const Center(child: CircularProgressIndicator());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_groceryItems.isNotEmpty) {
|
|
|
|
|
content = ListView.builder(
|
|
|
|
|
itemCount: _groceryItems.length,
|
|
|
|
|
itemBuilder: (ctx, index) => Dismissible(
|
|
|
|
|
onDismissed: (direction) {
|
|
|
|
|
_removeItem(_groceryItems[index]);
|
|
|
|
|
},
|
|
|
|
|
key: ValueKey(_groceryItems[index].id),
|
|
|
|
|
child: ListTile(
|
|
|
|
|
title: Text(_groceryItems[index].name),
|
|
|
|
|
leading: Container(
|
|
|
|
|
width: 24,
|
|
|
|
|
height: 24,
|
|
|
|
|
color: _groceryItems[index].category.color,
|
|
|
|
|
),
|
|
|
|
|
trailing: Text(
|
|
|
|
|
_groceryItems[index].quantity.toString(),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_error != null) {
|
|
|
|
|
content = Center(child: Text(_error!));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Scaffold(
|
|
|
|
|
appBar: AppBar(
|
|
|
|
|
title: const Text('Your Groceries'),
|
|
|
|
|
@ -150,7 +102,43 @@ class _GroceryListState extends State<GroceryList> {
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
body: content,
|
|
|
|
|
body: FutureBuilder(
|
|
|
|
|
future: _loadedItems,
|
|
|
|
|
builder: (context, snapshot) {
|
|
|
|
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
|
|
|
return const Center(child: CircularProgressIndicator());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (snapshot.hasError) {
|
|
|
|
|
return Center(child: Text(snapshot.error.toString()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (snapshot.data!.isEmpty) {
|
|
|
|
|
return const Center(child: Text('No items added yet.'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ListView.builder(
|
|
|
|
|
itemCount: snapshot.data!.length,
|
|
|
|
|
itemBuilder: (ctx, index) => Dismissible(
|
|
|
|
|
onDismissed: (direction) {
|
|
|
|
|
_removeItem(snapshot.data![index]);
|
|
|
|
|
},
|
|
|
|
|
key: ValueKey(snapshot.data![index].id),
|
|
|
|
|
child: ListTile(
|
|
|
|
|
title: Text(snapshot.data![index].name),
|
|
|
|
|
leading: Container(
|
|
|
|
|
width: 24,
|
|
|
|
|
height: 24,
|
|
|
|
|
color: snapshot.data![index].category.color,
|
|
|
|
|
),
|
|
|
|
|
trailing: Text(
|
|
|
|
|
snapshot.data![index].quantity.toString(),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|