Front-End/Flutter_Project_02_Expense Tracker

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

sd4beatles 2024. 12. 1. 22:01

1. Defining Expenses/Expense Widget Class

간략하게 StatefulWidget를 정의할 class를 만든다. 현재 step에선 부가적인 기능이 들어가기 전이라 Scaffold(앱의 뼈대를 위한 구성요소를 제공하는 Widget)에 임시 Text Widget을 집어넣는다.

import 'package:flutter/material.dart';

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

  @override
  State<Expenses>createState(){
    return _ExpensesState();
  }
}




class _ExpensesState extends State<Expenses>{

@override
Widget build(BuildContext context){
  return Scaffold(
    body:Column(
      children: const [
        Text("The car"),
        Text("The here"),
      ],
    ),


  );}
}

2.1 List Initializer

각 데이터가 지니는 특징을 구형할 수 있는 widget class를 만들어야 하는데,이를 Model이라는 폴더를 생성하고 그
아래에 expense.dart라는 파일을 집어넣도록 하자.

아래는 Expense Widget Class의 구성을 담은 내용을 간략하게 적어보았다.

  • title (String)
  • amount(double)
  • date (DateTime)
  • id (uuid)

이 때, backend에서도 많이 보았던 고유식별번호인 UUID를 사용하는데, 이를 위해선 flutter에서 module package를 설치해야 한다.

```flutter

flutter pub add uuid

```

더불어서, flutter에는 'initializer list'라는 keyword가 존재하게 된다. 이는, 현재 construcotr에서 받아들이는 외부 parameter외에 이미 class에서 member 변수로서 초기화시킬 때 사용하는 하나의 방법이라 생각하면 된다. 말이 어려우나, 간단히 "constructor에서 오지 않는 매개변수를 내부적으로 class 맴버 변수를 초기화 시키는 하나의 방법이다" 라고 생각하면 됨.

import 'package:uuid/uuid.dart';



// make uuid unresulable 
const  uuid= Uuid();


class Expense{

  //constructor;retrieve arguments
  Expense({
    required this.title,
    required this.amount,
    required this.date,

  }): id=uuid.v4();

  final String id; 
  final String title;
  final double amount;
  final DateTime date;

}

2.2 Enumerator

User에게 category를 선택할 수 있도록, 하나의 category를 만들 필요가 있다. 이 때 사용하는 자료구조가 바로 enum이라는 것이다.

enum <Name> {....}

아래의 코드는 Category에 end-user가 선택할 수 있는 목록들을 집어넣으면 되는데, 이때 _주의할 점은 Caetory에 있는 목록들은 쌍따움표나 따움표를 사용하지 않는다는 점_이다.

import 'package:uuid/uuid.dart';



// make uuid unresulable 
const  uuid= Uuid();

enum Category {food,travel,leisure,work}

class Expense{

  //constructor;retrieve arguments
  Expense({
    required this.title,
    required this.amount,
    required this.date,
    required this.category,
  }): id=uuid.v4();

  final String id; 
  final String title;
  final double amount;
  final DateTime date;
  final Category category;
}

3. Add List of Expense to Expenses class

Expenses class에서 memeber변수 하나를 추가하도록 해보자. Expenses Class의 맴버변수의 _registeredExpenses 를 하나 추가하고, 이는 Expense를 원소를 갖는 list로 정하도록 하자. 물론, list안에 있는 Expense의 매개변수는 하드코딩으로 하자.

import 'package:flutter/material.dart';
import 'package:expense_tracker/models/expense.dart';


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

  @override
  State<Expenses>createState(){
    return _ExpensesState();
  }
}




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),
]

@override
Widget build(BuildContext context){
  return Scaffold(
    body:Column(
      children: const [
        Text("The car"),
        Text("The here"),
      ],
    ),


  );
}

}

4. Efiiciently Rendering Long Lists with ListView

User가 소비했던, expense class의 정보들을 모아두고, 이를 화면에 보여주는 것을 생각할 수 있다. 그럼, 첫번째로 우리가 사용할 수 있는 방법은 flutter에서 제공하는 library 'ListView"를 생각할 수 있다. 그러나, 이 ListView에는 치명적인 단점이 존재하는데, List에 속한 원소들의 수가 적은 경우에는 쉽게 사용할 수 있지만, 이게 원소의 숫자가 길어지면 사용하기가 무진장 힘들어진다. 그러므로, 우리는 이를 대체할 수 있는,
ListView.builder를 사용하도록 하자.

 

이들을 간략하게 구별하는 방법은 아래를 참고하도록 하자!

  1. 일반적인 ListView를 명시적으로 호출하고 children 전달하는 방법 - 적은 데이터에 사용시 용이함
  2. ListView.builder builder를 사용하여 동적으로 호출 - 많은 양의 데이터 리스트에 용이함 index사용가능
✍️ListView.builder 아래의 supporting document를 참고하자면, ListView.builder는 - input: Function을 받으며, 매개변수로는 BuildContext 와 int를 받는다. - output: Widget를 return 해야함.

 

lib 폴더에 ExpensesList class를 정의한다. build Widget에서 ListView.Builder를 사용하도록 허용하며, Text Widget은 선택된 index에 해당되는  expense을 선정해서, 그에 해당되는 title를 화면에 보여준다.

 

import 'package:flutter/material.dart';
import 'package:expense_tracker/models/expense.dart';

class ExpensesList extends StatelessWidget{
  //initialize constructor
  const ExpensesList({super.key,required this.expenses,});


  final List<Expense>expenses;



  @override
  Widget build(BuildContext context){

    return ListView.builder(itemCount:expenses.length ,itemBuilder: (ctx,index)=>Text(expenses[index].title),
    );
  }
}

5. Efiiciently Rendering Long Lists with ListView

위에 정의내린 ExpensesList를 이제 Expense 화면에 집어 넣으려고 하자. 이때, 우리가 할 수 있는 방법은 아래와 같다. Expense class의 children list속에 ExpenseList를 집어넣는 것이다. 아래처럼 말이다.

#expense.dart
import 'package:flutter/material.dart';
import 'package:expense_tracker/models/expense.dart';
import 'package:expense_tracker/expenses_list.dart';

class Expenses extends StatefulWidget{
# 생략
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),
];

@override
Widget build(BuildContext context){
  return Scaffold(
    body:Column(
      children:  [
         Text("The chart"),
        #List안에 list를 집어넣지만 작동이 안됨. 
        ExpensesList(expenses:_registeredExpenses)
      ],
    ),


  );
}

}

 

 

그러나, 우리의 기대와는 다르게 화면에는 우리가 ListView에서 지정한 원소들이 나오지를 않는다. 이게 왜 그런것일까? 이유는 Colum안에 ExpenseList를 집어넣었고, 그 List에는 또 column이 존재하기 때문이다. 즉, Column안에 Column이 존재하기 때문이다. 이를 우회하는 방법은 Expanded Widget 을 사용하는 방법이 있다.

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),
];

@override
Widget build(BuildContext context){
  return Scaffold(
    body:Column(
      children:  [
         Text("The chart"),
        Expanded(child:ExpensesList(expenses:_registeredExpenses)),
      ],
    ),


  );
}

}