본문 바로가기
개발/Flutter

Flutter Provider (프로바이더 with MVVM) 상태관리 패키지

by 파란검정 2022. 4. 5.
반응형

Provider란? 

 

일반적으로 우리가 어떤 데이터를 위젯에서 표현하다 보면

같은 데이터를 공유하는 Widget이 발생하게 되고

데이터의 무결성을 위해서 State값을 공유하고

하위 위젯에게 전달하기 위해서 의미 없는 StatefulWidget을 구현해야 할때가 있다.

 

위젯에서 위젯으로 데이터 State를 전달하고 전달해서

그변화를 감지하여 화면에 다시 그려주고 하는 일들을 하게 되면 

State값의 변화로 인해 다시 그리지 않아도 되는 Widget을 빈번하게 다시 그리게 된다.

 

상태값(State)을 공유하면서 Depth가 깊어지고

그러면서 flutter구조가 가져오는 성능저하를 개선하고

반복되는 코드를 제거 하기 위해서 만들어진 것이 Provider이다. 

 

이 포스팅에서는 간단하게 viewModel과 provider를 작성하고

어떻게 동작하는지에 대하여 살펴 보겠다. 

 

 

다음은 Play 버튼을 클릭하면 viewModel의 상태를 변경하여

버튼의 이미지를 바꾸고 상태에 따른 Text를 표현하는 프로그램이다. 

 

 

 


환경설정 

pubspec.yaml 파일에 dependency 추가  (provider)

 

provider | Flutter Package

A wrapper around InheritedWidget to make them easier to use and more reusable.

pub.dev

provider: ^6.0.2

 

 

 

 


ViewModel 작성 

 

상태변화를 전달할 수 있게 ChangeNotifier 클래스를 상속받는 viewModel을 작성 

재생중인지 아닌지를 확인 할 수 있는 값인 isPlaying 값을 가지고 있는 ViewModel을 다음과 같이 작성한다. 

import 'package:flutter/material.dart';

class PlayerViewModel with ChangeNotifier {
  //player 중인지를 확인할 값
  var _isPlaying = false;

  bool get isPlaying => _isPlaying;

  void playSwitch() {
    _isPlaying = !_isPlaying;
    notifyListeners();  //<-- 변경 내용을 전파(알림)
  }
}

 

 


Provider 작성 

 

상태변화를 공유할 Widget을 정의 하기 위한 방법으로는 

다중 Provider와 단일 Provider 방식이 있다. 

 

다중 Provider의 경우는 다음과 같다. 여러개의 ViewModel을 사용할 수 있다. 

또한 정의한 위젯의 하위 Widget 이하의 모든 Widget에서 Provider를 통해 PlayerViewModel을 사용 할 수 있게 된다. 

void main() {
  runApp(MultiProvider(
    providers: [ChangeNotifierProvider(create: (_) => PlayerViewModel()),],
    child: const Player(),
  ));
}

 

 

하나의 Provider만 정의 하는 경우 하나의 ViewModel만 사용하게 되지만 역시 하위 모든 위젯에서 같은 viewModel을 이용할 수 있다. 

void main() {
  runApp(ChangeNotifierProvider(
    create: (_) => PlayerViewModel(),
    child: Player(),
  ));
}

 

 

Provider에 대해서 조금 더 살펴보자면 

Provider/MultiProvider는 Widget을 상속받고 있기 때문에 적용하고 싶은 Widget을 감싸서 적용하면 된다. 

다음은 Provider와 MultiProvider의 code이다. 

 

Provider

class Provider<T> extends InheritedProvider<T>
...
class InheritedProvider<T> extends SingleChildStatelessWidget

 

MultiProvider

class MultiProvider extends Nested 
...
class Nested extends StatelessWidget implements SingleChildWidget

 

 

Provider의 종류는 여러가지가 존재 한다. 위의 예제 에서는 ChangeNotifierProvider에 대해서 만 이야기 하고 있지만 

일반 Provider, StreamProvider, ProxyProvider... 등이 존재하고 각각 역할이 상이 하다.  

 

아래 문서를 참고 하면 다른 Provider들의 종류와 동작방식에 대해서 참고 할 수 있다. 

https://github.com/rrousselGit/provider/blob/master/resources/translations/ko-KR/README.md

 

GitHub - rrousselGit/provider: InheritedWidgets, but simple

InheritedWidgets, but simple. Contribute to rrousselGit/provider development by creating an account on GitHub.

github.com

 

Provider The most 기본적인 provider 형태. 어떤 값이던 간에 값을 노출시킵니다.
ListenableProvider Listenable 객체를 위한 특수한 provider. ListenableProvider는 listener가 호출될 때마다 오브젝트를 수신하고 오브젝트에 종속된 위젯을 재구성하도록 요청합니다.
ChangeNotifierProvider ChangeNotifier용 ListenableProvider 사양. 필요할 때 자동으로 ChangeNotifier.dispose를 호출합니다.
ValueListenableProvider ValueListenable을 수신하고, ValueListenable.value만을 노출합니다.
StreamProvider 스트림을 수신하고 최신 값을 표시합니다.
FutureProvider Future를 받고, 완성되었을 때 의존된 객체를 업데이트합니다.

 

 


ViewModel을 사용하는 Widget의 Sample

 

실제 사용하는 Widget Code안에서  

viewModel을 불러오는 코드의 예제는 다음과 같다.

context를 참조해야 하기 때문에 build 메소드 안에서 정의 되어야 한다. 

//viewModel의 상태 변화를 감기 가능 
var viewModel = Provider.of<PlayerViewModel>(context);

//viewModel의 상태 변화 감지 불가능 
var viewModel = Provider.of<PlayerViewModel>(context, listen: false);

 

 

viewModel이 가지고 있는 Data가 커서 사용하지 않는 데이터의 변화로 인해 UI의 reder가 자주 발생한다면 해당 widget이 사용해야할 특정 data값만 골라서 사용할 수 있다. 

var flag = context.select((PlayerViewModel value) => value.isPlaying);

 

 

아래 위젯을 잘보면 특이한 점이 하나 있는데 Widget은 분명 StatelessWidget인데 viewModel의 상태 값 변경에 따라 widget을 다시 그려준다. 변경된 상태값의 다시 적용을 위해 viewModel에서 사용한 notifyListeners()를 잊지 말자. 

 

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

  @override
  Widget build(BuildContext context) {
  	//viewModel load 
    var viewModel = Provider.of<PlayerViewModel>(context);
    
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(children: [
            const Spacer(),
            //viewModel 상태값으로 widget 그리기 
            Text(viewModel.isPlaying ? "재생중" : "정지중"),
            
            GestureDetector(
              onTap: () {
              	//viewModel 상태 변경 
                viewModel.playSwitch();
              },
              
              //viewModel 상태값으로 widget 그리기 
              child: Icon(
                 viewModel.isPlaying
                    ? Icons.pause_rounded
                    : Icons.play_arrow_rounded,
                size: 80,
              ),
            ),
            const Spacer(),
          ]),
        ),
      ),
    );
  }
}

 

 

 


 

 

flutter의 구조상 다른 depth에 있지만 상태값을 공유하는 widget을 표현하기 위해서 상태값과 연결된 최 하위 widget은 상태값 변경에 의하여 변경되면서 최상위와 최하위 사이에 있는 Widget들이 모두 다시 그려지게 되는데 이는 성능저하를 불러올 수 있다.

 

 

이런 문제점을 해결하기 위해서 만들어 진것이 Provider이다.

Provider를 통하면 실제 표현해야 하는 Data는 ViewModel로 구현하고 필요한 Widget에서 viewModel의 정보에 접근하여 표현 하면된다. Provider를 이해하고 있다면 코드 구현이 훨씬 더 효율적이고 이해하기 쉽게 된다. 

 

이는 로직 분리를 통해 더욱 더 효율적인 코드를 만들 수 있는데 MVVM 패턴이 그에 속한다. 

어플리케이션 개발을 경험 해본 사람은 MVVM에 대해서 들어본적이 있을 것이다. 

Provider는 Flutter에서 MVVM 패턴을 사용하기에 적합한 library이다. 

 

 

sample Code (https://github.com/blackzaket/sampleMVVM_easy_flutter)

 

 

반응형