728x90
3단계: InheritedWidget으로 상태 관리하기
💡 InheritedWidget은 props drilling 문제를 해결하기 위한 Flutter의 기본 상태 관리 방법 중 하나다.
하위 위젯들이 중첩된 경우 상태나 데이터를 전달하기 어려운 상황에서 상위 위젯의 상태를 공유하고, 하위 위젯들이 이를 활용할 수 있도록 돕는다.
하지만 몇 가지 단점이 존재해 더 나은 상태 관리 라이브러리인 Provider나 Riverpod을 많이 사용하는데, 이에 대해서는 다음 포스팅에 언급하겠다.
- Inherited_Parent
// InheritedWidget 내장 데이터 타입을 상속받아 구현한다.
import 'package:flutter/cupertino.dart';
import '../common.utils/logger.dart';
class InheritedParent extends InheritedWidget {
// 공유 상태 데이터 관리에 목적
// List<String> mySelectedBooks = [];
List<String> state; // 카드에 담긴 책 목록(공유 상태)
// 부모에게 콜백 메서드를 관리하는 목적
void Function(String book) onPressed; // 이벤트 핸들러
// HOW <-- 사용방법
// WHY <---
InheritedParent({
required this.state,
required this.onPressed,
required super.child,
});
// 상태가 변경되었는지 여부를 판단하는 메서드
// 주의점 ! InheritedWidget --> 재정의 클래스를 InheritedParent 넣어 주자.
// 메서드안 InheritedParent 타입 확인
@override
bool updateShouldNotify(covariant InheritedParent oldWidget) {
logger.d('InheritedParent - updateShouldNotify() 호출 확인 ');
// 상태가 달라졌다면 어떻게 판별할까?
if (state.length != oldWidget.state.length) {
logger.d('상태 변경 됨 ');
return true;
}
for (int i = 0; i < state.length; i++) {
// state --> String --> '호모사피엔스'
// state[i] --> String ---> 호모사피엔스
if (state[i] != oldWidget.state[i]) {
return true;
}
}
// 상태 변경이 없으면 false 를 반환하여 자식 위젯을 다시 빌드 않되도록 한다.
return false; // 상태 변경 없음
}
}
- InheritedWidget의 역할
- 상위 위젯의 데이터를 하위 위젯에서 쉽게 접근할 수 있도록 돕는다.
- props drilling 문제(데이터를 여러 계층의 위젯에 계속 전달하는 비효율)를 해결한다.
- 핵심 필드
- state: 공유 상태 데이터를 관리하는 리스트 (List<String>).
- onPressed: 상태를 변경하는 부모의 콜백 메서드.
- updateShouldNotify 메서드
- 목적: 상태가 변경되었는지 판단.
- 작동 방식:
- state 리스트의 길이 또는 각 요소를 비교하여 변경 여부 확인.
- 변경이 발생하면 true 반환 → 자식 위젯 재빌드.
- 변경이 없으면 false 반환 → 불필요한 재빌드 방지.
- 사용법
- InheritedParent를 상위 위젯으로 감싸고, 공유하려는 상태와 메서드를 전달.
- context.dependOnInheritedWidgetOfExactType<InheritedParent>()를 통해 하위 위젯에서 상태 접근.
- home_screen
import 'package:flutter/material.dart';
import 'package:flutter_statement/_03/inherited_parent.dart';
import '../common.utils/logger.dart';
import 'book_cart_page.dart';
import 'book_list_page.dart';
class HomeScreen extends StatefulWidget {
HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
//로컬상태
int pageIndex = 0;
// 공유상태 => 여러 위젯에서 관리할 수 있음 => 카트에 담긴 책 정보( 책 리스트 화면, 장바구니 화면 공유 데이터)
// 상품 -> 책 (String) 타입으로 관리하자
//객체 배열로 관리할 수 있다.
List<String> mySelectedBook = [];
//상태를 변경하는 메서드 만들기
void _toggleSaveStates(String book) {
//다시 화면을 그려라 => 요청함수
setState(() {
if (mySelectedBook.contains(book)) {
mySelectedBook.remove(book);
} else {
mySelectedBook.add(book);
}
});
}
@override
void initState() {
// TODO: implement initState
super.initState();
setState(() {
pageIndex = 0;
});
}
void _changePageIndex(int index) {
setState(() {
pageIndex = index;
});
}
@override
Widget build(BuildContext context) {
logger.d('HomeScreen build 메서드 호출됨 ');
return SafeArea(
child: Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () {
_changePageIndex(0);
},
icon: Icon(
Icons.arrow_back_ios,
color: Colors.white,
),
),
title: Text(
'텐코의 서재',
style: TextStyle(color: Colors.white),
),
actions: [
IconButton(
onPressed: () {
_changePageIndex(1);
},
icon: Icon(Icons.shopping_cart),
color: Colors.white,
)
],
backgroundColor: Theme.of(context).colorScheme.onSurfaceVariant,
),
body: InheritedParent(
state: mySelectedBook,
onPressed: _toggleSaveStates,
//super.child
child: IndexedStack(
index: pageIndex,
children: [
BookListPage(),
BookCartPage(),
],
),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: pageIndex,
onTap: (index) {
return _changePageIndex(index);
},
items: [
BottomNavigationBarItem(icon: Icon(Icons.list), label: 'book-list'),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart), label: 'book-cart'),
],
),
));
}
}
- 로컬 상태 관리
- pageIndex: 현재 페이지 인덱스 (책 목록 또는 장바구니).
- mySelectedBook: 선택된 책 목록을 관리하는 리스트.
- 상태 변경 메서드
- _toggleSaveStates(String book): 책을 선택하거나 제거하여 상태를 업데이트.
- _changePageIndex(int index): 페이지 전환 (책 목록 <-> 장바구니).
- 구조
- AppBar: 뒤로가기 버튼과 장바구니 버튼을 통해 페이지 전환.
- InheritedParent: state(책 목록)와 onPressed(이벤트 핸들러)를 자식 위젯에 전달.
- IndexedStack: BookListPage와 BookCartPage를 상태에 따라 렌더링.
- 공유 상태 전달
- InheritedParent를 활용하여 BookListPage와 BookCartPage에 state와 onPressed를 전달해 데이터 공유.
- book_list_page
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../common.utils/logger.dart';
import 'inherited_parent.dart';
class BookListPage extends StatelessWidget {
BookListPage({super.key});
//임시데이터 - 교보문고에 볼 수 있는 책 목록 리스트
final List<String> books = [
'호모사피엔스',
'dart입문',
'홍길동전',
'code Refactoring',
'전치사도감',
];
@override
Widget build(BuildContext context) {
//buildcontext를 사용하여 inheritedParentWidget에 접근할 수 있음
InheritedParent? inheritedParent =
context.dependOnInheritedWidgetOfExactType();
List<String> mySelectedBooked = inheritedParent?.state ?? [];
logger.d('데이터 확인 : ${inheritedParent?.state.toString()}');
return inheritedParent == null
? const Center(
child: Text('데이터가 없습니다.'),
)
: SafeArea(
child: Scaffold(
body: ListView(
children: books.map(
(book) {
//함수의 body에는 식을 작성할 수 있다
final isSelecedBook = mySelectedBooked!.contains(book);
//부모가 관리하는 장바구니 데이터에 있는지 없는지
return ListTile(
leading: Container(
width: 35,
height: 35,
decoration: BoxDecoration(
color: Theme.of(context).secondaryHeaderColor,
borderRadius: BorderRadius.circular(8.0),
border:
Border.all(color: Theme.of(context).primaryColor),
),
),
title: Text(
book,
style: TextStyle(
fontSize: 12, fontWeight: FontWeight.bold),
),
trailing: IconButton(
onPressed: () {
inheritedParent.onPressed(book);
},
icon: isSelecedBook
? Icon(Icons.remove_circle)
: Icon(Icons.add_circle),
color: isSelecedBook ? Colors.red : Colors.green,
),
);
},
).toList(),
),
),
);
}
}
- 책 목록 데이터
- books: 화면에 표시할 임시 책 목록 리스트.
- InheritedParent 데이터 접근
- context.dependOnInheritedWidgetOfExactType<InheritedParent>()를 사용해
InheritedParent의 state(선택된 책 목록)와 onPressed(이벤트 핸들러)를 가져옴.
- context.dependOnInheritedWidgetOfExactType<InheritedParent>()를 사용해
- UI 구성
- ListView: 책 목록을 리스트로 출력.
- ListTile:
- leading: 간단한 장식 아이콘.
- title: 책 제목.
- trailing: 상태에 따라 추가(녹색) 또는 제거(빨간색) 아이콘 표시.
- 상태 확인 및 아이콘 표시
- isSelectedBook: 현재 책이 선택된 상태인지 확인.
- 아이콘 버튼 클릭 시 InheritedParent.onPressed 호출로 상태 변경.
- book_cart_page
import 'package:flutter/material.dart';
import 'package:flutter_statement/_03/inherited_parent.dart';
class BookCartPage extends StatelessWidget {
const BookCartPage({super.key});
@override
Widget build(BuildContext context) {
InheritedParent inheritedParent = context
.dependOnInheritedWidgetOfExactType<InheritedParent>()!; //null assert
List<String> mySelectedBooked = inheritedParent.state;
return ListView(
children: mySelectedBooked.map((cart) {
return ListTile(
leading: Container(
width: 35,
height: 35,
decoration: BoxDecoration(
color: Theme.of(context).secondaryHeaderColor,
borderRadius: BorderRadius.circular(8.0),
border: Border.all(color: Theme.of(context).primaryColor),
),
),
title: Text(
cart,
style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
),
trailing: IconButton(
onPressed: () {
inheritedParent.onPressed(cart);
},
icon: Icon(
Icons.remove_circle,
color: Theme.of(context).primaryColor,
)),
);
}).toList());
}
}
- InheritedParent에서 데이터 가져오기
- context.dependOnInheritedWidgetOfExactType<InheritedParent>()를 사용해 상위 InheritedParent에서 state(선택된 책 목록)와 onPressed(삭제 기능)를 가져옴.
- UI 구성
- ListView: 선택된 책 목록을 리스트로 출력.
- ListTile:
- leading: 선택된 책을 표시하는 아이콘.
- title: 선택된 책의 제목.
- trailing: 제거 버튼 (빨간색 remove_circle 아이콘).
- 책 삭제 기능
- onPressed: 버튼 클릭 시 InheritedParent.onPressed(cart) 호출 → 선택 목록에서 제거.
이번 포스팅에서는 InheritedWidget을 활용한 상태 관리 방법을 알아봤다.
상위 위젯에서 데이터를 하위 위젯으로 전달하고 공유할 수 있도록 돕는 유용한 기능이지만,
모든 하위 위젯이 리빌드되는 단점이 있어 Provider나 Riverpod 같은 라이브러리가 더 자주 사용된다.
다음 포스팅에서는 라이브러리를 활용한 상태 관리를 다룰 예정이니 기대해도 좋다! 🚀
궁금한 점이나 추가 질문이 있다면 댓글로 남겨주세요! 😊
이전 단계가 궁금하다면?
2025.02.03 - [Flutter/App] - [Flutter] 상태 관리 앱 만들기 (StatefulWidget 사용)
[Flutter] 상태 관리 앱 만들기 (stl, stf)
💡 우리가 배울 여러 상태 관리 방법을 이용해 데이터 일관성을 유지 해보자!StatefulWidgetInheritedWidgetProvider(라이브러리)Riverpod(라이브러리)1단계: 기본 위젯 (StatelessWidget & StatefulWidget) 💡 Stateless
seohong.tistory.com
'Flutter > App' 카테고리의 다른 글
[Flutter] 블로그 만들기 - 기본 프로젝트 설정 (1) | 2025.02.08 |
---|---|
[Flutter] 상태 관리 앱 만들기 (라이브러리 사용) (0) | 2025.02.05 |
[Flutter] 상태 관리 앱 만들기 (StatefulWidget 사용) (1) | 2025.02.03 |
[Flutter] 상태 관리 앱 만들기 (stl, stf) (1) | 2025.02.03 |
[Flutter] 연습하기 4 - Flutter Shopping Cart app (1) | 2025.01.14 |