본문 바로가기
Flutter/Flutter

Provider 기초개념정리 + 예제

by 복복씨 2024. 9. 18.

Provider란?

Provider는 상태 관리를 쉽게 하게 해준다. 여러 화면에서 같은 상태를 공유할 수 있고, 상태가 변경되면 UI를 자동으로 업데이트한다.

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();  // 상태 변경 알림
  }
}

ChangeNotifier

상태가 변경될 때 UI에 알림을 보내 다시 그리도록 한다.

class MyModel extends ChangeNotifier {
  int value = 0;

  void increment() {
    value++;
    notifyListeners();  // UI에 상태가 변경되었음을 알림
  }
}

ChangeNotifierProvider

ChangeNotifier를 앱에서 사용할 수 있도록 해주는 도구다.

ChangeNotifierProvider(
  create: (_) => MyModel(),
  child: MyApp(),
);

MultiProvider

여러 상태를 다루고 싶을 때 사용한다.

MultiProvider(
  providers: [
    ChangeNotifierProvider(create: (_) => MyModel1()),
    ChangeNotifierProvider(create: (_) => MyModel2()),
  ],
  child: MyApp(),
);

Provider.of(context)

Provider에 저장된 상태를 가져올 때 사용한다.

int value = Provider.of<MyModel>(context).value;

Consumer

상태가 변경될 때 해당 부분만 다시 그리도록 도와준다.

Consumer<MyModel>(
  builder: (context, model, child) {
    return Text(model.value.toString());
  },
);

context.watch()

상태를 구독하고, 변경이 발생하면 UI를 다시 그린다.

int value = context.watch<MyModel>().value;

context.read()

상태를 한 번만 가져오고, 이후 상태 변경을 감지하지 않는다.

context.read<MyModel>().increment();

notifyListeners()

상태가 변경되면 구독하는 모든 위젯에 알림을 보낸다.

void increment() {
  value++;
  notifyListeners();  // 구독 중인 모든 위젯에 알림
}

성능 최적화

ConsumerSelector를 사용해 필요한 부분만 다시 그리도록 하여 성능을 최적화한다.

Consumer<MyModel>(
  builder: (context, model, child) {
    return Text(model.value.toString());
  },
);

이 코드들을 통해 상태를 효율적으로 관리하고 성능을 최적화할 수 있다.

 

더 잘 이해하기 위해 버스가 도착하는 시간을 알려주는 버스알림앱을 예로 들어보도록 하자

 

코드 예시 1)

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'bus_view_model.dart';
import 'user_favorite_bus_view_model.dart';

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Provider를 사용해 BusViewModel과 UserFavoriteBusViewModel을 주입받음
    final busViewModel = Provider.of<BusViewModel>(context);  
    final favoriteBusViewModel = Provider.of<UserFavoriteBusViewModel>(context);

    return Scaffold(
      appBar: AppBar(title: Text('즐겨찾기한 버스')),
      body: Column(
        children: [
          // 버튼 클릭 시 BusViewModel의 fetchBusStops() 호출 -> API 호출
          ElevatedButton(
            onPressed: () {
              busViewModel.fetchBusStops(); // API 호출
            },
            child: Text('버스 도착 정보 불러오기'),
          ),
          // 즐겨찾기된 버스 리스트 출력
          Expanded(
            child: ListView.builder(
              itemCount: favoriteBusViewModel.favoriteBuses.length,
              itemBuilder: (context, index) {
                final bus = favoriteBusViewModel.favoriteBuses[index];
                return ListTile(
                  title: Text('${bus.busNumber}번 버스 - ${bus.stopName}'),
                  subtitle: Text('도착 시간: ${bus.arrivalTime}'),
                  // 즐겨찾기 삭제 버튼
                  trailing: IconButton(
                    icon: Icon(Icons.delete),
                    onPressed: () {
                      favoriteBusViewModel.removeFavoriteBus(bus); // 즐겨찾기 삭제
                    },
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

 

  • Provider 주입: Provider.of<T>(context)를 사용해 BusViewModelUserFavoriteBusViewModel을 각각 주입받음.
  • API 호출: 버튼 클릭 시 busViewModel.fetchBusStops()를 호출해 버스 도착 정보를 가져오는 API 호출.
  • 즐겨찾기 목록: favoriteBusViewModel.favoriteBuses에서 즐겨찾기된 버스 리스트를 가져와 화면에 표시.
  • 즐겨찾기 삭제: 삭제 버튼 클릭 시 favoriteBusViewModel.removeFavoriteBus(bus)로 즐겨찾기된 버스를 삭제.

 

2) 메인의 코드를 봐보자 

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'bus_view_model.dart';
import 'user_favorite_bus_view_model.dart';
import 'home_screen.dart';
import 'bus_list_screen.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // MultiProvider로 여러 ViewModel을 제공
    return MultiProvider(
      providers: [
        // BusViewModel을 주입하여 상태 관리
        ChangeNotifierProvider(create: (_) => BusViewModel()),
        // UserFavoriteBusViewModel을 주입하여 즐겨찾기 상태 관리
        ChangeNotifierProvider(create: (_) => UserFavoriteBusViewModel()),
      ],
      child: MaterialApp(
        title: 'Bus API Example',
        home: HomeScreen(), // 초기 화면을 HomeScreen으로 설정
        routes: {
          '/bus-list': (context) => BusListScreen(), // '/bus-list' 경로 설정
        },
      ),
    );
  }
}

 

 

  • MultiProvider: BusViewModel과 UserFavoriteBusViewModel 두 개의 상태(ViewModel)를 제공.
  • ChangeNotifierProvider: 각 ViewModel을 ChangeNotifierProvider로 주입하여 상태 관리.
  • MaterialApp: 앱의 초기 화면을 HomeScreen으로 설정하고, /bus-list 경로를 설정해 화면 간 이동 가능하게 함.

 


궁금증, 프로바이더가 MVVM에 쓰이는 기본 상태관리 툴이라고 했는데, 그렇다면 ViewModel 자체에 프로바이더를 씌우는게 당연한가?


< 이야기를 나눠 본 결과 >

보통은 그렇게 쓴다고 한다. 보통 RxSwift에서 쓰는 형태로 생각하면 될 것 같다. 상태는 보통 Data라고 생각해 상태관리하면 그 안에 있는 데이터만 바뀌어야한다고 생각했다. 편협한 생각이었을지도. MVVM에 안맞는 생각이었던거같은데, 좀 더 고민해볼 여지가 있는듯하다.

댓글로 다르게 생각하는, 혹시 첨언을 해주실 여러분의 의견을 정말 환영하며