[Flutter] 탭 이동해도 상태 유지하기 | AutomaticKeepAliveClientMixin & PageView

수정 전 수정 후
  • 수정 전의 앱은 각각의 탭에 들어갈 때마다 로딩 스피너가 돌아가면서 API 콜을 해서 데이터를 새로 불러왔다.
  • 하지만 교정일기 앱은 사용자가 데이터를 추가하는 경우가 아니라면 업데이트할 필요가 전혀 없다.
  • 따라서 불필요한 네트워크 통신을 줄이고, 더 빠른 사용자 경험을 제공하기 위해 최초 1회만 데이터를 불러오고, 그 이후에는 탭을 이동해도 기존에 불러온 데이터를 유지해서 보여주고 싶었다.

 

위젯의 상태 유지하기 AutomaticKeepAliveClientMixin

  • 플러터에서는 기본적으로 탭을 이동하면 보이지 않는 탭의 상태가 `dispose` 되어 메모리에서 제거된다.
  • 하지만 ` AutomaticKeepAliveClientMixin`을 사용하면 탭을 이동하더라도 위젯이 보존되며 상태를 유지할 수 있다.
  • 이를 통해 불필요한 네트워크 요청이나 위젯 리빌드를 피할 수 있고, 탭을 이동해도 이전 탭의 스크롤 위치를 유지하고 싶은 경우에도 유용하다.

 

AutomaticKeepAliveClientMixin 적용 코드

class TimelineTab extends StatefulWidget {
  const TimelineTab({super.key});

  @override
  State<TimelineTab> createState() => _TimelineTabState();
}

// ✅ mixin 추가
class _TimelineTabState extends State<TimelineTab> with AutomaticKeepAliveClientMixin {
  // ✅ 추가
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context); // ✅ 추가
    return const Scaffold();
  }
}
  • `StatefulWidget` 클래스에 `AutomaticKeepAliveClientMixin`를 추가한다.
  • `wantKeepAlive`를 `true`로 설정한다.
  • `build` 메서드 안에서 `super.build(context)`를 호출한다.

만약 `AutomaticKeepAliveClientMixin`을 추가했는데도 여전히 탭을 이동할 때마다 상태가 초기화된다면 한 가지를 더 수정해야 한다.

 

BottomNavigaionBar에 PageView 적용하기

  • `PageView`와 `AutomaticKeepAliveClientMixin`를 함께 사용하면 현재 보이지 않는 탭도 위젯 트리에서 활성 상태를 유지할 수 있다.
  • `PageView`뿐만 아니라 `TabBarView`를 사용할 수도 있다. `TabBarView`를 사용하면 기본적으로 탭을 이동할 때 옆으로 스와이프하는 듯한 애니메이션 효과가 적용된다.

 

PageView  적용 코드

class _IndexScreenState extends State<IndexScreen> {
  late PageController _pageController;
  int _currentIndex = 0;

  final List<Widget> _tabs = [
    const HomeTab(),
    const TimelineTab(),
    const AlbumTab(),
    const ProfileTab(),
  ];

  void _onItemTapped(int index) {
    _pageController.jumpToPage(index);
    setState(() {
      _currentIndex = index; 
    });
  }

  @override
  void initState() {
    super.initState();
    _pageController = PageController(initialPage: _currentIndex);
  }

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        physics: const NeverScrollableScrollPhysics(), // 스와이프로 탭 이동 불가
        controller: _pageController,
        onPageChanged: _onItemTapped,
        children: _tabs,
      ),
      bottomNavigationBar: BottomNavigationBar(
        type: BottomNavigationBarType.fixed,
        currentIndex: _currentIndex, 
        onTap: _onItemTapped,
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '홈',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.list),
            label: '타임라인',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.image_outlined),
            label: '앨범',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: 'MY',
          ),
        ],
      ),
    );
  }
}
  • `PageController`를 생성하고 `body`에 `PageView`를 적용한다.

이제 탭을 이동해도 해당 탭의 위젯을 다시 빌드하지 않고, 상태도 유지되는 것을 확인할 수 있다.

 

참고 자료