기존 코드
import 'package:flutter/material.dart';
import 'package:meals_clone/screens/categories.dart';
// import 'package:riverpod/riverpod.dart';
class TabScreen extends StatefulWidget {
const TabScreen({super.key});
@override
State<TabScreen> createState() => _TabScreenState();
}
class _TabScreenState extends State<TabScreen> {
int _selectedPageIndex = 0;
Widget categoriesScreen = const CategoriesScreen();
Widget placeholder = const Placeholder();
Widget activePage = const CategoriesScreen();
void _selectPage(int index){
setState(() {
_selectedPageIndex = index;
});
if(_selectedPageIndex == 0){
activePage = categoriesScreen;
}
else if(_selectedPageIndex == 1){
activePage = placeholder;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Categories'),
centerTitle: true,
),
// body: const CategoriesScreen(),
body: activePage,
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(
icon: Icon(
Icons.set_meal,
size: 30,
),
label: '카테고리'),
BottomNavigationBarItem(
icon: Icon(
Icons.star,
size: 30,
),
label: '즐겨찾기')
],
showSelectedLabels: false, // 선택된 레이블 숨기기
showUnselectedLabels: false, // 선택되지 않은 레이블 숨기기
currentIndex: _selectedPageIndex,
onTap: _selectPage,
),
);
}
}
수정된 코드
import 'package:flutter/material.dart';
import 'package:meals_clone/screens/categories.dart';
// import 'package:riverpod/riverpod.dart';
class TabScreen extends StatefulWidget {
const TabScreen({super.key});
@override
State<TabScreen> createState() => _TabScreenState();
}
class _TabScreenState extends State<TabScreen> {
int _selectedPageIndex = 0;
void _selectPage(int index) {
setState(() {
_selectedPageIndex = index;
});
}
@override
Widget build(BuildContext context) {
// 현재 선택된 페이지에 따라 적절한 위젯을 동적으로 선택합니다.
Widget activePage = _selectedPageIndex == 0
? const CategoriesScreen()
: const Placeholder();
return Scaffold(
appBar: AppBar(
title: const Text('Categories'),
centerTitle: true,
),
body: activePage,
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(
icon: Icon(
Icons.set_meal,
size: 30,
),
label: '카테고리'),
BottomNavigationBarItem(
icon: Icon(
Icons.star,
size: 30,
),
label: '즐겨찾기')
],
showSelectedLabels: false, // 선택된 레이블 숨기기
showUnselectedLabels: false, // 선택되지 않은 레이블 숨기기
currentIndex: _selectedPageIndex,
onTap: _selectPage,
),
);
}
}
수정된 내용
불필요한 인스턴스화 제거:
categoriesScreen과placeholder위젯을 미리 인스턴스화할 필요가 없다. 이들은 필요할 때 생성될 수 있으며, 위와 같이 수정하면 메모리 사용량을 줄이고 성능을 향상시킬 수 있다.상태 관리 개선: 상태를 더 효율적으로 관리하기 위해,
_selectPage메서드 내에서activePage를 직접 변경하는 대신,build메서드 내에서_selectedPageIndex에 기반하여 동적으로 페이지를 결정하도록 수정되었다.
+추가 의문사항
이렇게 수정하고나니 만약 탭이 여러 개일 때는 어떻게 구현해야 적절할지, 그리고 방식에 따라서 메모리는 어떻게 사용될지 궁금해서 찾아보았다.
1. List 방식
설명:
List<Widget>방식에서는 탭에 대응하는 모든 위젯을List에 미리 저장한다. 탭 전환 시, 이List에서 해당 인덱스의 위젯을 가져와 렌더링한다.이 방식에서는 각 탭의 위젯이
List에 미리 인스턴스화되어 있으며, 탭 전환 시 해당 인스턴스를 재사용한다.
장점:
초기 세팅의 단순함: 모든 탭의 위젯을 미리 정의하고 관리하기 때문에, 초기 코드 구성이 단순.
렌더링 성능: 이미 생성된 위젯 인스턴스를 재사용하기 때문에, 탭 전환 자체는 빠르게 이루어진다.
단점:
메모리 사용량: 모든 탭의 위젯이 메모리에 미리 로드되므로, 앱 시작 시 메모리 사용량이 높아질 수 있다.
상태 유지의 부재: 탭 전환 시 위젯의 상태가 유지되지 않는다. 새로운 탭으로 전환할 때마다 위젯이 재생성될 수 있으며, 이는 이전 탭의 상태가 초기화될 수 있음을 의미한다.
예시 코드:
class MyTabScreen extends StatefulWidget {
@override
_MyTabScreenState createState() => _MyTabScreenState();
}
class _MyTabScreenState extends State<MyTabScreen> {
int _selectedIndex = 0;
final List<Widget> _tabs = [
WidgetA(), // 탭 A에 대응하는 위젯
WidgetB(), // 탭 B에 대응하는 위젯
WidgetC(), // 탭 C에 대응하는 위젯
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: _tabs[_selectedIndex], // 현재 선택된 탭의 위젯을 렌더링
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: (index) => setState(() => _selectedIndex = index),
// BottomNavigationBarItems 정의
),
);
}
}
2. IndexedStack 방식
설명:
IndexedStack은 모든 자식 위젯을 미리 인스턴스화하지만, 한 번에 하나의 자식만 화면에 표시한다.이 방식은 탭 간 상태를 유지하며 탭 전환 시 빠른 반응성을 제공한다.
장점:
탭의 상태가 유지된다. 예를 들어, 사용자가 스크롤한 위치는 그대로 유지된다.
이미 로드된 위젯을 화면에 표시하기만 하면 되기 때문에 탭 전환 시 빠르게 반응한다.
단점:
- 초기 앱 로드 시 모든 탭의 위젯이 메모리에 로드된다. 이는 초기 메모리 사용량이 크다는 것을 의미한다.
예시 코드:
class MyTabScreen extends StatefulWidget {
@override
_MyTabScreenState createState() => _MyTabScreenState();
}
class _MyTabScreenState extends State<MyTabScreen> {
int _selectedIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _selectedIndex,
children: [
WidgetA(),
WidgetB(),
WidgetC(),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: (index) => setState(() => _selectedIndex = index),
// ... BottomNavigationBarItems ...
),
);
}
}
3. 지연 인스턴스화 (Lazy Instantiation) 방식
설명:
지연 인스턴스화 방식은 탭의 위젯을 실제로 필요할 때 생성한다. 즉, 탭이 활성화될 때 해당 탭의 위젯이 인스턴스화된다.
이 방식은 메모리 사용을 최적화할 수 있으나, 탭의 상태가 유지되지 않는다.
장점:
초기 앱 로드 시에는 필요한 탭의 위젯만 메모리에 로드되어 메모리 사용량이 적다.
사용하지 않는 탭의 위젯은 메모리에 로드되지 않는다.
단점:
탭 전환 시마다 새로운 위젯 인스턴스를 생성해야 한다.
탭의 상태가 유지되지 않는다.
예시 코드:
class MyTabScreen extends StatefulWidget {
@override
_MyTabScreenState createState() => _MyTabScreenState();
}
class _MyTabScreenState extends State<MyTabScreen> {
int _selectedIndex = 0;
Widget _buildTab(int index) {
switch (index) {
case 0:
return WidgetA();
case 1:
return WidgetB();
case 2:
return WidgetC();
default:
return Placeholder();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: _buildTab(_selectedIndex),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: (index) => setState(() => _selectedIndex = index),
// ... BottomNavigationBarItems ...
),
);
}
}