1. Adding More Filter Options
우리가 만든 filter에 option을 더 추가할 예정이다. 현재까지는 'Glutten-free' 였다면, 추가적으로 "Lactos-Free"를 더 추가해서 아래의 meal filter option을 풍요롭게 해준다.
import 'package:flutter/material.dart';
import 'tabs.dart';
import '../widget/main_drawer.dart';
class FilterScreen extends StatefulWidget{
const FilterScreen({super.key});
@override
State<StatefulWidget>createState(){
return _FilterScreen();
}
}
class _FilterScreen extends State<FilterScreen>{
var _glutenFreeFilterSet=false;
var _lactoseFreeFilterSet=false;
var _vegeterianFilterSet=false;
var _veganFilterSet=false;
@override
Widget build(BuildContext context) {
return Scaffold(
//Scaffold-appBar
appBar:AppBar(
title:const Text("Your filter"),
)
,//Scaffold-body
body:Column(
children: [
//value:boolean, onChanged:activation funciton if true
//title: message appears
//activeColor: choose the color if switch is activated
//SwitchListTile(01)
SwitchListTile(
value:_glutenFreeFilterSet ,
onChanged: (bool isChecked){
setState((){
_glutenFreeFilterSet=isChecked;
});
},
//1 ) SwitchListTile-Glutten-free
title:Text(
'Glutten-free',
style:Theme.of(context).textTheme.titleLarge!.copyWith(
color:Theme.of(context).colorScheme.onSurface,)),
//SwitchListTile-subtitle
subtitle:Text(
'Only include glute-free meals.',
style:Theme.of(context).textTheme.labelMedium!.copyWith(
color:Theme.of(context).colorScheme.onSurface,
)
),
//active color-color if switch is activated
activeColor: Theme.of(context).colorScheme.tertiary,
contentPadding: const EdgeInsets.only(left:34,right:22),
),
//2) SwitchListTitle-Lactose Free
SwitchListTile(
value:_lactoseFreeFilterSet,
onChanged:(bool isChecked){
setState((){
_lactoseFreeFilterSet=isChecked;
});
},
title:Text(
'Lactose-Free',
style:Theme.of(context).textTheme.titleLarge!.copyWith(
color:Theme.of(context).colorScheme.onSurface,)
),
subtitle:Text(
"Only include lactose-free meals",
style:Theme.of(context).textTheme.labelMedium!.copyWith(
color:Theme.of(context).colorScheme.onSurface,
)
),
activeColor:Theme.of(context).colorScheme.tertiary,
contentPadding: const EdgeInsets.only(left:34,right:22),
),
//3) SwitchListTitle-vegeterian
SwitchListTile(
value:_vegeterianFilterSet,
onChanged:(bool isChecked){
setState((){
_vegeterianFilterSet=isChecked;
});
},
title:Text(
'Vegetrian',
style:Theme.of(context).textTheme.titleLarge!.copyWith(
color:Theme.of(context).colorScheme.onSurface,)
),
subtitle:Text(
"Only include meals for Vegeterian",
style:Theme.of(context).textTheme.labelMedium!.copyWith(
color:Theme.of(context).colorScheme.onSurface,
)
),
activeColor:Theme.of(context).colorScheme.tertiary,
contentPadding: const EdgeInsets.only(left:34,right:22),
),
//4) SwitchListTitle-_veganFilterSet
SwitchListTile(
value:_veganFilterSet,
onChanged:(bool isChecked){
setState((){
_veganFilterSet=isChecked;
});
},
title:Text(
'Vegean',
style:Theme.of(context).textTheme.titleLarge!.copyWith(
color:Theme.of(context).colorScheme.onSurface,)
),
subtitle:Text(
"Only include meals for Vegan",
style:Theme.of(context).textTheme.labelMedium!.copyWith(
color:Theme.of(context).colorScheme.onSurface,
)
),
activeColor:Theme.of(context).colorScheme.tertiary,
contentPadding: const EdgeInsets.only(left:34,right:22),
),
],
)
);
}
}
2. Displaying Chosen Meals Based on Filter Selections
2.1 PopScope
2.2 Project (Part 01)
Filter부분에서 check된 selections을 토대로, 다시 tap화면으로 돌아갔을 때, 그 options들을 토대로 tap화면에는 선택된 음식들만 보여주게 끔 하면 된다. filters.dart로 다시 돌아가서, 아래의 Scaffold에 다가 refractor를 이용하여, wrap with widget을 사용한다. 그 후에, widget을 PopScope으로 작성하도록 하자. 그리고, onPopInvokedWithResult option에서, pop을 통해서 보내줄 데이터를 결정하다록 하자.
pop에서 보내줄 데이터는 key-value 형태의 map 자료구조이면, 우리는 unique한 key가 필요하므로, 일단 파일 서두에 enum(열거형 자료)를 통해서 unique key를 선정하도록 하자.
enum filter{
glutenFree,
lactosFree,
vegeterian,
vegan,
}
😒추가보충 내용 (강의내용이 너무 어렵고,교사의 내용전달이 부실함)
- 일반 코드의 흐름은 Navigator.of(context).pop([...result])는 이전에서 이야기 했던 것 처럼, 현재의 route를 제거(즉,현재화면을 닫고), 선택적으로 결과(result)를 이전 화면으로 반환할 수가 았다. 즉, pop()안에 있는 데이터가 이전의 화면으로 전달되는 값이다.
- onPopInvokedWithResult 는 뒤로 가기 동장이 호출되었을 때, 실행되는 것이다.
- didPop이 true라면, 뒤로 가기 동작이 이미 처리된 상태이므로, 추가 작업 없이 종료됨
- didPop이 false라면, 사용자가 뒤로 가기 동작을 차단했으며, 우리는 map을 이전화면으로 데이터반환할 예정임
```flutter
PopScope(
canPop: false,
onPopInvokedWithResult: (bool didPop, dynamic result) {
if(didPop) return;
Navigator.of(context).pop({
Filter.glutenFree: \_glutenFreeFilterSet,
Filter.lactoseFree: \_lactoseFreeFilterSet,
Filter.vegetarian: \_vegetarianFilterSet,
Filter.vegan: \_veganFilterSet,
});
},
child: Column(...) // same code as shown in the next lecture
),
The complete code is as follows;
import 'package:flutter/material.dart';
import 'tabs.dart';
import '../widget/main_drawer.dart';
enum Filter{
glutenFree,
lactoseFree,
vegetarian,
vegan
}
class FilterScreen extends StatefulWidget{
const FilterScreen({super.key});
@override
State<StatefulWidget>createState(){
return _FilterScreen();
}
}
class _FilterScreen extends State<FilterScreen>{
var _glutenFreeFilterSet=false;
var _lactoseFreeFilterSet=false;
var _vegetarianFilterSet=false;
var _veganFilterSet=false;
@override
Widget build(BuildContext context) {
return Scaffold(
//Scaffold-appBar
appBar:AppBar(
title:const Text("Your filter"),
)
,//Scaffold-body
body:PopScope(
canPop: false,
onPopInvokedWithResult: (bool didPop, dynamic result) {
if(didPop) return;
Navigator.of(context).pop({
Filter.glutenFree: _glutenFreeFilterSet,
Filter.lactoseFree:_lactoseFreeFilterSet,
Filter.vegetarian:_vegetarianFilterSet,
Filter.vegan: _veganFilterSet, });
},
child: Column(
children: [
//value:boolean, onChanged:activation funciton if true
//title: message appears
//activeColor: choose the color if switch is activated
//SwitchListTile(01)
SwitchListTile(
value:_glutenFreeFilterSet ,
onChanged: (bool isChecked){
setState((){
_glutenFreeFilterSet=isChecked;
});
},
//1 ) SwitchListTile-Glutten-free
title:Text(
'Glutten-free',
style:Theme.of(context).textTheme.titleLarge!.copyWith(
color:Theme.of(context).colorScheme.onSurface,)),
//SwitchListTile-subtitle
subtitle:Text(
'Only include glute-free meals.',
style:Theme.of(context).textTheme.labelMedium!.copyWith(
color:Theme.of(context).colorScheme.onSurface,
)
),
//active color-color if switch is activated
activeColor: Theme.of(context).colorScheme.tertiary,
contentPadding: const EdgeInsets.only(left:34,right:22),
),
//2) SwitchListTitle-Lactose Free
SwitchListTile(
value:_lactoseFreeFilterSet,
onChanged:(bool isChecked){
setState((){
_lactoseFreeFilterSet=isChecked;
});
},
title:Text(
'Lactose-Free',
style:Theme.of(context).textTheme.titleLarge!.copyWith(
color:Theme.of(context).colorScheme.onSurface,)
),
subtitle:Text(
"Only include lactose-free meals",
style:Theme.of(context).textTheme.labelMedium!.copyWith(
color:Theme.of(context).colorScheme.onSurface,
)
),
activeColor:Theme.of(context).colorScheme.tertiary,
contentPadding: const EdgeInsets.only(left:34,right:22),
),
//3) SwitchListTitle-vegeterian
SwitchListTile(
value:_vegetarianFilterSet,
onChanged:(bool isChecked){
setState((){
_vegetarianFilterSet=isChecked;
});
},
title:Text(
'Vegetrian',
style:Theme.of(context).textTheme.titleLarge!.copyWith(
color:Theme.of(context).colorScheme.onSurface,)
),
subtitle:Text(
"Only include meals for Vegeterian",
style:Theme.of(context).textTheme.labelMedium!.copyWith(
color:Theme.of(context).colorScheme.onSurface,
)
),
activeColor:Theme.of(context).colorScheme.tertiary,
contentPadding: const EdgeInsets.only(left:34,right:22),
),
//4) SwitchListTitle-_veganFilterSet
SwitchListTile(
value:_veganFilterSet,
onChanged:(bool isChecked){
setState((){
_veganFilterSet=isChecked;
});
},
title:Text(
'Vegean',
style:Theme.of(context).textTheme.titleLarge!.copyWith(
color:Theme.of(context).colorScheme.onSurface,)
),
subtitle:Text(
"Only include meals for Vegan",
style:Theme.of(context).textTheme.labelMedium!.copyWith(
color:Theme.of(context).colorScheme.onSurface,
)
),
activeColor:Theme.of(context).colorScheme.tertiary,
contentPadding: const EdgeInsets.only(left:34,right:22),
),
],
),
)
);
}
}
그리고 난 다음에 다시 ./screens/tabs.dart로 돌아간다. 그리고는 아래와 같이 void _setScreen 함수에 아래와 같이 코드를 수정해주는데,이는 아래와 같다. ( 반환되는 값은 Futre이므로 꼭 async awit문을 쓸 것!!)
void _setScreen(String identifier)async {
Navigator.of(context).pop();
if(identifier=='filters'){
final result=await Navigator.of(context).push<Map<Filter,bool>>(
MaterialPageRoute(builder: (ctx)=>const FilterScreen(),
)
);
2.3 Project (Part 02) Applying Filters
step 01)
다시 tabs.dart로 돌아가서, void _setScreen 함수를 찾아보도록 하자. 일단, tabs.dart상단에 우리가 이전 Map에서 지정했던, 모든 key,value를 담은 Map 자료구조를 사용하도록 하자. 이 자료구조는 global 변수로서, 초기화값을 담을 때 사용한다.
const KInitialFilters={
Filter.glutenFree:false,
Filter.lactoseFree:false,
Filter.vegetarian:false,
Filter.vegan:false,
};
또한, _TabsScreen에 _selectedFilters의 초기화값으로 KInitialFilters를 사용하도록 하자.
class _TabsScreen extends State<TabsScreen>{
int _selectedPageIndex=0;
final List<Meal>_favoriteMeals=[];
Map<Filter,bool>_selectedFilters=KInitialFilters;
void _setScreen함수에서, setState() 함수를 다시 작동시켜서, filter의 초기화값을 result값으로 설정한 후, 다시 화면을 전환하는 방법을 설정한다. 근데 이때, _selectedFilters는 절대로 null값을 허용하지 않는다. 그러므로 dart문법에 사용되어지는, ?? 사용을 권장한다. 이때, left_value가 null이면, right_value를 자동으로 채택하게 된다.
//new function
void _setScreen(String identifier)async {
Navigator.of(context).pop();
if(identifier=='filters'){
final result=await Navigator.of(context).push<Map<Filter,bool>>(
MaterialPageRoute(builder: (ctx)=>const FilterScreen(),
)
);
setState(() {
_selectedFilters=result??KInitialFilters;
});
}else{
Navigator.of(context).pop();
}
}
step 02) 정의내린 filter를 category screen에 적용하도록 하자.
Widget build함수에 availableMeals의 list를 하나 만들어준다. 이 list의 원소는 아래와 같다. 아래의 예시는 glutenFree이고, 이를 적용한 알고리즘은 다른 항목에도 동일하게 적용된다.
- 조건(1)_selectedFilters[Filter.glutenFree]항목이 true이면, 배제
- 조건(2)dummyMeals의 data중에서 meal.isGluten항목이 false이면, !meal.isGlutenFree는 true가 됨.
두 조건이 true이면, 해당 Meal항목을 제거해야 하므로, false를 반한한다.
final availableMeals=dummyMeals.where((meal){
if(_selectedFilters[Filter.glutenFree]! && !meal.isGlutenFree){
return false;
}
if(_selectedFilters[Filter.lactoseFree]! && !meal.isLactoseFree){
return false;
}
if(_selectedFilters[Filter.vegan]! && !meal.isVegan){
return false;
}
if(_selectedFilters[Filter.vegetarian]! && !meal.isVegetarian){
return false;}
return true;
}).toList();
이제,filter의 모든 항목을 정했으니, 이를 CategoryScreen에 적용해야 한다. 앞에 정의내린 List을 받아줄 변수를 하나 생성한다.
class CategoriesScreen extends StatelessWidget{
const CategoriesScreen({
super.key,
required this.ontoggleFavorite,
required this.availableMeals,
});
final void Function(Meal meal) ontoggleFavorite;
final List<Meal>availableMeals;
void __selectCategory(BuildContext context,Category category){
final filterMeals=availableMeals.where((meal)=>meal.categories.contains(category.id)).toList();
그렇다면, tab.dart에 CategoriesScreen부분 또한 이 변화된 내용을 적용해야하므로, 아래와 같이 정의한다.
Widget activePage=CategoriesScreen(
ontoggleFavorite:_toggleMealFavoriteStatus,
availableMeals: availableMeals,);
step 03) 아래의 filter내용을 조정한다.
import 'package:flutter/material.dart';
import 'tabs.dart';
import '../widget/main_drawer.dart';
enum Filter{
glutenFree,
lactoseFree,
vegetarian,
vegan
}
class FilterScreen extends StatefulWidget{
const FilterScreen({super.key,required this.currentFilters});
final Map<Filter,bool>currentFilters;
@override
State<StatefulWidget>createState(){
return _FilterScreen();
}
}
class _FilterScreen extends State<FilterScreen>{
var _glutenFreeFilterSet=false;
var _lactoseFreeFilterSet=false;
var _vegetarianFilterSet=false;
var _veganFilterSet=false;
@override
void initState(){
super.initState();
_glutenFreeFilterSet=widget.currentFilters[Filter.glutenFree]!;
_lactoseFreeFilterSet=widget.currentFilters[Filter.lactoseFree]!;
_vegetarianFilterSet=widget.currentFilters[Filter.vegetarian]!;
_veganFilterSet=widget.currentFilters[Filter.vegan]!;
}
@override
Widget build(BuildContext context) {
return Scaffold(
//Scaffold-appBar
appBar:AppBar(
title:const Text("Your filter"),
)
,//Scaffold-body
body:PopScope(
canPop: false,
onPopInvokedWithResult: (bool didPop, dynamic result) {
if(didPop) return;
Navigator.of(context).pop({
Filter.glutenFree: _glutenFreeFilterSet,
Filter.lactoseFree: _lactoseFreeFilterSet,
Filter.vegetarian: _vegetarianFilterSet,
Filter.vegan: _veganFilterSet,
});
},
child: Column(
children: [
아래는 full code
./screens/filter.dart
import 'package:flutter/material.dart';
import 'tabs.dart';
import '../widget/main_drawer.dart';
enum Filter{
glutenFree,
lactoseFree,
vegetarian,
vegan
}
class FilterScreen extends StatefulWidget{
const FilterScreen({super.key,required this.currentFilters});
final Map<Filter,bool>currentFilters;
@override
State<StatefulWidget>createState(){
return _FilterScreen();
}
}
class _FilterScreen extends State<FilterScreen>{
var _glutenFreeFilterSet=false;
var _lactoseFreeFilterSet=false;
var _vegetarianFilterSet=false;
var _veganFilterSet=false;
@override
void initState(){
super.initState();
_glutenFreeFilterSet=widget.currentFilters[Filter.glutenFree]!;
_lactoseFreeFilterSet=widget.currentFilters[Filter.lactoseFree]!;
_vegetarianFilterSet=widget.currentFilters[Filter.vegetarian]!;
_veganFilterSet=widget.currentFilters[Filter.vegan]!;
}
@override
Widget build(BuildContext context) {
return Scaffold(
//Scaffold-appBar
appBar:AppBar(
title:const Text("Your filter"),
)
,//Scaffold-body
body:PopScope(
canPop: false,
onPopInvokedWithResult: (bool didPop, dynamic result) {
if(didPop) return;
Navigator.of(context).pop({
Filter.glutenFree: _glutenFreeFilterSet,
Filter.lactoseFree: _lactoseFreeFilterSet,
Filter.vegetarian: _vegetarianFilterSet,
Filter.vegan: _veganFilterSet,
});
},
child: Column(
children: [
//value:boolean, onChanged:activation funciton if true
//title: message appears
//activeColor: choose the color if switch is activated
//SwitchListTile(01)
SwitchListTile(
value:_glutenFreeFilterSet ,
onChanged: (bool isChecked){
setState((){
_glutenFreeFilterSet=isChecked;
});
},
//1 ) SwitchListTile-Glutten-free
title:Text(
'Glutten-free',
style:Theme.of(context).textTheme.titleLarge!.copyWith(
color:Theme.of(context).colorScheme.onSurface,)),
//SwitchListTile-subtitle
subtitle:Text(
'Only include glute-free meals.',
style:Theme.of(context).textTheme.labelMedium!.copyWith(
color:Theme.of(context).colorScheme.onSurface,
)
),
//active color-color if switch is activated
activeColor: Theme.of(context).colorScheme.tertiary,
contentPadding: const EdgeInsets.only(left:34,right:22),
),
//2) SwitchListTitle-Lactose Free
SwitchListTile(
value:_lactoseFreeFilterSet,
onChanged:(bool isChecked){
setState((){
_lactoseFreeFilterSet=isChecked;
});
},
title:Text(
'Lactose-Free',
style:Theme.of(context).textTheme.titleLarge!.copyWith(
color:Theme.of(context).colorScheme.onSurface,)
),
subtitle:Text(
"Only include lactose-free meals",
style:Theme.of(context).textTheme.labelMedium!.copyWith(
color:Theme.of(context).colorScheme.onSurface,
)
),
activeColor:Theme.of(context).colorScheme.tertiary,
contentPadding: const EdgeInsets.only(left:34,right:22),
),
//3) SwitchListTitle-vegeterian
SwitchListTile(
value:_vegetarianFilterSet,
onChanged:(bool isChecked){
setState((){
_vegetarianFilterSet=isChecked;
});
},
title:Text(
'Vegetrian',
style:Theme.of(context).textTheme.titleLarge!.copyWith(
color:Theme.of(context).colorScheme.onSurface,)
),
subtitle:Text(
"Only include meals for Vegeterian",
style:Theme.of(context).textTheme.labelMedium!.copyWith(
color:Theme.of(context).colorScheme.onSurface,
)
),
activeColor:Theme.of(context).colorScheme.tertiary,
contentPadding: const EdgeInsets.only(left:34,right:22),
),
//4) SwitchListTitle-_veganFilterSet
SwitchListTile(
value:_veganFilterSet,
onChanged:(bool isChecked){
setState((){
_veganFilterSet=isChecked;
});
},
title:Text(
'Vegean',
style:Theme.of(context).textTheme.titleLarge!.copyWith(
color:Theme.of(context).colorScheme.onSurface,)
),
subtitle:Text(
"Only include meals for Vegan",
style:Theme.of(context).textTheme.labelMedium!.copyWith(
color:Theme.of(context).colorScheme.onSurface,
)
),
activeColor:Theme.of(context).colorScheme.tertiary,
contentPadding: const EdgeInsets.only(left:34,right:22),
),
],
),
)
);
}
}
./screens/tabs.dart
import 'package:flutter/material.dart';
import 'package:meal_app/data/dummy_data.dart';
import 'categories.dart';
import 'filters.dart';
import 'meals.dart';
import '../models/meal.dart';
import '../widget/main_drawer.dart';
import '../widget/meal_item.dart';
const KInitialFilters={
Filter.glutenFree:false,
Filter.lactoseFree:false,
Filter.vegetarian:false,
Filter.vegan:false,
};
//하나의 route에서 다른 route로 이동을 해야하므로, 우리는
//StatelessWidget이 아닌 StatefulWidget를 사용한다.
class TabsScreen extends StatefulWidget{
const TabsScreen({super.key});
@override
State<TabsScreen>createState(){
return _TabsScreen();
}
}
class _TabsScreen extends State<TabsScreen>{
int _selectedPageIndex=0;
final List<Meal>_favoriteMeals=[];
Map<Filter,bool>_selectedFilters=KInitialFilters;
/*
그렇다면, snackBar라는 option을 통해서 우리가 전달하고자 하는 메시지를
인자로 주고, 그걸 snackBar에 보여주는 것을 목표로 한다.
1) _favoriteMeals에 meal을 추가할때,
"Marked as a favorite"메세지 보내기
2) _favoriteMeals에서 meal을 삭제할때,
"Meal is no longer a favorite"메시지 보내기
*/
void _showInfoMessage(String message){
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
),
);
}
void _toggleMealFavoriteStatus(Meal meal){
//parameter로 받은 meal이 현재 _favoriteMeal에 있는지 체크
final isExisting=_favoriteMeals.contains(meal);
if(isExisting){
setState(() {
_favoriteMeals.remove(meal);
});
_showInfoMessage("Meal is no longer a favorite");
}else{
setState(() {
_favoriteMeals.add(meal);
});
_showInfoMessage("Marked as a favorite");
}
}
var activePageTitle="Categories";
void _selectPage(int index){
setState((){
_selectedPageIndex=index;
});
}
//new function
void _setScreen(String identifier)async {
Navigator.of(context).pop();
if(identifier=='filters'){
final result=await Navigator.of(context).push<Map<Filter,bool>>(
MaterialPageRoute(builder: (ctx)=>FilterScreen(currentFilters: _selectedFilters,),
)
);
setState(() {
_selectedFilters=result??KInitialFilters;
});
}else{
Navigator.of(context).pop();
}
}
@override
Widget build(BuildContext context){
final availableMeals=dummyMeals.where((meal){
if(_selectedFilters[Filter.glutenFree]! && !meal.isGlutenFree){
return false;
}
if(_selectedFilters[Filter.lactoseFree]! && !meal.isLactoseFree){
return false;
}
if(_selectedFilters[Filter.vegan]! && !meal.isVegan){
return false;
}
if(_selectedFilters[Filter.vegetarian]! && !meal.isVegetarian){
return false;}
return true;
}).toList();
Widget activePage=CategoriesScreen(
ontoggleFavorite:_toggleMealFavoriteStatus,
availableMeals: availableMeals,);
if(_selectedPageIndex==1){
activePage=MealsScreen(
meals:_favoriteMeals,
ontoggleFavorite:_toggleMealFavoriteStatus,);
activePageTitle="Your favorite";
}
return Scaffold(
appBar:AppBar(
title:Text(activePageTitle),
),
drawer: MainDrawer(
onSelectScreen:_setScreen,
),
body:activePage,
bottomNavigationBar:BottomNavigationBar(
onTap:_selectPage,
currentIndex: _selectedPageIndex,
items:const [
BottomNavigationBarItem(icon:Icon(Icons.set_meal),label:"Categories"),
BottomNavigationBarItem(icon:Icon(Icons.star),label:"Favorite"),
]
),
);
}
}
./screens/categories.dart
import 'package:flutter/material.dart';
import '../data/dummy_data.dart';
import '../widget/category_grid_item.dart';
import '../models/category.dart';
import '../models/meal.dart';
import './meals.dart';
class CategoriesScreen extends StatelessWidget{
const CategoriesScreen({
super.key,
required this.ontoggleFavorite,
required this.availableMeals,
});
final void Function(Meal meal) ontoggleFavorite;
final List<Meal>availableMeals;
void __selectCategory(BuildContext context,Category category){
final filterMeals=availableMeals.where((meal)=>meal.categories.contains(category.id)).toList();
Navigator.of(context).push(
MaterialPageRoute(
builder: (context)=>MealsScreen(
title: 'Welcome to ${category.title} section',
meals: filterMeals,
ontoggleFavorite:ontoggleFavorite,
) )
);
}
@override
Widget build(BuildContext context){
return Scaffold(
//appBar
body:GridView(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 3/2,
crossAxisSpacing: 20,
mainAxisSpacing: 20,
),
children: [
for(final category in availableCategories)
CategoryGridItem(
category:category,
onSelectCategory:(){
__selectCategory(context,category);
},
)
],
),
);
}
}
'Front-End > Flutter_Project_03_ToDo APP' 카테고리의 다른 글
7.Udemy 강의를 통한 Meals Project (0) | 2025.01.21 |
---|---|
6. Udemy 강의를 통한 Meals Project (0) | 2025.01.20 |
5. Udemy 강의를 통한 Meals Project (0) | 2025.01.19 |