Flutter
Flutter Day 10-2: InheritedWidget를 이용한 도서 관리 앱
@leem
2025. 1. 20. 17:07
Flutter에서 상태를 공유하고 효율적으로 관리하려면 InheritedWidget을 사용하는 방법이 있습니다.
InheritedWidget은 위젯 트리에서 데이터를 공유하고 자식 위젯들에게 알림을 줄 수 있는 강력한 도구입니다.
InheritedWidget을 활용해 도서 관리 앱을 구현한 예제를 중심으로 개념과 코드를 정리해 보겠습니다.
InheritedWidget
InheritedWidget은 위젯 트리에서 데이터를 공유하기 위해 사용됩니다. 부모 위젯에서 상태를 관리하고, 자식 위젯들이 그 상태를 참조하거나 변경할 수 있도록 합니다.
이전의 props drilling(프로퍼티 전달) 문제를 해결하고, 트리가 깊어져도 데이터를 효율적으로 관리할 수 있는 구조를 제공합니다.
특징 | 설명 |
데이터 공유 | 부모 위젯이 관리하는 데이터를 트리 아래의 모든 위젯과 공유 가능. |
성능 최적화 | updateShouldNotify를 통해 데이터 변경 여부를 판단하여 필요할 때만 자식 위젯을 다시 빌드. |
BuildContext 사용 | context.dependOnInheritedWidgetOfExactType<T>()를 통해 데이터를 쉽게 접근. |
Flutter 기본 제공 | 상태 관리 라이브러리 없이도 기본적으로 사용할 수 있는 방식. |
InheritedWidget을 활용한 도서 관리 앱
(1) InheritedParent 클래스 구현
InheritedWidget을 상속받아 도서 목록 상태와 콜백 메서드를 관리합니다.
import 'package:flutter/cupertino.dart';
import '../common.utils/logger.dart';
class InheritedParent extends InheritedWidget {
final List<String> state; // 공유 상태 (선택된 도서 목록)
final void Function(String book) onPressed; // 콜백 메서드
InheritedParent({
required this.state,
required this.onPressed,
required super.child,
});
@override
bool updateShouldNotify(covariant InheritedParent oldWidget) {
logger.d('InheritedParent - updateShouldNotify 호출됨');
if (state.length != oldWidget.state.length) return true;
for (int i = 0; i < state.length; i++) {
if (state[i] != oldWidget.state[i]) return true;
}
return false; // 상태 변경 없음
}
(2) BookListPage (책 목록 화면)
도서 목록을 표시하고, 선택된 도서는 하이라이트됩니다.
import 'package:flutter/material.dart';
import '../common.utils/logger.dart';
import 'inherited_parent.dart';
class BookListPage extends StatelessWidget {
final List<String> books = ['호모사피엔스', '다트입문', '홍길동전', '코드리팩토링', '전치사도감'];
@override
Widget build(BuildContext context) {
InheritedParent? inheritedParent =
context.dependOnInheritedWidgetOfExactType<InheritedParent>();
List<String> selectedBook = inheritedParent?.state ?? [];
return inheritedParent == null
? Center(child: Text('데이터가 없습니다.'))
: ListView(
children: books.map((book) {
final isSelectedBook = selectedBook.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: Colors.black),
),
),
title: Text(book, style: TextStyle(fontWeight: FontWeight.bold)),
trailing: IconButton(
onPressed: () {
inheritedParent?.onPressed(book); // 콜백 호출
},
icon: Icon(
isSelectedBook ? Icons.remove_circle : Icons.add_circle,
color: isSelectedBook ? Colors.red : Colors.green,
),
),
);
}).toList(),
);
}
}
(3) BookCartPage (장바구니 화면)
선택된 도서만 표시합니다.
import 'package:flutter/material.dart';
import 'inherited_parent.dart';
class BookCartPage extends StatelessWidget {
const BookCartPage({super.key});
@override
Widget build(BuildContext context) {
InheritedParent inheritedParent =
context.dependOnInheritedWidgetOfExactType<InheritedParent>()!;
return ListView(
children: inheritedParent.state
.map((book) => ListTile(title: Text(book)))
.toList(),
);
}
}
(4) HomeScreen (상태 관리 및 화면 전환)
상태를 변경하거나 화면을 전환하는 역할을 합니다.
import 'package:flutter/material.dart';
import '../common.utils/logger.dart';
import 'book_list_page.dart';
import 'book_cart_page.dart';
import 'inherited_parent.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int pageIndex = 0;
List<String> mySelectedBook = [];
void _toggleSaveStates(String book) {
setState(() {
if (mySelectedBook.contains(book)) {
mySelectedBook.remove(book);
} else {
mySelectedBook.add(book);
}
});
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text('도서 관리 앱'),
backgroundColor: Theme.of(context).colorScheme.tertiaryContainer,
),
body: InheritedParent(
state: mySelectedBook,
onPressed: _toggleSaveStates,
child: IndexedStack(
index: pageIndex,
children: [
BookListPage(),
BookCartPage(),
],
),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: pageIndex,
onTap: (index) {
setState(() {
pageIndex = index;
});
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.list),
label: '책 목록',
),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart),
label: '장바구니',
),
],
),
),
);
}
}
- InheritedWidget의 사용 방법:
- InheritedWidget을 상속받아 상태와 메서드를 관리.
- updateShouldNotify로 상태 변경 여부 판단.
- 데이터 흐름 최적화:
- 중첩된 위젯 트리에서 상태를 필요로 하는 자식 위젯만 다시 빌드.
- props drilling 방지:
- 부모 위젯에서 자식 위젯으로 콜백과 데이터를 계속 전달하는 번거로움을 제거.