728x90
์ด๋ฒ ํฌ์คํธ์์๋ MVVM ํจํด๊ณผ ChangeNotifier๋ฅผ ํ์ฉํด์ ์ฑ ์์ ๊น๋ํ๊ฒ ๋ถ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด๊ฒ ์ต๋๋ค. ์ฝ๋๊ฐ ์ผ๋ง๋ ๊น๋ํด์ง๋์ง ์ง์ ํ์ธํด๋ณด์ธ์!
StatefulWidget์ ๋ฌธ์ ์ ๋์ง์ด๋ณด๊ธฐ
// ์ด์ ๋ฐฉ์: ๋ชจ๋ ๊ฒ์ด ํ ๊ณณ์
class _StateCounterHomePageState extends State<StateCounterHomePage> {
int count = 0; // ๋ฐ์ดํฐ
Widget build(BuildContext context) { ... } // ํ๋ฉด
onPressed: () { // ๋ก์ง
setState(() { count++; });
}
}
๋ฌธ์ ์ : ํ๋ฉด + ๋ฐ์ดํฐ + ๋ก์ง์ด ๋ชจ๋ ์์ฌ์์ด์ ์ ์ง๋ณด์๊ฐ ์ด๋ ค์
MVVM ํจํด์ด๋?
MVVM = Model + View + ViewModel
- Model: ๋ฐ์ดํฐ ๊ตฌ์กฐ (์ค๋ ์์ ์์๋ ๋จ์ํ int)
- View: UI ํ๋ฉด (Widget)
- ViewModel: ๋ฐ์ดํฐ์ ๋ก์ง์ ๊ด๋ฆฌํ๋ ์ค๊ฐ ๊ณ์ธต
ChangeNotifier์ ์ญํ
ChangeNotifier๋ Flutter์์ ์ ๊ณตํ๋ ๊ด์ฐฐ์ ํจํด(Observer Pattern) ๊ตฌํ์ฒด์ ๋๋ค.
class CounterViewModel extends ChangeNotifier {
// ๋ฐ์ดํฐ ๋ณด๊ด
int count = 0;
// ๋ก์ง ์คํ
void countUp() {
count = count + 1;
notifyListeners(); // ๋ณ๊ฒฝ์ฌํญ ์๋ฆผ
}
}
๋์ ๋ฐฉ์
- ๋ฐ์ดํฐ ๋ณ๊ฒฝ → 2. notifyListeners() ํธ์ถ → 3. UI ์๋ ์ ๋ฐ์ดํธ
์ฝ๋ ๋ถ์: ๊น๋ํ๊ฒ ๋ถ๋ฆฌ๋ ๊ตฌ์กฐ
ViewModel: ๋ฐ์ดํฐ์ ๋ก์ง ๋ด๋น
class CounterViewModel extends ChangeNotifier {
// ๋ฐ์ดํฐ
int count = 0;
CounterViewModel(); // ์์ฑ์
// โก ๋ก์ง
void countUp() {
print('CounterViewModel - countUp count: $count');
count = count + 1;
notifyListeners(); // "์ํ๊ฐ ๋ณ๊ฒฝ๋์ด์!" ์๋ฆผ
}
void countDown() {
count = count - 1;
notifyListeners(); // UI์๊ฒ ๋ค์ ๊ทธ๋ฆฌ๋ผ๊ณ ์ ํธ
}
}
ํน์ง:
- UI์ ์์ ํ ๋ถ๋ฆฌ๋ ์์ํ Dart ํด๋์ค
- Flutter ์์ ฏ๊ณผ ๋ ๋ฆฝ์ ์ผ๋ก ํ ์คํธ ๊ฐ๋ฅ
- ๋น์ฆ๋์ค ๋ก์ง์๋ง ์ง์ค
View: ํ๋ฉด๋ง ๋ด๋น
class MvvmChangeNotifierHomePage extends StatelessWidget {
MvvmChangeNotifierHomePage({super.key});
final viewModel = CounterViewModel(); // ViewModel ์ธ์คํด์ค ์์ฑ
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('MMVM Notifier Counter App')),
body: SafeArea(
child: Center(
// ListenableBuilder๋ก ๋ณ๊ฒฝ์ฌํญ ๊ฐ์ง
child: ListenableBuilder(
listenable: viewModel,
builder: (context, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 10,
children: [
Text(
'Counter.count : ${viewModel.count}', // ๋ฐ์ดํฐ ํ์
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
),
ElevatedButton(
onPressed: () {
viewModel.countUp(); // ๋ก์ง ํธ์ถ
},
child: Text('์นด์ดํธ ์ฆ๊ฐ'),
),
ElevatedButton(
onPressed: () {
viewModel.countDown(); // ๋ก์ง ํธ์ถ
},
child: Text('์นด์ดํธ ๊ฐ์'),
),
],
);
}
),
),
),
);
}
}
ํน์ง:
- StatelessWidget ์ฌ์ฉ! (์ํ ๊ด๋ฆฌ๋ ViewModel์ด ๋ด๋น)
- ListenableBuilder๊ฐ ViewModel์ ๋ณ๊ฒฝ์ฌํญ์ ์๋์ผ๋ก ๊ฐ์ง
- ์ค์ง UI ๊ตฌ์ฑ์๋ง ์ง์ค
ListenableBuilder์ ํน์ง
ListenableBuilder(
listenable: viewModel, // ์ด ๊ฐ์ฒด์ ๋ณํ๋ฅผ ๋ค์ด๋ผ
builder: (context, child) { // ๋ณํ๊ฐ ์์ผ๋ฉด ์ด ํจ์๋ฅผ ๋ค์ ์คํํด๋ผ
return Column(...); // ์๋ก์ด UI๋ฅผ ๊ทธ๋ ค๋ผ
}
)
StatefulWidget vs MVVM ChangeNotifier ๋น๊ต
์ฑ ์ ๋ถ๋ฆฌ
// StatefulWidget: ๋ชจ๋ ๊ฒ์ด ํ ๊ณณ์
class _CounterState extends State<CounterWidget> {
int count = 0; // ๋ฐ์ดํฐ
Widget build() { return ...; } // ํ๋ฉด
onPressed: () { setState(() {...}); } // ๋ก์ง
}
// MVVM: ๊น๋ํ๊ฒ ๋ถ๋ฆฌ
class CounterViewModel extends ChangeNotifier {
int count = 0; // ๋ฐ์ดํฐ
void countUp() {...} // ๋ก์ง
}
class CounterView extends StatelessWidget {
Widget build() {...} // ํ๋ฉด
}
'๐ฅ Bread Basics > Flutter' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Flutter] Riverpod ์ํ๊ด๋ฆฌ:: ๊ตฌ์กฐํ๋ ์ ๊ทผ๋ฒ๊ณผ ํ์ ์์ ์ฑ (0) | 2025.06.27 |
---|---|
[Flutter] GetX ์ํ๊ด๋ฆฌ: ๋ฐ์ํ ํ๋ก๊ทธ๋๋ฐ (0) | 2025.06.27 |
[Flutter] Provider ์ํ๊ด๋ฆฌ:: ChangeNotifier๋ฅผ ๋ ์ฒด๊ณ์ ์ผ๋ก (0) | 2025.06.27 |
[Flutter] StatefulWidget:: ํ๋ฉด, ๋ฐ์ดํฐ, ๋ก์ง์ ํ ๋ฒ์ (0) | 2025.06.27 |
Dart ์ธ์ด ๊ธฐ๋ณธ๊ธฐ ๋ฐฐ์ฐ๊ธฐ (0) | 2025.04.28 |
mixin, sealed, base ํด๋์ค (0) | 2025.04.25 |
Record(ํํ), Destructuring (0) | 2025.04.25 |
Future, async, await, stream, listen, sink, yield (0) | 2025.04.25 |