[BookBox] 패키지 구조와 로딩 알고리즘

HootJem's avatar
Dec 04, 2024
[BookBox] 패키지 구조와 로딩 알고리즘
 
BottomNavi 는 알 고 있었는데 상단과 하단에 동시에 버튼이 존재한다면 어떻게 처리해야하는지 굉장한 혼란에 빠졌다. TopNavi 라는 검색도 해보고 여러가지 찾아보다가 생각해 보니 프로필 앱을 만들면서 탭바 라는 기능을 사용한 적이 있는 것이다.
이를 활용하여 아래처럼 페이지를 구성하였다.
 
notion image
일단 바텀 네비게이션바의 홈은 탭바 를 갖고 있고, 각 탭바는 탭바 뷰를 갖고 있다.
이때 알아야 되는건, 탭바 뷰 는 Page 가 아니고 컴포넌트라고 생각해야 한다. 탭바가 움직일 때 모든 페이지가 랜더링 되는 것이 아니라 오렌지색인 내서재 에 해당하는 곳이 랜더링 되는 것이기 때문이다.
 

1. 기본 코드

class MainPage extends StatefulWidget { const MainPage({super.key}); @override State<MainPage> createState() => _MainPageState(); } class _MainPageState extends State<MainPage> with SingleTickerProviderStateMixin { late TabController controller; //탭의 컨트롤러 int _currentTab = 0; // 바텀네비바의 인덱스 // 각 탭의 제목 리스트 final List<String> titles = ['홈', '내서재', '검색', '설정']; @override void initState() { super.initState(); controller = TabController(length: 4, vsync: this); // 탭바를 정의한다. } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( // AppBar의 title을 현재 선택된 탭의 제목으로 설정 appBar: CustomAppBar( titleText: titles[_currentTab], ), body: IndexedStack( index: _currentTab, children: [ // 하위 항목은 모두 bottomNavigationBar 의 인덱스 페이지 HomePage(), // 탭바를 갖고 있다. LibraryPage(), SearchPage(), SettingPage(), ], ), bottomNavigationBar: _bottomNavigation()); } }
_bottomNavigation 위젯 코드
BottomNavigationBar _bottomNavigation() { return BottomNavigationBar( selectedItemColor: Colors.blue, unselectedItemColor: Colors.black38, selectedFontSize: 10, unselectedFontSize: 10, type: BottomNavigationBarType.fixed, onTap: (index) { setState(() { _currentTab = index; }); }, //현재 선택된 index 지정 currentIndex: _currentTab, items: [ const BottomNavigationBarItem( label: '홈', icon: Icon(CupertinoIcons.house), ), const BottomNavigationBarItem( label: '내서재', icon: Icon(CupertinoIcons.book), ), const BottomNavigationBarItem( label: '검색', icon: Icon(CupertinoIcons.search), ), const BottomNavigationBarItem( label: '설정', icon: Icon(CupertinoIcons.settings), ), ], ); }
 
이때 큰 문제가 발생한다.
제일 처음 호출되는 MainPage 는 HomePage(), LibraryPage(), SearchPage(), SettingPage(), 를 갖고 있기 때문에 한번에 모든 페이지를 가져오려고 한다. (로딩이 100년 걸리게 된다.) 그렇기 때문에 필요한 페이지만 가져오는 알고리즘을 추가해야 한다.
 

2. 로딩 최적화

var loadPages = [0]; 를 추가한다. loadPages 는 현재 로딩 되어 진 페이지를 저장하는 곳이다. 기본적으로 home 을 갖고있게 되는데 (0번) 얘는 로딩시 처음 뜨는 곳이라 바로 넣어두었다.
 
children: [ loadPages.contains(0) ? const HomePage() : Container(), loadPages.contains(1) ? const LibraryPage() : Container(), loadPages.contains(2) ? const SearchPage() : Container(), loadPages.contains(3) ? const SettingPage() : Container(), ],
배열에 해당 인덱스가 들어있다면 페이지를 랜더링 하고, 아닐 시 빈 Container 를 만든다.
이후 탭을 눌렀을 때 배열에 인덱스를 추가하여 랜더링 할 수 있도록 하고, const 를 사용하여 이미 랜더링 되었던 화면은 다시 그리지 않도록 하였다.
 
notion image
 

전체코드

import 'package:bookbox/ui/components/custom_app_bar.dart'; import 'package:bookbox/ui/main/home/home_page.dart'; import 'package:bookbox/ui/main/library/library_page.dart'; import 'package:bookbox/ui/main/search/search_page.dart'; import 'package:bookbox/ui/main/setting/setting_page.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class MainPage extends StatefulWidget { const MainPage({super.key}); @override State<MainPage> createState() => _MainPageState(); } class _MainPageState extends State<MainPage> with SingleTickerProviderStateMixin { late TabController controller; int _currentTab = 0; var loadPages = [0]; //저장되는 곳 // 각 탭의 제목 리스트 final List<String> titles = ['홈', '내서재', '검색', '설정']; @override void initState() { super.initState(); controller = TabController(length: 4, vsync: this); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( // AppBar의 title을 현재 선택된 탭의 제목으로 설정 appBar: CustomAppBar( titleText: titles[_currentTab], ), //titleText: titles[index], body: IndexedStack( index: _currentTab, children: [ loadPages.contains(0) ? const HomePage() : Container(), loadPages.contains(1) ? const LibraryPage() : Container(), loadPages.contains(2) ? const SearchPage() : Container(), loadPages.contains(3) ? const SettingPage() : Container(), ], ), bottomNavigationBar: _bottomNavigation()); } BottomNavigationBar _bottomNavigation() { return BottomNavigationBar( selectedItemColor: Colors.blue, unselectedItemColor: Colors.black38, selectedFontSize: 10, unselectedFontSize: 10, type: BottomNavigationBarType.fixed, currentIndex: _currentTab, onTap: (index) { var pages = loadPages; if (!pages.contains(index)) { pages.add(index); print(pages); } _currentTab = index; loadPages = pages; setState(() {}); }, //현재 선택된 index 지정 items: [ const BottomNavigationBarItem( label: '홈', icon: Icon(CupertinoIcons.house), ), const BottomNavigationBarItem( label: '내서재', icon: Icon(CupertinoIcons.book), ), const BottomNavigationBarItem( label: '검색', icon: Icon(CupertinoIcons.search), ), const BottomNavigationBarItem( label: '설정', icon: Icon(CupertinoIcons.settings), ), ], ); } }
 

패키지

notion image
notion image
패키지는 각 페이지이고, 패키지 내부에 탭을 위한 패키지를 생성했다. 이제 내부 요소를 만들면 된다.
 
 

데이터를 불러온 뒤 생각해 보니, 이미 index 에 저장된 요소를 불러오기 때문에 그 사이에 업로드가 발생한다면 사용자는 그것을 볼 수 없는 문제가 있다
따라서 상단으로 스크롤 하면 새로운 데이터를 가져오는 기능이 추가로 필요하다.
Share article

[HootJem] 개발 기록 블로그