Flutter Study-3주차(Expense Tracker)

CHO WanGi·2023년 11월 5일

Flutter

목록 보기
6/27

Enum

열거형, 정해진 수의 상수값을 갖는 일종의 클래스

enum Category { food, travel, leisure, work }

const categoryIcons = {
  Category.food: Icons.lunch_dining,
  Category.travel: Icons.flight,
  Category.leisure: Icons.movie,
  Category.work: Icons.work,
};
  • indexing도 가능
  • 리스트로도 얻을 수 있음
  • switch 문 사용도 가능하나 모든 Enum 요소를 처리해야함

-> 여러가지 옵션을 미리 지정하여 단순 오타로 인한 코드 오류 방지가 가능

uuid 패키지

인스턴스 화 될때 마다 각각의 요소가 고유의 id를 얻고자 할때

Expense({
    required this.title,
    required this.amount,
    required this.date,
  }): id = uuid.v4();

개체를 생성하고 얻을 때마다 각 개체에 고유의 id가 생성됨

ListView

 ListView.builder(
      itemCount: expenses.length,
      itemBuilder: (ctx, index) => Dismissible(
        key: ValueKey(expenses[index]),
        background: Container(
          color: Theme.of(context).colorScheme.error.withOpacity(0.75),
          margin: EdgeInsets.symmetric(
            horizontal: Theme.of(context).cardTheme.margin!.horizontal,
          ),
        ),
        onDismissed: (direction) {
          onRemoveExpense(expenses[index]);
        },
        child: ExpenseItem(
          expenses[index],
        ),
      ),
    );

🚨 길이를 모르는 리스트를 Column 위젯에 넣으면 길이가 무한정 길어지는 문제가 발생

ListView 위젯을 통해 Scrollable 하게 만들 필요성이 있음

builder

builder 속성을 사용하여 스크롤 가능한 리스트를 생성
보이기 직전에만 생성이 가능하고, 보이지 않을때는 생성 X -> 앱의 퍼포먼스 향상

  • 어떤 요소를 포함할지, 언제 빌드할지를 알려주는 역할

  • 서로다른 위젯이 어떻게 출현할 지도 결정이 가능

  • itemCount : 몇개의 아이템이 렌더링 될것인가

ListView vs SingleChildScrollView

둘다 Scrollable 하게 만드는 목적은 동일
그러나 반복되는 위젯을 사용할 경우에는 캐싱문제로 ListView를 권장
(가계부앱같은 경우 Expense라는 위젯이 계속 반복되기에 ListView를 사용한듯)

ListView vs ListView.builder

왜 builder를 따로 제공할까?
바로 itemcount의 존재때문, 렌더링개수의 범위를 알려줌으로써 더 빠른 렌더링이 가능하다고 함.

출처: https://velog.io/@tygerhwang/Flutter-Scroll-View-%EB%A7%8C%EB%93%A4%EA%B8%B03-ListView

List inside of List

만약 이 리스트를 중첩해서 위젯에 할당할 경우
Colunm 안의 Colunm같은 경우에는 Expanded 를 통해 크기 관련 문제를 해결하는 것이 필수!

body: Column(
        children: [
          // Toolbar
          Chart(expenses: _registeredExpenses),
          Expanded(
            child: mainContent,
          ),
        ],
      ),

-> 위 코드처럼 Colunm안에 mainContent라는 내용을 집어넣을 경우 Expanded를 통해서 크기 관련 문제를 해결해야함.

Card

스타일링 목적의 위젯, 카드형 이미지

  • padding 옵션이 없기에 Text(내부요소)를 Padding 으로 리팩터링할 필요가 있음
  • Spacer()를 통해 위젯 사이 남은 공간을 만들어줌

AppBar

상단에 대부분 배터리 잔량, 시간등 장치를 위한 표기가 있음,
이를 고려하기 위한 위젯

  • elevation 옵션을 통한 그림자 정도 변경도 가능

ShowModalBottomSheet

overlay의 목적,
contextbuilder를 전달해야함

  • context: 빌드 메서드 밖이지만 전역적으로 사용이 가능한 객체로 meta information 이라고도 하며, 다른 위젯과의 관계를 표현하는데 사용

모달창을 닫을때는 Naviagator 클래스를 사용

https://api.flutter.dev/flutter/material/showModalBottomSheet.html

Textfield

사용자의 입력을 받기 위한 위젯

TextField(
                  controller: _amountController,
                  keyboardType: TextInputType.number,
                  decoration: const InputDecoration(
                    prefixText: '\$',
                    label: Text('Amount'),
                  ),
                ),
  • 입력시 키보드 타입도 설정이 가능

TextEditingController

Textfield위젯을 통해 사용자가 입력을 했으면, 그 입력값을 가져오는 것이 필요
이때 사용하는 것이 TextEditingController

다만, 위젯이 필요없다면(오버레이가 닫힐경우) 플러터에게 컨트롤러를 지우라고 명령하는 것이 필수 -> 계속 메모리를 소모하기 때문

dispose 메서드를 통해 플러터에게 위젯이나 State를 지우라고 명령하는 것이 필요함

void dispose() {
    _titleController.dispose();
    _amountController.dispose();
    super.dispose();
  }

StatefulWidget의 생명주기

출처 : https://fronquarry.tistory.com/16

  • 위젯구축 : createState -> initState -> didChangeDependencies
  • 재드로잉 : didUpdateWidget + build + setState
  • 위젯파기 : deactivate -> dispose

Future 타입 & Async와 Await

like JS의 Promise
지금은 없지만 미래에 요청할 데이터가 담길 상자의 느낌

https://velog.io/@jintak0401/FlutterDart-%EC%97%90%EC%84%9C%EC%9D%98-Future-asyncawait

void main() {
  // future 라는 변수에서 미래에(3초 후에) int가 나올 것입니다
  Future<int> future = futureNumber();

  future.then((val) {
    // int가 나오면 해당 값을 출력
    print('val: $val');
  }).catchError((error) {
    // error가 해당 에러를 출력
    print('error: $error');
  });

  print('기다리는 중');
}

라는 코드가 존재할때, 동기적 실행이라면 val이 먼저 나오고 기다리는 중이라는 문자열이 나오겠지만, then 함수를 통해서 Future<int> 를 다루기 때문에 비동기적으로 실행되어
기다리는 중이 먼저 나오고 그다음 Future<int> 값을 얻을 수 있다.
이를 통해 Future<int> 의 값을 얻을때까지 기다리는 것이 아니라, 효율적으로 실행 할 수 있게 된다.

async 와 await 은 왜 사용하는 걸까?

Future<ProcessedData> createData() {
  return _loadFromDisk().then((id){
    return _fetchNetworkData(id);
  }).then((data){
    return ProcessedData(data);
  })
}  
Future<ProcessedData> createDate() async {
  final id = await _loadFromDisk();
  final data = await _fetchNetworkData(id);
  return ProcessedData(data);
}

두 코드를 비교했을때 어떤 값이 리턴이 되는지 async/await 을 통해 더 명확하게 파악이 가능하다.

showDialog & AlertDialog

팝업 메시지를 구현할때 필요한 두가지 위젯

if (_titleController.text.trim().isEmpty ||
        amountIsInvalid ||
        _selectedDate == null) {
      showDialog(
        context: context,
        builder: (ctx) => AlertDialog(
          title: const Text('Invalid input'),
          content: const Text(
              'Please make sure a valid title, amount, date and category was entered :)'),
          actions: [
            TextButton(
              onPressed: () {
                Navigator.pop(ctx);
              },
              child: const Text('Okay'),
            )
          ],
        ),
      );
      return;
    }

위 코드는 사용자의 입력이 빈값이 아닌지, 날짜를 선택했는지, 가격이 제대로 입력되었는지를 확인하는 기능의 코드이다.

만약 어느 하나라도 조건을 만족하지 않으면 팝업 메시지를 통해서 유저에게 잘못된 입력을 안내한다.

context를 입력으로 받아서 AlertDialog로 변환하는 것을 볼 수 있다.

Dismissible

위젯을 스와이핑을 통해 없앨 수 있도록 하는 기능을 담당

  • key 매커니즘을 통해 위젯을 구분한다
  • onDismissed : 화면상에서만 삭제될 경우를 대비하여 실제 데이터에서도 지우도록 하는 옵션

Snackbar

화면 아래 간단한 메시지를 보여줄 때 사용하는 위젯

ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        duration: const Duration(seconds: 5),
        content: const Text('Expense Deleted.'),
        action: SnackBarAction(
          label: 'Undo',
          onPressed: () {
            setState(() {
              _registeredExpenses.insert(expenseIndex, expense);
            });
          },
        ),
      ),
    );

ScaffoldMessenger를 통해 Snackbar 위젯 사용을 관리한다.
위 코드는 undo 버튼을 snackbar를 통해서 짧은시간 노출하고, 사용자가 실수로 지웠을경우나 되돌릴경우,
이 버튼을 눌러 지워졌던 Expense를 그 자리에 똑같이(-> insert O, add X) 다시 출력하는 기능을 담당했다.

ColorScheme

색채 배합을 정해두고 이를 활용할 수 있도록 하는 기능

var kColorScheme = ColorScheme.fromSeed(
  seedColor: const Color.fromARGB(255, 123, 190, 248),
);

var kDarkColorScheme = ColorScheme.fromSeed(
  seedColor: const Color.fromARGB(255, 7, 13, 126),
);

  darkTheme: ThemeData.dark().copyWith(
        useMaterial3: true,
        colorScheme: kDarkColorScheme,
        cardTheme: const CardTheme().copyWith(
          color: kDarkColorScheme.secondaryContainer,
          margin: const EdgeInsets.symmetric(
            horizontal: 16,
            vertical: 8,
          ),
        ),

이렇게 copyWith를 사용하여 색채배합에서 원하는 색을 사용하는 것도 가능하다.
다크모드 지원역시 이 ColorScheme을 이용하면 쉽게 구현이 가능하다.

profile
제 Velog에 오신 모든 분들이 작더라도 인사이트를 얻어가셨으면 좋겠습니다 :)

0개의 댓글