
자식 위젯을 부모 영역 안에서 정렬하는 위젯
Stack 없이도 위치 조절 가능
주 용도: 정렬 + 위치 미세조정
Align(
alignment: Alignment.center,
child: Widget,
)
Alignment.center
Alignment.topLeft
Alignment.topCenter
Alignment.topRight
Alignment.centerLeft
Alignment.centerRight
Alignment.bottomLeft
Alignment.bottomCenter
Alignment.bottomRight
Alignment(x, y)
범위: -1.0 ~ 1.0
기준: 부모의 중심
Stack 안에서만 사용 가능 (❗ Stack의 자식으로만)
자식 위젯을 절대 좌표로 배치
화면·부모 기준으로 정확한 위치 지정
Stack(
children: [
Positioned(
top: 0,
left: 0,
child: Widget,
),
],
)
| 속성 | 기준 |
|---|---|
| top | 부모(Stack) 위 |
| bottom | 부모 아래 |
| left | 부모 왼쪽 |
| right | 부모 오른쪽 |
Stack 전체를 채움
Positioned.fill(
child: Container(color: Colors.black),
)
| Positioned | Align |
|---|---|
| 절대 위치 | 상대 위치 |
| Stack 필수 | Stack 불필요 |
| 픽셀 단위 | 비율·좌표 |
| 반응형 약함 | 반응형 강함 |
➡ 정확한 위치 → Positioned
➡ 유동 위치 → Align

UI를 결정하는 값
값이 바뀌면 화면이 바뀜
카운트 숫자, 체크 여부, 입력한 텍스트, 로그인 여부, 서버 데이터
상태가 바뀌었음을 Flutter에 알리는 트리거
StatefulWidget 전용
setState(() {
count++;
});
상태 변경
해당 State가 관리하는 위젯 트리 리빌드
setState는 해당 StatefulWidget 안에서만 유효
문제: 다른 위젯이 같은 상태를 써야 하면 전달해야 함
위젯 깊어질수록 props 지옥
Parent(
value: value,
child: Child(
value: value,
child: GrandChild(value: value),
),
);
어디서 setState가 호출됐는지 한눈에 안 보임
버튼, 콜백, async 완료 시점마다 흩어짐
로그인 상태, 테마, 사용자 정보
이걸 setState로 하면:
최상단 위젯에서 관리
모든 하위 위젯 리빌드
→ 성능 + 구조 둘 다 망함
상태를 한 곳에서 관리하고
여러 UI에 안전하게 전달하는 구조
setState만 쓰면 한계 존재
상태 공유 불가
UI + 로직 뒤섞임
변경 흐름 추적 어려움
앱 커질수록 유지보수 붕괴
→ 상태를 한 곳에서 관리해야 함
[UI] → 사용자 액션
↓
[State Notifier / Controller]
↓
[State 변경]
↓
[UI 자동 리빌드]
핵심:
UI는 상태를 읽기만
변경은 전용 로직에서
상태 저장소 (Store)
상태를 들고 있는 곳
상태 변경자
상태를 바꾸는 메서드
구독 (Listen / Watch)
상태 변경 시 UI 업데이트
단방향 데이터 흐름
UI → Action → State → UI
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는 불변 객체 유지하고 객체 수정이 아닌 새로운 객체로 교체하는 방식 적용
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 메서드 구현 기존 객체의 값은 유지하, 변경이 필요한 필드만 바꾸어 새로운 인스턴스를 반환하는 메서드를 추가
void toggleDone(int index) {
setState(() {
todoList[index] =
todoList[index].copyWith(isDone: !todoList[index].isDone);
});
}
기존 객체 수정 ✘
새 객체로 교체 ✔
리스트 자체를 변경해야 UI가 갱신됨
final 에러는 단순 문법 오류가 아니라, 불변성(Immutability)을 지키라는 설계의 신호였음
Flutter에서는 상태 관리를 위해 객체를 직접 수정하기보다 새로운 상태로 대체하는 것이 일반적임을 이해함
부모 위젯(HomePage)의 함수를 자식 위젯(TodoView)으로 완료(isDone) 즐겨찾기(isFavorite) 전달하는 과정에서 에러 발생
This expression has a type of 'void' so its value can't be used.
void toggleDone(int index) → void Function(int) 타입final VoidCallback onToggleDone; → void Function() 타입 (인자 없음)onToggleFavorite: onToggleFavorite(index)매개변수 없음, index 전달 불가
부모는 int index를 요구하는 함수 전달
자식은 인자 없는 함수로 받음
함수 호출 시 타입이 맞지 않아 오류 발생
class TodoView extends StatelessWidget {
final void Function(int) onToggleFavorite;
final void Function(int) onToggleDone;
}
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) |
showModalBottomSheet에서 할 일을 입력할 때 빈 값을 막기 위해 SnackBar를 띄우려 했으나 문제 발생
SnackBar는 가까운 Scaffold에 메세지를 띄워줘서 레이어에 가려지고 BottomSheet를 Scaffold로 감싸면 height 값을 직접 지정해줘야해서
기기별 대응이 안 좋다고 판단함
SnackBar는 작업 완료 결과를 알리는 데 적합하며 모달 창 내부의 실시간 입력 제어 (키보드가 내려가지 않는 상황)에는 부적합하다고 생각
TextEditingController의 리스너를 활용해 입력값이 비어있을 경우 저장 버튼 자체를 비활성화하는 방식으로 변경
initState에서 컨트롤러에 리스너를 달아서 텍스트가 변경될 때마다 isTitleEmpty 상태를 갱신하도록 함
void initState() {
super.initState();
titleController.addListener(() {
setState(() {
isTitleEmpty = titleController.text.trim().isEmpty;
});
});
}
TextButton은 onPressed 속성이 null일 경우 자동으로 비활성화 스타일(회색 텍스트, 클릭 불가)이 적용되는 점을 활용
TextButton(
onPressed: isTitleEmpty ? null : onSubmit,
child: Text("저장"),
),
사용자 입력 값이 입력이 되어있지 않을 때 어떤 ux를 보여주는 것이 사용자 경험에 좋을지 여러 방면으로 생각해보는 기회였음
여러 todo 어플을 참고해보며 주로 사용하는 방법을 참고해서 문제 해결에 적용