๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿฅ– Bread Basics/Flutter

[Flutter] MVVM ChangeNotifier:: (ํ™”๋ฉด) / (๋ฐ์ดํ„ฐ, ๋กœ์ง) ๋”ฐ๋กœ

by BreadDev 2025. 6. 27.
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(); // ๋ณ€๊ฒฝ์‚ฌํ•ญ ์•Œ๋ฆผ
  }
}

๋™์ž‘ ๋ฐฉ์‹

  1. ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ → 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() {...}     // ํ™”๋ฉด
}