1. 로딩 부분을 추가해주자.
앱 재시작 후, 값을 불러올때 필요한 로딩 화면, 그리고 newItem 화면에서 값을 파베로 보낼 때 필요한 로딩 화면을 꾸며주자.
- grocery_list.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shopping_list/data/categories.dart';
import 'package:shopping_list/models/grocery_item.dart';
import 'package:shopping_list/widgets/new_item.dart';
class GroceryList extends StatefulWidget {
const GroceryList({super.key});
@override
State<GroceryList> createState() => _GroceryListState();
}
class _GroceryListState extends State<GroceryList> {
List<GroceryItem> _groceryItems = [];
var _isLoading = true;
@override
void initState() {
super.initState();
_loadItems();
}
void _loadItems() async {
final url = Uri.https('flutter-study-1acb3-default-rtdb.firebaseio.com',
'shopping-list.json');
final response = await http.get(url);
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,
),
);
}
setState(() {
_groceryItems = loadedItems;
_isLoading = false;
});
}
void _addItem() async {
final newItem = await Navigator.of(context).push<GroceryItem>(
MaterialPageRoute(
builder: (ctx) => const NewItem(),
),
);
if (newItem == null) {
return;
}
setState(() {
_groceryItems.add(newItem);
});
}
void _removeItem(GroceryItem item) {
setState(() {
_groceryItems.remove(item);
});
}
@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(),
),
),
),
);
}
return Scaffold(
appBar: AppBar(
title: const Text('Your Groceries'),
actions: [
IconButton(
onPressed: _addItem,
icon: const Icon(Icons.add),
),
],
),
body: content,
);
}
}
1) var _isLoading = true;
기본 true로 해두었다가,
loadItem 함수 부분에서
setState(() {
_groceryItems = loadedItems;
_isLoading = false;
});
값을 받아오면 false로 바꿔준다.
빌드 위젯부분에서
if (_isLoading) {
content = const Center(child: CircularProgressIndicator());
}
를 추가해주어, 로딩이 true인 동안에는 로딩 써클이 나오게끔 해주자.
- new_item.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shopping_list/data/categories.dart';
import 'package:shopping_list/models/category.dart';
import 'package:shopping_list/models/grocery_item.dart';
class NewItem extends StatefulWidget {
const NewItem({super.key});
@override
State<NewItem> createState() {
return _NewItemState();
}
}
class _NewItemState extends State<NewItem> {
final _formKey = GlobalKey<FormState>();
var _enteredName = '';
var _enteredQuantity = 1;
var _selectedCategory = categories[Categories.vegetables]!;
var _isSending = false;
void _saveItem() async {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
setState(() {
_isSending = true;
});
final url = Uri.https('flutter-study-1acb3-default-rtdb.firebaseio.com',
'shopping-list.json');
final response = await http.post(
url,
headers: {
'Content-Type': 'application/json',
},
body: json.encode(
{
'name': _enteredName,
'quantity': _enteredQuantity,
'category': _selectedCategory.title,
},
),
);
final Map<String, dynamic> resData = json.decode(response.body);
if (!context.mounted) {
return;
}
Navigator.of(context).pop(
GroceryItem(
id: resData['name'],
name: _enteredName,
quantity: _enteredQuantity,
category: _selectedCategory,
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Add a new item'),
),
body: Padding(
padding: const EdgeInsets.all(12),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
maxLength: 50,
decoration: const InputDecoration(
label: Text('Name'),
),
validator: (value) {
if (value == null ||
value.isEmpty ||
value.trim().length <= 1 ||
value.trim().length > 50) {
return 'Must be between 1 and 50 characters.';
}
return null;
},
onSaved: (value) {
// if (value == null) {
// return;
// }
_enteredName = value!;
},
), // instead of TextField()
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: TextFormField(
decoration: const InputDecoration(
label: Text('Quantity'),
),
keyboardType: TextInputType.number,
initialValue: _enteredQuantity.toString(),
validator: (value) {
if (value == null ||
value.isEmpty ||
int.tryParse(value) == null ||
int.tryParse(value)! <= 0) {
return 'Must be a valid, positive number.';
}
return null;
},
onSaved: (value) {
_enteredQuantity = int.parse(value!);
},
),
),
const SizedBox(width: 8),
Expanded(
child: DropdownButtonFormField(
value: _selectedCategory,
items: [
for (final category in categories.entries)
DropdownMenuItem(
value: category.value,
child: Row(
children: [
Container(
width: 16,
height: 16,
color: category.value.color,
),
const SizedBox(width: 6),
Text(category.value.title),
],
),
),
],
onChanged: (value) {
setState(() {
_selectedCategory = value!;
});
},
),
),
],
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: _isSending
? null
: () {
_formKey.currentState!.reset();
},
child: const Text('Reset'),
),
ElevatedButton(
onPressed: _isSending ? null : _saveItem,
child: _isSending
? const SizedBox(
height: 16,
width: 16,
child: CircularProgressIndicator(),
)
: const Text('Add Item'),
)
],
),
],
),
),
),
);
}
}
1) 데이터를 파베로 보낼 때 화면가운데 써클이 나오는게 아니라 reset버튼은 사라지게, add Item 버튼은 로딩써클로 바꾸게 해줄것이다.
2) 위 grocery_list와 유사한 로직으로
var _isSending = false;
를 기본값으로 해주고, _saveItem 함수부분에
setState(() {
_isSending = true;
});
를 추가함으로써 true로 바꿔준다.
3) 삼항연산자를 활용하여, 리셋버튼은 아래와 같이,
TextButton(
onPressed: _isSending
? null
: () {
_formKey.currentState!.reset();
},
child: const Text('Reset'),
),
Add Item 버튼은 아래와 같이 세팅한다.
ElevatedButton(
onPressed: _isSending ? null : _saveItem,
child: _isSending
? const SizedBox(
height: 16,
width: 16,
child: CircularProgressIndicator(),
)
: const Text('Add Item'),
)