Front-End/Flutter_Project_02_Expense Tracker

3. Udemy Flutter 강의를 통한 Project - Expense Tracker

sd4beatles 2024. 12. 7. 23:35

## 3.1 Setting an AppBar with a Tittle

user가 자산의 expense를 추가할 수 있도록 해주는 하나의 장치를 추가하려고 한다. 이때, 자주 쓰이는 app layout이 바로 appbar이다. 이러한 기본적인 틀을 이용하여 앱의 모양을 만들도록 하자.

3.1.1 appBar 설정

expenses.dart 파일로 이동해서, 추가적인 코드를 집어넣어 보자. scaffold 부분에서 appBar option을 사용하도록 하자. 이때, 주의할 점은 appBar option은 특정 widget을 매개변수로 받게되어있는데, 이때 우리는 AppBar를 사용하도록 하겠다.

@override
Widget build(BuildContext context){
  return Scaffold(
    appBar:AppBar(
      title:Text("Flutter Expense Tracker"),
      actions:[
        IconButton(onPressed:(){}, 
        icon:const Icon(Icons.add),
        ),
      ]
    ),
    body:Column(
      children:  [

         Text("The chart"),
        Expanded(child:ExpensesList(expenses:_registeredExpenses)),
      ],
    ),

위의 코드를 실행하면, (+) 사인이 추가되는 것을 알 수 있다. main.dart에서 theme 부분을 아래의 "useMaterial3" false를 하게 되면, 또 다른 layout이 나오게 된다. 추가적인 theme option은 추후 레슨에서 다루겠다고 함.

theme useMaterial3 를 false 설정

3.2.2 BuildContext

*(정의 1) A handle to the location of a widget in the widget tree *

flutter에서 모든 widget 'build method' 함수를 가진다. 그리고 이를 통해서 계층구조를 만들게 된다. 흔히 우리가 StatelessWidget을 구성할 때, build함수를 정의했던 것을 기억하는가? 이때, build함수는 context를 매개변수로 받아들이고, Scaffold widget를 return하게 되는데, 이때 widget tree 상에서 어느 곳에 위치하고 있는지에 대한 정보를 담고 있는 context를 매개변수로 하여,return을 해준다는 말이다.

@override
Widget build(BuildContext context){
 return Scaffold(//생략)
}

출처: Coding Chef

**정의 2) Each widget has its own BuildContext, which becomes the parent of the widget returned by the StatelessWidget.build or State.build function **

말이 너무 추상적이라서 처음에 이해를 하지 못했는데, 유튜브의 한 영상을 보고 좀 이해를 할 수 있었다. 예를 들어, 그림과 같이 우리가 집을 Renovation하기 위해서, 가구 및 전자기계 재배치 및 벽지 색깔 설정등 여러가지 등을 설정할 수 있게 되는데, 이러한 역할을 바로 scaffold 담당하게 된다. 하지만, 웃기게도 scaffol는 tree에서 자신의 위치가 어디있는지를 알 수가 없게 되는데, 이는 자신의 위치 주소를 가지고 있지 않기 때문이다. 즉, 밑에 그림 처럼 집을 설정하는 방법은 가지고 있으나, 자신이 몇 층 몇 호에 사는지에 관련된 정보를 가지고 있지가 않다.

이때, context라는 위치 정보를 기반을 받아 들여서, Scaffold가 어느 위치에 있는지를 알게 된다. 게다가, Widget이 만약 child를 가지고 있다면(계층을 만든다고 위에서 이야기 했으므로), 부모의 build method에서 return된 Widget은 부모의 Scaffold 위젯의 진짜 context를 물려받 Widget이 된다. (이게 정말 중요함)

3.2.3 Bottom Model Sheet

+ 버튼을 누르면, 아래로부터 layout이 띄어지는 modal을 만들어 보도록하자. 이는 상대적으로 간단하게 사용할 수 있으며, showModalBottom



class _ExpensesState extends State<Expenses>{
final List<Expense>_registeredExpenses=[
  Expense(
    title:"Flutter Course",
    amount:12.22,
    date:DateTime.now(),
    category:Category.work),

  Expense(
    title:"50-60 Nights",
    amount:50,
    date:DateTime.now(),
    category:Category.leisure),
];

//add a function to show bottom sheet if (+) is clicked!
void _openAddExpenseOverlay(){
  showModalBottomSheet(
    context: context, 
    builder: (ctx)=> Text("Modal btoom sheet"),
    );
}

@override
Widget build(BuildContext context){
  return Scaffold(
    appBar:AppBar(
      title:Text("Flutter Expense Tracker"),
      actions:[
        IconButton(onPressed:_openAddExpenseOverlay, 
        icon:const Icon(Icons.add),
        ),
      ]
    ),
    body:Column(
      children:  [
        Text("The chart"),
        Expanded(child:ExpensesList(expenses:_registeredExpenses)),
      ],
    ),


  );
}

}

3.2.4 Bottom Model Sheet(Part2)

기존 expense widget class에서 openAddExpenseOverlay 함수부분을 수정할 수 있는 widget을 정의내리도록 하자. 이를 위해선, 우리는 추가적인 widget을 선언할 파일이 필요하며, 이를 위해선 new_expense widget class를 선언하도록 하자.

일단 메인 화면의 (+)를 클릭을 하면, 창이 아래로부터 나타나는 화면을 설정하기 위한 widget을 설정해 보자.

bottom pop up화면

import 'package:flutter/material.dart';

class NewExpense extends StatefulWidget {
  const NewExpense({super.key});

  @override
  State<NewExpense> createState() {
    return _NewExpenseState();
  }
}

class _NewExpenseState extends State<NewExpense> {
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(18),
      child: Column(
        children: const [
          TextField(
            maxLength: 50,
            decoration: InputDecoration(
              label: Text('Title'),
            ),
          ),
        ],
      ),
    );
  }
}

 

코드에서 눈여겨볼 점이 있는데, Padding의 child option에 들어가는 Column을 쓴 이유는, Tile외에 추가적으로 user가 집어넣을 항목을 설정하기 위함이다. 또한, TextField를 통해서, input field의 명칭과 decoration 등을 추가적으로 설정할 수가 있다는 점도 상기하도록 하자.

 

 

3.2.4 Bottom Model Sheet(Part3)

추가적으로, title 목록에 user가 input을 집어넣은 다음에 작동할 수 있는 meachanism을 구현해보도록 하자.

첫번째, 새로 추가할 expense의 목록이름을 '_enteredTtitle'이라고 이름을 설정하자. 초기값으론 null값을 가지며,

        이를 변경할 수 있는 함수를 설정하는 것이 필요하다. 이는, 아래와 같이 설정할 수 있다. 

class _NewExpenseState extends State<NewExpense> {
  var _enteredTitle='';

  void _saveTitleInput(String inputValue){
    _enteredTitle=inputValue;
  }

이 함수를 작동시키기 위해선, 우리는 TextField 부분이 onChanged 옵션을 추가하고, 함수를 집어넣도록 하자. 참고로,onChanged는 함수만을 매개변수로 받는 것을 잊지말자.

 

두번째, 변경된 내용을 updated할 수 있는 button이 필요한데, 이를 위해선 우리는 ElevatedButton option이 필요하다.

"ElevatedButton을 사용하는 기본적인 방법은 매우 간단하다. onPressed 콜백(event가 발생했을 때, 활성화되는 함수)과 child 속성을 사용하여 버튼이 눌렸을 때의 동작과 버튼에 표시될 위젯을 지정할 수 있다."


class _NewExpenseState extends State<NewExpense> {
  var _enteredTitle='';

  void _saveTitleInput(String inputValue){
    _enteredTitle=inputValue;
  }


  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(18),
      child: Column(
        children:  [
          TextField(
            //onChanged는 함수를 매개변수로 받음
            onChanged:_saveTitleInput,
            maxLength: 50,
            decoration: InputDecoration(
              label: Text('Title'),
            ),
          ),
          Row(children: [
            ElevatedButton(onPressed: (){
              //버튼을 누른 후 작동할 함수 설정
              print(_enteredTitle);
            }, 
            //button의 label 사용
            child: const Text('Save Expense'),)
          ],)




        ],
      ),
    );
  }
}