Front-End/Flutter_Project_03_ToDo APP

5. Udemy 강의를 통한 Meals Project

sd4beatles 2025. 1. 19. 21:20

1. Updating MealDeatilScreen

Category---> MealScreen ---> MealDatilsScreen으로 차례대로 코딩을 진행하였다.이때, MealDetailsScreen에는 위에 보이는 것처럼 이미지밖에 나오지 않도록 코드를 작성했는데, 이를 코드를 추가하여 , 음식성분 등 음식에 대한 세부정보를 추가하도록 하자.

import 'package:flutter/material.dart';
import 'package:meals/models/meal.dart';

class MealDetailsScreen extends StatelessWidget{
  const MealDetailsScreen({
    super.key,
    required this.meal,
    });

  final Meal meal;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar:AppBar(
        title:Text(meal.title),
      ),
      body:Column(
        children: [
          //image
          Image.network(
            meal.imageUrl,
            height:300,
            width:double.infinity,
            fit:BoxFit.cover,
          ),
          const SizedBox(height: 14,),
          //ingradients-title
          Text(
            'Ingredients',
            style:Theme.of(context).textTheme.titleLarge!.copyWith(
              color:Theme.of(context).colorScheme.primary,
              fontWeight: FontWeight.bold,
            )
          ),
          const SizedBox(height: 14,),
          //ingradients-contents
          for(final ingradient in meal.ingredients)
            Text(ingradient,
              style:Theme.of(context).textTheme.titleMedium!.copyWith(
                color:Theme.of(context).colorScheme.onSurface
              )),
          const SizedBox(height:14,),
          //steps-title
          Text('Steps',
            style:Theme.of(context).textTheme.titleLarge!.copyWith(
              color:Theme.of(context).colorScheme.primary,
              fontWeight: FontWeight.bold,
            )),
          //steps-contents
          for(final step in meal.steps)

            Text(
              step,
              textAlign: TextAlign.center,
              style:Theme.of(context).textTheme.bodyMedium!.copyWith(
                color:Theme.of(context).colorScheme.onSurface
              )

            )





        ],
        )

위 사진을 보면, steps에 간격이 너무 좋아서 가독성이 떨어지므로, 우리는 padding option을 집어넣기로 하자.

 for(final step in meal.steps)
            Padding(
              padding:const EdgeInsets.symmetric(
                horizontal: 12,
                vertical:6),
              child:Text(
                step,
                textAlign: TextAlign.center,
                style:Theme.of(context).textTheme.bodyMedium!.copyWith(
                color:Theme.of(context).colorScheme.onSurface
              )

            )

이와 같이 padding을 넣어주면, 범위의 관련된 오류가 발생한다.

scrollable과 관련된 오류

위와 같은 오류는 현재 widget이 scrollable하지 않기 때문에, 나오는 오류이다. 이를 수정하기 위해선, 우리는 ListView 또는 SingleChildScrollView를 사용한다. 전체 코드는 아래와 같다.

import 'package:flutter/material.dart';
import 'package:meals/models/meal.dart';

class MealDetailsScreen extends StatelessWidget{
  const MealDetailsScreen({
    super.key,
    required this.meal,
    });

  final Meal meal;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar:AppBar(
        title:Text(meal.title),
      ),
      body:SingleChildScrollView(
        child: Column(
          children: [
            //image
            Image.network(
              meal.imageUrl,
              height:300,
              width:double.infinity,
              fit:BoxFit.cover,
            ),
            const SizedBox(height: 14,),
            //ingradients-title
            Text(
              'Ingredients',
              style:Theme.of(context).textTheme.titleLarge!.copyWith(
                color:Theme.of(context).colorScheme.primary,
                fontWeight: FontWeight.bold,
              )
            ),
            const SizedBox(height: 14,),
            //ingradients-contents
            for(final ingradient in meal.ingredients)
              Text(ingradient,
                style:Theme.of(context).textTheme.titleMedium!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface
                )),
            const SizedBox(height:14,),
            //steps-title
            Text('Steps',
              style:Theme.of(context).textTheme.titleLarge!.copyWith(
                color:Theme.of(context).colorScheme.primary,
                fontWeight: FontWeight.bold,
              )),
            //steps-contents
            for(final step in meal.steps)
              Padding(
                padding:const EdgeInsets.symmetric(
                  horizontal: 12,
                  vertical:6),
                child:Text(
                  step,
                  textAlign: TextAlign.center,
                  style:Theme.of(context).textTheme.bodyLarge!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface
                )

              )


              )





          ],
          ),
      )






    );

  }

}

2. Adding Tab-based Navigation

공식문서에서 tab-based navigation을 아래와 같이 설명한다.

"Possibly the most common style of navigation in mobile apps is tab-based navigation. This module can manage the tabs on the screen."

/screens/tabs.dart 파일에 다가, 아래와 같이 코드를 작성한다.

import 'package:flutter/material.dart';
import 'package:meals/screens/categories.dart';
import 'package:meals/screens/meals.dart';

//하나의 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;
  var activePageTitle="Categories";

  void _selectPage(int index){
    setState((){
      _selectedPageIndex=index;

    });
  }

  @override
  Widget build(BuildContext context){
    Widget activePage=const CategoriesScreen();

    if(_selectedPageIndex==1){
      activePage=MealsScreen(title: 'Favorites', meals:[]);
      activePageTitle="Your favorite";
    }



    return Scaffold(
      appBar:AppBar(
        title:Text(activePageTitle),
      ),
      body:activePage,
      bottomNavigationBar:BottomNavigationBar(
        onTap:_selectPage,
        currentIndex: _selectedPageIndex,
        items:const [
          BottomNavigationBarItem(icon:Icon(Icons.set_meal),label:"Categories"),
          BottomNavigationBarItem(icon:Icon(Icons.start),label:"Favorite"),

        ]
       ),
    );
  }

}

그리고 main.dart에 다가

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: theme,
      home:const TabsScreen()  ,


    );
  }
}

두개의 tapBar

두개의 tapbar를 사용하니깐,사실상 design 측면에선 사실 미흡하다는 생각이 든다. 이때는, 단순히 ./screens/categories.dart에서 appBar를 지운다.

tab이 보이지 않는 문제점

tab이 보이지 않는 문제점이 있다. 예를들어, category를 select했는데 위에 이미지처럼 tab이 보이지 않는 문제점이 발생한다. 그렇다면, 이를 어떻게 관리해야할까?

./screens/meals.dart로 이동해서, MealsScreen의 멤버변수인 title을 required에서 null 값을 포함한 값으로 바꾼다.

class MealsScreen extends StatelessWidget{
  const MealsScreen({
    super.key,
    this.title,
    required this.meals
  });

  final String? title;
  final List<Meal>meals;
   if(title==null){
      return content;
    }

    return Scaffold(

      appBar:AppBar(
        title:Text(title!),
      ),
      //ListViewBuilder
      //itemCount만큼, itemBuilder를 실행시켜 widget를 반환
      body: content,
    );

  }

마지막으로 ./screens/tabs.dart로 가서, 아래와 같이 MealsScreen에서 title을 빼준다.

import 'package:flutter/material.dart';
import 'package:meals/screens/categories.dart';
import 'package:meals/screens/meals.dart';

//하나의 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;
  var activePageTitle="Categories";

  void _selectPage(int index){
    setState((){
      _selectedPageIndex=index;

    });
  }

  @override
  Widget build(BuildContext context){
    Widget activePage=const CategoriesScreen();

    if(_selectedPageIndex==1){
      activePage=const MealsScreen( meals:[]);
      activePageTitle="Your favorite";
    }