본문 바로가기
Flutter

[Flutter/플러터] Riverpod 장점 및 사용법

by 얘리밍 2022. 9. 28.
320x100
728x90

 

getx를 사용하다가 프로젝트의 규모가 점점 더 커질 것 같아서,

riverpod를 공부하기로 마음먹었다.

 

getx를 사용했을 때, 코드가 간결해져서 생산성이 높고,

global context로 어디에서나 객체 접근이 가능하지만 

이것이 나중에는 단점이 될 수도 있다는 생각에 flutter에서 공식으로 밀고있는 provider와 

그것의 개선안인 riverpod를 사용할 예정이다..

 

 

 


 

 

일단 본격적으로 riverpod에 대해 알아보자..

 

 

Riverpod가 생겨난 배경

    ▶   provider단점

 

  •   provider를 사용하다 보면 결국에는 UI 코드와 섞이게 되는 문제를 볼 수 있다.

 

main.dart

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider(
      create: (context) => MyFirstClass(),
      child: ProxyProvider<MyFirstClass, MySecondClass>(
        update: (context, firstClass, previous) => MySecondClass(firstClass),
        child: MyVisibleWidget(),
      ),
    );
  }
}

 

위의 코드를 보면 알 수 있다.. 

Provider로 위젯을 감싼 뒤 그 안에서 변화를 체크해야 하니 결국에는 UI와 복잡하게

얽혀있는 모습이다.. 

 

 

 

  • 타입에 의존하는 provider

 

main.dart

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider(
      create: (context) => 'A String far away.',
      child: Provider(
        create: (context) => 'A String that is close.',
        builder: (context, child) {
          return Text(Provider.of<String>(context));
        },
      ),
    );
  }
}

String 타입의 객체를 동일하게 제공하면 제일 가까운 놈에만 접근 가능하여

결국 A String far away의 값을 얻지 못한다..

 

 

 

그 외에도 여러번 접근하려면 그만큼의 선언이 필요하여 불편하고 제한적이다..

MultiProvider(
  providers: [
    Provider<Something>(create: (_) => Something()),
    Provider<SomethingElse>(create: (_) => SomethingElse()),
    Provider<AnotherThing>(create: (_) => AnotherThing()),
  ],
  child: Container(),
)

해당과 같이 main에서 사용할 provider들에 대해 명시해 주어야 한다. 결국엔 파일을 왔다 갔다 해야 할 것이다.

 

 

 

 

 

이러한 단점들을 보완한 것이 riverpod 이다.

 Riverpod장점

  •  provider를 일단 한번 선언하면 어디에서든 global 접근이 가능하다 
final greetingProvider = Provider((ref) => 'Hello Riverpod!');

 

 

  하지만 provider 자체는 flutter 위젯 트리에 종속된다. 따라서

  전체 위젯트리를 ProviderScope으로 감쌀 필요가 있다.

 

void main() {
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
}

 

 

 

  • ConsumerWidget의 사용

 

    이제 greetingProvider의 String 문자를 Text 위젯에 띄우는 방법에 대해 알아보자 

 

 

    1.  superclass를 ConsumerWidget으로 변경

class MyApp extends ConsumerWidget{
	...

 

 

  2. 위젯의 build 메소드에 ScopedReader function 추가

Widget build(BuildContext context, ScopedReader watch){
	...

 

 

  3. 위젯의 상태를 체크하여 rebuild 시켜주는 watch를 통해 provider 전달

final greeting = watch(greetingProvider);

 

 

 

전체 코드 

class MyApp extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final greeting = watch(greetingProvider);

    return MaterialApp(
      title: 'Riverpod Tutorial',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Riverpod Tutorial'),
        ),
        body: Center(
          child: Text(greeting),
        ),
      ),
    );
  }
}

 

 

 

오직 변화하는 Text만 감지를 해서 rebuild 해주고 싶을 때는 Consumer widget을 사용하면 된다.

 

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        ...
        body: Center(
          child: Consumer(
            builder: (context, watch, child) {
              final greeting = watch(greetingProvider);
              return Text(greeting);
            },
         ...
  }
}

 

 

 

  • AsyncValue의 사용으로 Future 처리를 더 쉽게 만들어준다.
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      		...
          child: Consumer(
            builder: (context, watch, child) {
              final responseAsyncValue = watch(responseProvider);
              return responseAsyncValue.map(
                data: (_) => Text(_.value),
                loading: (_) => CircularProgressIndicator(),
                error: (_) => Text(
                  _.error.toString(),
                  style: TextStyle(color: Colors.red),
                ),
              );
            ...
}

 

   map을 통해 data가 존재할 때, 데이터를 불러오는 중인 loading 상태일 때, 데이터를 불러올 수 없는 error 상태일 때

   각각 상태 처리를 한번에 가능하다. 

 

 

 

  • family를 통한 arguments 전달 
final greetingFamilyProvider = Provider.family<String, String>(
  (_, name) {
    return "Welcome, $name!";
  },
);

 

  family를 통해 String 인자를 넘겨주는 provider를 선언한다

 

sendArguments(WidgetRef ref) {
  ref.read(
    greetingFamilyProvider('yerim')
  );
}

 

  sendArguments 함수에서 greetingFamilyProvider에 name 인자값을 넘겨주면 된다.

 

 

 

 

  • autoDispose의 사용으로 자동으로 상태를 dispose 해줌

   

   fake request가 있을 때, 직접 dispose 해주지 않는 이상 계속 존재하였다. 

   하지만 autoDispose는 provider가 사용되지 않을 때, 알아서 dispose 해주는 역할을 한다. 

 

final responseProvider =
    FutureProvider.autoDispose.family<String, String>((ref, url) async {
  final httpClient = ref.read(fakeHttpClientProvider);
  return httpClient.get(url);
});

 


 

 

정리해보자면,

 

Riverpod

1. provider와 달리 compile time에 오류를 캐치할 수 있으며 (provider는 runtime에 오류가 난다..)

2. 동일한 유형의 여러 종속성을 주입할 수 있으며,

3. No BuildContext  즉, BuildContext가 더 이상 필요하지 않는다 

 

 

 

 

간단한 튜토리얼에 대해 알아보았다..

다음엔 더 깊게 들어가 보도록 하자

 

 

 

참고 : https://resocoder.com/

 

728x90
반응형