[TIL] Day 22 Align & Positioned & State management & 과제 트러블 슈팅💥

현서·2025년 12월 23일

[TIL] Flutter 9기

목록 보기
34/102
post-thumbnail

튜터님과 Widget 공부

Align

Align이란?

자식 위젯을 부모 영역 안에서 정렬하는 위젯
Stack 없이도 위치 조절 가능
주 용도: 정렬 + 위치 미세조정

기본 구조

Align(
  alignment: Alignment.center,
  child: Widget,
)

alignment 속성

1️⃣ 미리 정의된 정렬

Alignment.center
Alignment.topLeft
Alignment.topCenter
Alignment.topRight
Alignment.centerLeft
Alignment.centerRight
Alignment.bottomLeft
Alignment.bottomCenter
Alignment.bottomRight

2️⃣ Alignment(x, y) 좌표 개념

Alignment(x, y)

범위: -1.0 ~ 1.0
기준: 부모의 중심

Positioned

Positioned란?

Stack 안에서만 사용 가능 (❗ Stack의 자식으로만)
자식 위젯을 절대 좌표로 배치
화면·부모 기준으로 정확한 위치 지정

기본 구조

Stack(
  children: [
    Positioned(
      top: 0,
      left: 0,
      child: Widget,
    ),
  ],
)

Positioned 속성

위치 속성

속성기준
top부모(Stack) 위
bottom부모 아래
left부모 왼쪽
right부모 오른쪽

Positioned.fill

Stack 전체를 채움
Positioned.fill(
child: Container(color: Colors.black),
)

Positioned vs Align

PositionedAlign
절대 위치상대 위치
Stack 필수Stack 불필요
픽셀 단위비율·좌표
반응형 약함반응형 강함

➡ 정확한 위치 → Positioned
➡ 유동 위치 → Align


📝 상태 관리

✏️ 상태란?

UI를 결정하는 값
값이 바뀌면 화면이 바뀜

카운트 숫자, 체크 여부, 입력한 텍스트, 로그인 여부, 서버 데이터

✏️ setState란?

상태가 바뀌었음을 Flutter에 알리는 트리거
StatefulWidget 전용

setState(() {
  count++;
});

상태 변경
해당 State가 관리하는 위젯 트리 리빌드

✏️ setState의 한계

상태 공유 불가

setState는 해당 StatefulWidget 안에서만 유효
문제: 다른 위젯이 같은 상태를 써야 하면 전달해야 함
위젯 깊어질수록 props 지옥

Parent(
  value: value,
  child: Child(
    value: value,
    child: GrandChild(value: value),
  ),
);

상태 변경 흐름 추적 불가

어디서 setState가 호출됐는지 한눈에 안 보임
버튼, 콜백, async 완료 시점마다 흩어짐

전역 상태 관리 불가

로그인 상태, 테마, 사용자 정보
이걸 setState로 하면:
최상단 위젯에서 관리
모든 하위 위젯 리빌드
→ 성능 + 구조 둘 다 망함

✏️ State Management

State Management란?

상태를 한 곳에서 관리하고
여러 UI에 안전하게 전달하는 구조

왜 필요한가

setState만 쓰면 한계 존재

상태 공유 불가
UI + 로직 뒤섞임
변경 흐름 추적 어려움
앱 커질수록 유지보수 붕괴
→ 상태를 한 곳에서 관리해야 함

State Management의 기본 구조

[UI]  →  사용자 액션
 ↓
[State Notifier / Controller][State 변경][UI 자동 리빌드]

핵심:
UI는 상태를 읽기만
변경은 전용 로직에서

핵심 개념 정리

  • 상태 저장소 (Store)
    상태를 들고 있는 곳

  • 상태 변경자
    상태를 바꾸는 메서드

  • 구독 (Listen / Watch)
    상태 변경 시 UI 업데이트

  • 단방향 데이터 흐름
    UI → Action → State → UI


flutter 기초 개인과제 To Do App 트러블 슈팅💥

1️⃣ 불변 객체(Immutable Object)와 final 필드 수정 오류

문제 상황

ToDo 앱에서 할 일 추가는 정상 동작 되었지만
할 일 완료 체크(isDone) 또는 즐겨찾기(isFavorite) 기능을 구현하던 중, 상태 변경 로직에서 컴파일 에러가 발생

'isDone' can't be used as a setter because it's final.

원인 분석

ToDoEntity

class ToDoEntity {
  ToDoEntity({
    required this.title,
    this.description,
    this.isFavorite = false,
    this.isDone = false,
  });

  final String title;
  final String? description;
  final bool isFavorite;
  final bool isDone;
}

ToDoEntity 클래스의 모든 필드를 final로 선언하여 불변 객체로 설계했으나 이를 가변 객체처럼 수정하려고 시도한 것이 원인

설계 코드: final bool isDone; (생성 후 값 변경 불가)
잘못된 접근: todo.isDone = !todo.isDone; (재할당 시도)

해결 방법

ToDoEntity는 불변 객체 유지하고 객체 수정이 아닌 새로운 객체로 교체하는 방식 적용

1. copyWith 메서드

ToDoEntity copyWith({
  String? title,
  String? description,
  bool? isFavorite,
  bool? isDone,
}) {
  return ToDoEntity(
    title: title ?? this.title,
    description: description ?? this.description,
    isFavorite: isFavorite ?? this.isFavorite,
    isDone: isDone ?? this.isDone,
  );
}

copyWith 메서드 구현 기존 객체의 값은 유지하, 변경이 필요한 필드만 바꾸어 새로운 인스턴스를 반환하는 메서드를 추가

2. HomePage에 상태 변경 함수 작성

void toggleDone(int index) {
  setState(() {
    todoList[index] =
        todoList[index].copyWith(isDone: !todoList[index].isDone);
  });
}

기존 객체 수정 ✘
새 객체로 교체 ✔
리스트 자체를 변경해야 UI가 갱신됨

정리

final 에러는 단순 문법 오류가 아니라, 불변성(Immutability)을 지키라는 설계의 신호였음
Flutter에서는 상태 관리를 위해 객체를 직접 수정하기보다 새로운 상태로 대체하는 것이 일반적임을 이해함

2️⃣ 콜백 함수 타입 불일치 및 Void 반환 오류

문제 상황

부모 위젯(HomePage)의 함수를 자식 위젯(TodoView)으로 완료(isDone) 즐겨찾기(isFavorite) 전달하는 과정에서 에러 발생

  • 타입 불일치: VoidCallback은 매개변수가 없는데, int index가 필요한 함수를 넣으려 함.
  • void 반환값 사용: 함수 자체를 넘겨야 하는데, 함수를 실행해버려서 void 값을 변수에 할당하려 함.
This expression has a type of 'void' so its value can't be used.

원인 분석

    1. 잘못된 타입 정의
      부모 함수: void toggleDone(int index)void Function(int) 타입
      자식 정의: final VoidCallback onToggleDone;void Function() 타입 (인자 없음)
    1. 함수의 직접 실행 (Immediate Execution)
      잘못된 코드: onToggleFavorite: onToggleFavorite(index)
      ()를 붙이면 코드가 읽히는 시점에 함수가 즉시 실행되며, 그 결과인 void를 onToggleFavorite에 넣으려 하여 에러가 발생

매개변수 없음, index 전달 불가
부모는 int index를 요구하는 함수 전달
자식은 인자 없는 함수로 받음
함수 호출 시 타입이 맞지 않아 오류 발생

해결 방법

1. TodoView의 콜백 타입 수정

class TodoView extends StatelessWidget {
  final void Function(int) onToggleFavorite;
  final void Function(int) onToggleDone;
}

2. index를 포함해 ToDoWidget으로 전달

itemBuilder: (context, index) {
  return ToDoWidget(
    todo: todoList[index],
    onToggleFavorite: () => onToggleFavorite(index),
    onToggleDone: () => onToggleDone(index),
  );
},

함수를 즉시 실행하지 않고, 클릭 시점에 실행되도록 익명 함수로 감싸서 전달
onToggleFavorite: () => onToggleFavorite(index),
의미: "TodoDetailPage에서 onToggleFavorite가 호출되면, 부모의 onToggleFavorite(index)를 실행하라"

정리

타입 불일치 문제 해결하여 토글 버튼 정상 동작
괄호 없이 함수 참조만 전달하면 즉시 호출되지 않음
람다로 감싸면 매개변수 전달 가능

구분코드 형태의미비유반환값 (결과)
함수 호출func()야, 지금 당장 실행해!요리사에게 지금 요리를 만들어 내놔라고 명령함요리 결과물 (Return Value) 또는 void
함수 참조func이거 설명서(주소)야. 필요할 때 써요리사에게 레시피(조리법)만 전달함함수 그 자체 (Function Type)

3️⃣ 입력 검증 UX 개선 (SnackBar vs 버튼 비활성화)

문제 상황

showModalBottomSheet에서 할 일을 입력할 때 빈 값을 막기 위해 SnackBar를 띄우려 했으나 문제 발생

  1. 키보드 가림: 키보드가 올라오면 하단의 SnackBar가 가려짐
  2. 레이어 이슈: SnackBar가 BottomSheet 뒤(Scaffold)에 그려져 보이지 않음
  3. 복잡성: 위치 보정을 위한 코드 비용이 단순 검증 기능에 비해 너무 큼

원인 분석

SnackBar는 가까운 Scaffold에 메세지를 띄워줘서 레이어에 가려지고 BottomSheet를 Scaffold로 감싸면 height 값을 직접 지정해줘야해서
기기별 대응이 안 좋다고 판단함
SnackBar는 작업 완료 결과를 알리는 데 적합하며 모달 창 내부의 실시간 입력 제어 (키보드가 내려가지 않는 상황)에는 부적합하다고 생각

해결 방법

TextEditingController의 리스너를 활용해 입력값이 비어있을 경우 저장 버튼 자체를 비활성화하는 방식으로 변경

1. 상태 감지 리스너 등록

initState에서 컨트롤러에 리스너를 달아서 텍스트가 변경될 때마다 isTitleEmpty 상태를 갱신하도록 함


void initState() {
  super.initState();
  titleController.addListener(() {
    setState(() {
      isTitleEmpty = titleController.text.trim().isEmpty;
    });
  });
}

2. TextButton의 비활성화 상태 활용

TextButton은 onPressed 속성이 null일 경우 자동으로 비활성화 스타일(회색 텍스트, 클릭 불가)이 적용되는 점을 활용

TextButton(
  onPressed: isTitleEmpty ? null : onSubmit,
  child: Text("저장"),
),

정리

사용자 입력 값이 입력이 되어있지 않을 때 어떤 ux를 보여주는 것이 사용자 경험에 좋을지 여러 방면으로 생각해보는 기회였음
여러 todo 어플을 참고해보며 주로 사용하는 방법을 참고해서 문제 해결에 적용

0개의 댓글