플러터에서 상태 관리는 굉장히 중요한 개념입니다
상태는 앱의 정보나 데이터를 나타내는 것으로, 어떤 시점에서 앱이 "어떻게 보이는지"와 "어떻게 동작하는지"를 결정합니다. 예를 들면, 체크박스의 체크 여부, 텍스트 입력 필드의 내용, 리스트의 아이템 등이 상태에 해당합니다.
플러터에서 모든 UI 요소는 위젯입니다. 위젯은 불변(Immutable)합니다. 즉, 한 번 생성되면 변경할 수 없습니다. 그렇기 때문에 UI를 업데이트하기 위해서는 새로운 위젯을 생성해야 합니다.
상태를 갖지 않는 위젯으로, 한 번 생성되면 변경되지 않습니다. build
메서드를 통해 UI를 정의합니다.
변경 가능한 상태를 갖는 위젯입니다. State
객체를 통해 상태를 관리하며, 상태가 변경될 때마다 setState
메서드를 호출하여 UI를 업데이트합니다.
플러터에서는 다양한 상태 관리 기법과 도구가 있습니다:
💡 작업 순서
1. 폴더 구조 잡기
1단계 목표 화면
ProductList
import 'package:class_my_part/models/product.dart';
import 'package:flutter/material.dart';
class ProductList extends StatelessWidget {
ProductList({super.key});
// 샘플 데이터 ==> class_model 로 옮길 예정
List<Product> productList =
List.generate(10, (index) => Product('p_${index}', '상품 ${index}', 1000));
Widget build(BuildContext context) {
return ListView.builder(
itemCount: productList.length,
itemBuilder: (context, index) {
return ListTile(
leading: Text('${productList[index].productId}'),
title: Text('${productList[index].productName}'),
trailing:
IconButton(onPressed: () {
// 로직 추가 예정
}, icon: Icon(Icons.shopping_cart)),
);
});
}
}
Product
class Product {
String productId;
String productName;
double price;
Product(this.productId, this.productName, this.price);
}
main
import 'package:class_my_part/view/product_list.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(child: Scaffold(
body: IndexedStack(
children: [
ProductList()
],
),
)),
);
}
}
2단계 목표 화면
💡 작업 순서
1. MyCart 뷰 만들기
2. main.dart 파일 수정
3. Appbar 생성
import 'package:flutter/material.dart';
import '../models/product.dart';
class MyCart extends StatelessWidget {
MyCart({super.key});
// 샘플 데이터 ---> view_model 옮길 예정
List<Product> cartList =
List.generate(2, (index) => Product('p_${index}', '상품 ${index}', 1000));
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.shopping_cart,
size: 30,
color: Colors.orangeAccent,
),
Text(
'${cartList.length} 개',
style: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
)
],
)
],
);
}
}
main
import 'package:clss_my_cart/view/my_cart.dart';
import 'package:clss_my_cart/view/product_list.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int _index = 0;
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: SafeArea(
child: Scaffold(
appBar: buildAppBar(),
body: IndexedStack(
index: _index,
children: [
ProductList(),
MyCart(),
],
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(icon: Icon(Icons.list), label: '상품'),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart), label: '장바구니'),
],
currentIndex: _index,
onTap: (newIndex) {
setState(() {
_index = newIndex;
});
},
),
),
),
);
}
}
AppBar buildAppBar() {
return AppBar(
title: const Text('teco 쇼핑'),
actions: [
Center(
child: Stack(
children: [
Icon(Icons.shopping_cart),
Positioned(
top: 0,
right: 0,
child: CircleAvatar(
radius: 8.0,
backgroundColor: Colors.redAccent,
child: Text('2'),
),
)
],
),
),
SizedBox(width: 16),
],
);
}
💡 작업 순서
1. view_model 만들기
ProductListViewModel
import 'package:class_my_part/models/product.dart';
class ProductListViewModel {
// 샘플 데이터 ==> class_model 로 옮길 예정 (통신)
List<Product> _productList =
List.generate(10, (index) => Product('p_${index}', '상품 ${index}', 1000));
List<Product> get products => _productList;
}
MyCartViewModel
import 'package:class_my_part/models/product.dart';
class MyCartViewModel {
// 데이터 상태 값
List<Product> _items = [];
List<Product> get items => _items;
// 아이템 등록 기능
void addProduct(Product product) {
_items.add(product);
}
// 아이템 제거 기능
void removeProduct(Product product) {
_items.remove(product);
}
}
MyCart 코드 수정
import 'package:flutter/material.dart';
import '../models/product.dart';
import '../view_models/my_cart_view_model.dart';
class MyCart extends StatelessWidget {
final MyCartViewModel myCartVm;
MyCart({required this.myCartVm, super.key});
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.shopping_cart,
size: 30,
color: Colors.orangeAccent,
),
Text(
'${myCartVm.items.length} 개',
style: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
)
],
)
],
);
}
}
최종 코드
main
import 'package:class_my_part/view/my_cart.dart';
import 'package:class_my_part/view/product_list.dart';
import 'package:class_my_part/view_models/my_cart_view_model.dart';
import 'package:class_my_part/view_models/product_list_view_model.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final ProductListViewModel productVm = ProductListViewModel();
final MyCartViewModel cartVm = MyCartViewModel();
int _index = 0;
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: SafeArea(
child: Scaffold(
appBar: buildAppbar(cartVm),
body: IndexedStack(
index: _index,
children: [
ProductList(productVm: productVm,myCartVm: cartVm),
MyCart(myCartVm: cartVm),
],
),
bottomNavigationBar: BottomNavigationBar(items: [
BottomNavigationBarItem(icon: Icon(Icons.list), label: '상품'),
BottomNavigationBarItem(icon: Icon(Icons.shopping_cart), label: '장바구니'),
],
currentIndex: _index,
onTap: (newIndex){
setState(() {
_index = newIndex;
});
},
),
)),
);
}
}
AppBar buildAppbar(MyCartViewModel myCartVm) {
return AppBar(
title: const Text('tenco 쇼핑'),
actions: [
Center(
child: Stack(
children: [
const Icon(Icons.shopping_cart),
Positioned(
top: 0,
right: 0,
child: CircleAvatar(
radius: 8.0,
backgroundColor: Colors.redAccent,
child: Text('${myCartVm.items.length}'),
),
)
],
),
),
const SizedBox(width: 15,)
],
);
}
ProductList
import 'package:class_my_part/view_models/my_cart_view_model.dart';
import 'package:class_my_part/view_models/product_list_view_model.dart';
import 'package:flutter/material.dart';
class ProductList extends StatefulWidget {
final ProductListViewModel productVm;
final MyCartViewModel myCartVm;
ProductList({required this.myCartVm,required this.productVm,super.key});
State<ProductList> createState() => _ProductListState();
}
// StatefulWidget 으로 변경 상위 클래스, 하위 클래스가 존재
// 하위 클래스에서 --> 상위 클래스에 접근 하기 위해 widget을 참조 변수로 제공합니다.
// 즉 widget은 StatefulWidget 클래스의 인스턴스를 참조 하며, 이를 통해 부모 위젯으로 부터
// 데이터를 전달 받거나 부모 위젯에 메서드를 호출 할 수 있습니다.
class _ProductListState extends State<ProductList> {
// DI 외부에서 생성자를 통해서 데이터를 주입
Widget build(BuildContext context) {
return ListView.builder(
itemCount: widget.productVm.products.length,
itemBuilder: (context, index) {
return ListTile(
leading: Text('${widget.productVm.products[index].productId}'),
title: Text('${widget.productVm.products[index].productName}'),
trailing:
IconButton(onPressed: () {
// 로직 추가 예정
setState(() {
widget.myCartVm.addProduct(widget.productVm.products[index]);
});
}, icon: Icon(Icons.shopping_cart)),
);
});
}
}
Mycart
import 'package:class_my_part/view_models/my_cart_view_model.dart';
import 'package:flutter/material.dart';
class MyCart extends StatelessWidget {
final MyCartViewModel myCartVm;
MyCart({required this.myCartVm,super.key});
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.shopping_cart,
size: 30,
color: Colors.blueAccent,
),
Text(
'${myCartVm.items.length} 개',
style: const TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
)
],
)
],
);
}
}
안녕하세요! 인상 깊게 봤습니다. 특히 앱 구현하려는 gif 에 흥미가 갔습니다 ㅎㅎ
이런 gif 제작 방법에 대해 알 수 있을까요??