[Flutter]상태 관리 앱 만들기 (nheritedWidget 사용)

2025. 2. 3. 10:20·Flutter/App
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(이벤트 핸들러)를 가져옴.
  • 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
'Flutter/App' 카테고리의 다른 글
  • [Flutter] 블로그 만들기 - 기본 프로젝트 설정
  • [Flutter] 상태 관리 앱 만들기 (라이브러리 사용)
  • [Flutter] 상태 관리 앱 만들기 (StatefulWidget 사용)
  • [Flutter] 상태 관리 앱 만들기 (stl, stf)
공돌이 출신 개발자
공돌이 출신 개발자
공돌이 출신 개발자입니다
  • 공돌이 출신 개발자
    공돌이 출신 개발자
    공돌이 출신 개발자
  • 전체
    오늘
    어제
    • 분류 전체보기 (124)
      • Database (0)
        • SQL (0)
        • 1일 1쿼리 (9)
      • Flutter (40)
        • Dart 언어 (18)
        • App (22)
      • Git (0)
      • Http 기초 지식 (14)
      • HTML5 & CSS3 (0)
      • Java (33)
      • JSP (0)
      • JavaScript (0)
      • Linux (0)
      • MSA (0)
      • Project (0)
      • React (0)
      • Spring (19)
      • 설치 메뉴얼 (1)
      • [Flutter] 프로젝트 (눈길) (8)
        • 작업일지 (8)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • GitHub
  • 공지사항

  • 인기 글

  • 태그

    플러터
    SQLD
    앱 개발
    객체지향
    android studio
    객체
    로그인
    spring boot
    안드로이드 앱 개발
    공부
    안드로이드
    회원가입
    flutter
    앱개발
    1일1쿼리
    SQL
    메서드
    코딩
    클래스
    dart
    개발
    데이터
    프로젝트
    블로그 만들기
    Android
    jsp
    Java
    HTTP
    프로그래밍
    JAVA 기초
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
공돌이 출신 개발자
[Flutter]상태 관리 앱 만들기 (nheritedWidget 사용)
상단으로

티스토리툴바