[Flutter] Provider를 통해 상태관리하기

NoeG·2022년 7월 14일
0

개발

목록 보기
3/3

Flutter로 프로젝트를 진행하며 ViewModel을 구현하기 위해 상태관리 패키지를 사용하였습니다.

왜 상태관리를 사용하는가?

State 관리

Flutter에서는 두 가지 State의 위젯을 만들 수 있습니다.

1. Stateless Widget

정적인 상태
= State가 변하지 않는다
= 화면이 한 번만 빌드된다

2. Stateful

동적인 상태
= State가 변한다
= State의 변화에 따라 화면이 반복적으로 빌드된다

Stateful Widget의 경우 State 관리를 통해 를 계속 관찰하며 화면을 빌드해주어야 합니다.

Stateful Widget에서 setState() 를 통해 화면을 재빌드하는 방식이 제공이 되지만 그럼에도 상태 관리를 별개로 해주어야하는 이유가 있습니다.

어플리케이션의 구조

데이터를 관리해야한다

어플리케이션에서 활용되는 데이터는 서버, 로컬 DB, 사용자 등과 통신을 하며 처리를 통해 사용하게 됩니다.

이러한 데이터 관리를 Stateful 위젯의 setState() 만으로 처리하는 것은 어플리케이션의 구조가 복잡해지고 데이터의 양이 많아질수록 불가능합니다.

따라서 이를 효과적으로 처리하기 위하여 아키텍처 패턴을 통해 작업들을 완전히 분리하여 코드의 생산성을 높이게 됩니다.

View & Model 그리고 ViewModel

모바일 어플리케이션은 기본적으로 View와 Model 그리고 View와 Model을 연결하는 레이어로 이루어져 있습니다.

  • MVC 패턴의 경우 Controller
  • MVP 패턴의 경우 Presenter
  • MVVM 패턴의 경우 ViewModel

본 프로젝트에서는 MVVM 패턴을 사용하며 ViewModel을 구현했어야 했습니다.

ViewModel의 역할은 View에 뿌려질 데이터를 처리 및 관리하는 것입니다.

BLOC 패턴

Flutter에서는 BLOC 패턴을 권장해왔습니다.

BLOC = Bussiness Logic Component

MVVM 모델과 유사하지만 ViewModel로 BLOC을 사용하게 되는 것입니다. 그러나 BLOC 패턴을 사용하기 위해서는 수많은 작업이 필요합니다.

MVVM 모델의 약점인 ViewModel을 짜기 어렵다를 더욱 어렵게 많드는 것입니다.

BLOC 패턴의 구현을 위해서는
1. Flutter 에서는 Stream 을 이용하여 BLoC 을 구현
2. StreamController 으로 Observable 객체 생성
3. StreamController 의 Sink 를 이용하여 값 전달
4. StreamController 의 Steam 를 이용하여 상태 구독
이러한 과정이 필요합니다.

너무나 많은 과정을 거쳐야하기에 이를 편리하게 할 수 있도록 나온 것이 Provider라는 상태관리 패키지입니다.

Provider

https://pub.dev/packages/provider

많은 Flutter 유저의 사랑을 받는 상태관리 패키지입니다.
이외에도 Get, RiverPod 등이 있으나 Provider는 이러한 상태관리 패키지 중 가장 기본이라고 할 수 있습니다.

Get

context 불필요 등 다양한 편의 기능 제공
과하다라는 평이 많고 의존도가 높아진다는 단점이 있습니다.

Get과 Provider를 비교하여 편한 것을 사용하면 된다고 생각합니다만, Get에 대한 불안감과 Flutter 역량을 높이기 위하여 원조 패키지 격인 Provider를 채택하였습니다.

Provider 사용하기

State 관리가 이루어지는 ViewModel

// ChangeNotifier를 꼭 extends 해야한다
class Counter extends ChangeNotifier {
  // 값을 저장할 변수로 외부에서 직접 접근할 수 없도록 private으로 선언
  int _count = 0;

  // count 값을 내보내는 get 선언
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // notifyListeners()를 호출하여 View가 다시 빌드하여 새롭게 관찰하도록 공지
  }
}

State 관리가 필요한 View

참조할 Provider 선언

class Count extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Counter>( // 상태 관리가 이루어지는 Provider 사용 선언
      create: (_) => Counter(),
      // Counter의 숫자를 보여주는 View
      child: Example(), 
    );
  }
}

Provider를 통해 View 의 상태를 관리하기 위해서는 필수적으로 create를 거쳐야 합니다.

MultiProvider

void main() {
  runApp(
    /// 사용할 Provider를 모두 선언
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => Counter()),
      ],
      child: const MyApp(),
    ),
  );
}

사용되는 위치에서 선언을 해도 되지만 공식 예제처럼 main 함수에서 MultiProvider 통해 미리 사용될 Provider에 대해 초기 선언을 하기도 합니다.

  1. ChangeNotifierProvider
    데이터를 관찰할 수 있다
  2. Provider
    데이터를 관찰할 수 없다

즉 데이터가 변경되어도 View에 반영할 수 없습니다.
(ChangeNotifierProvider에서 관찰을 안 할 수는 있지만 Provider에서는 관찰을 할 수 없으니) ChangeNotifierProvider를 사용하는 것이 좋습니다.

적절한 데이터에서 Provider 사용하기

class Count extends StatelessWidget {
  const Count({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
    	// Provider 내에서 관리하는 count 데이터 관찰
        '${Provider.of<Counter>(context).count}',
        key: const Key('counterState'),
        style: Theme.of(context).textTheme.headline4);
  }
}

class Example extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          child: Text(
            // Provider 내에서 관리하는 count 데이터 관찰
            '${context.watch<Counter>().count}',
          ),
          onPressed: () {
            context.read<Counter>().increment(); // Count를 증가시키기는 이벤트 발생
          },
        ),
      ),
    );
  }
}

Provider의 사용 용도는 두 가지입니다.
1. 이벤트를 수행하기

context.read<Counter>().count 
= Provider.of<Counter>(context. listen: false).increment

해당 이벤트를 수행하며 재빌드되지 않습니다
(데이터의 관찰에 의해 빌드되는 것입니다)

  1. 데이터의 상태를 관찰하기
context.watch<Counter>().count 
= Provider.of<Counter>(context. listen: true).count

notifyListeners()가 호출되면 Provider의 데이터를 관찰하기 위해 재빌드가 이루어집니다.

Provider의 직관적인 사용

read와 watch보다는 listen에 true/false를 주어 구분하는 것이 직관적이라고 생각됩니다.

Provider를 사용하였을 때 Stateless Widget을 주로 사용하였습니다. 관찰을 통해 재빌드를 하기에 Stateful Widget이 사용될 필요가 없는 경우가 많았습니다. 이를 통해 어플리케이션의 성능을 미세하게 개선시킬 수 있을 것입니다.

profile
아이디어를 세상에 꽃 피워보기

0개의 댓글