1. 값을 파이어베이스로(백엔드)로 보내고, 다시 받아오는 과정을 알아보자.
- 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' ;
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] ! ;
void _saveItem () async {
if (_formKey . currentState ! . validate ()) {
_formKey . currentState ! . save () ;
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 ,
} ,
) ,
) ;
print (response . body) ;
print (response . statusCode) ;
if ( ! context . mounted) {
return ;
}
Navigator . of (context) . pop () ;
}
}
@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 : () {
_formKey . currentState ! . reset () ;
} ,
child : const Text ( 'Reset' ) ,
) ,
ElevatedButton (
onPressed : _saveItem ,
child : const Text ( 'Add Item' ) ,
)
] ,
) ,
] ,
) ,
) ,
) ,
) ;
}
}
1) void _saveItem() async { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); 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, }, ), ); print(response.body); print(response.statusCode); if (!context.mounted) { return; } Navigator.of(context).pop(); } }
--> 아래 !context.mounted 이하의 뜻은, async await 구문에서는 context가 들어갈 시 (화면 이동, 변경) 앞에서 반드시 해당 if(!context.mounted)~~ 가 들어가야 한다.(플러터 권장) 해석하자면, 값을 백엔드로 보내고 화면 이동전(여기서는 뒤로가기)에 mounte되지 않으면(원래 화면으로 돌아오지 않으면) 뒤로 가지 않는 다는 것이다. (정확한 해석인지는 모름, 뇌피셜임)
프린트 된 값을 확인해보면 아래값을 확인 할 수 있음.
- 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/category.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 = [] ;
@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 ;
}) ;
}
void _addItem () async {
await Navigator . of (context) . push < GroceryItem >(
MaterialPageRoute (
builder : (ctx) => const NewItem () ,
) ,
) ;
_loadItems () ;
}
void _removeItem ( GroceryItem item) {
setState (() {
_groceryItems . remove (item) ;
}) ;
}
@override
Widget build ( BuildContext context) {
Widget content = const Center (child : Text ( 'No items added yet.' )) ;
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) 이제는 pop에서 받은 데이터가 아닌 파.베에서 받은 값을 화면에 적용시켰다.
2) void _addItem() async { await Navigator.of(context).push<GroceryItem>( MaterialPageRoute( builder: (ctx) => const NewItem(), ), ); _loadItems(); }
--> newItem화면에서 값을 받아올때까지 기다렸다가, 값을 받아오면 _loadItems실행
3)
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; }); }
-->
1.파베로부터 값을 받아오는 로직이고, loadedItems의 빈 리스트에 값을 넣고 이를 다시 _groceryItems 에 할당함.
2. 파베에서 받아온 값중에 카테고리는 title값은 문자값이기 때문에 해당 문자값을 기준으로 로컬에서 가지고 있는 값을 찾기 위해 firstWhere를 사용 (뒤에 value가 왜 또 붙는지는 모르겠다. 없어도 작동하긴한다. 버그 잡는 용도라는데 자세히 모르겠음)
3. for 문을 통해 loadedItems 리스트에 값(클래스 값)을 하나씩 넣어줌
4. setState를 통해 바로 업데이트시켜줌
4)
@override void initState() { super.initState(); _loadItems(); }
--> 앱을 처음 켰을 때 바로 파베로부터 값을 가지고 오게함 (initState의 역할)