현재 Flutter에서 많이 사용되는 상태관리 도구중 하나가 Riverpod 입니다.
provider에서 출발했지만 provider에 존재하는 단점이 꽤 있어서 이것을 극복하고자 Riverpod가 탄생 하였습니다.
Flutter를 개발하시는 분들은 다들 아시겠지만 context의존도가 꽤 높습니다.
그리고 위젯별로 독립적이다 보니 A위젯과 B위젯의 데이터 전달하기가 꽤나 까다롭습니다.
물론 static 클래스를 활용해서 전달하면 되겠지만 A위젯에서 데이터를 변경해서 B위젯을 업데이트 하는 방식, 혹은 DB를 감지하다 DB가 변경되어 위젯을 업데이트 하는 방식 등에서 Flutter의 기본 상태 관리만으로 업데이트 하기가 상당히 까다롭습니다.
그래서 나온것들이 상태관리 개념인데 이전에는 GetX가 많이 쓰였고 현재는 Riverpod가 많이 쓰이는 상태 입니다.
Riverpod 는 StatelessWidget과 StatefulWidget에 각각 대응되는 ConsumerWidget 과 ConsumerStatefulWidget이 있습니다.
ConsumerWidget은 업데이트 없이 Riverpod에 데이터를 읽어와서 한번 표시하는 말그대로 StatelessWidget에 대응되는 개념입니다.
ConsumerSatefulWidget은 Riverpod 의 값을 읽어서 계속 위젯을 업데이트 하는 방식 입니다.
값을 읽으려면 ref란 객체에 접근해야 합니다.
ref는 provider에 접근하고 제어하는 도구 입니다.
riverpod 의 ConsumerWidget 과 ConsumerStatefulWidget을 생성하면 ref란 객체가 있는데
여기서 provider를 read하거나 watch 할 수 있습니다.
provider는 ref에 데이터를 제공하는 객체로 사용자가 관리할 데이터들 입니다.
클래스가 될 수 있고 단일 데이터가 될 수 있습니다.
class Counter extends Notifier<int> {
@override
int build() => 0;
void increment() => state++;
}
final counterProvider = NotifierProvider<Counter, int>(() => Counter());
CounterProvider를 구성하는 기본적인 예 입니다.
int형 데이터 1개를 provider로 구성하여 상태관리하게 됩니다.
state 내부에 실제 값이 있고(generic으로 구성된 객체가 state가 됩니다.) 이것들을 제어하는 함수가 현재는 increment()함수로 구성 됩니다.
- state : 실제 값을 가지고 있는 객체
- increment() : 값을 변경하는 함수
여기서 중요한 것이 provider를 구성할때 Notifier 의 generic으로 구성하면 되는데 대부분의 케이스에서 클래스를 사용 할 것 입니다.
관리해야 하는 데이터가 많을 것 이니까요.
class Human {
int age;
String name;
Human({this.age = 1, this.name = ""});
}
이렇게 구성된 클래스가 있다고 했을때 provider에서 setAge 라는 함수가 있다고 친다면
void setAge(int age){
state.age = age;
}
이렇게 구성 하실 수 있는데 이러면 데이터 변경에 대한 업데이트가 이루어 지지 않습니다.
state 객체가 통으로 바뀌어야 업데이트가 이루어 지게 됩니다.
이렇게 통으로 업데이트 하기 위해서는 copyWith 이란 함수를 만들어 줍니다.
Human copyWith({int? age, String? name}) {
return Human(age: age ?? this.age, name: name ?? this.name);
}
이렇게 되면 둘중에 하나의 변수만 넣게되도 Human 객체가 새로 만들어 지게 됩니다.
이제 provider의 전체모습을 보면 다음과 같습니다.
import 'package:awesomeday/data/human.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class HumanNotifier extends Notifier<Human> {
@override
Human build() {
return Human();
}
void setAge(int age){
state = state.copyWith(age: age);
}
}
final humanProvider = NotifierProvider<HumanNotifier, Human>(HumanNotifier.new);
build 함수에서는 Human 객체의 기본값을 정의 해 줍니다.
setAge 함수에서는 나이가 업데이트 될때 처리를 해 줍니다.
이제 값을 읽어올 때는 다음과 같이 해줍니다.
var readHuman = ref.read(humanProvider);
var watchHuman = ref.watch(humanProvider);
크게 2가지 방법이 있는데 read 와 watch 입니다.
read 는 1회성 읽기 입니다.
1번 읽고 추가로 읽지 않습니다.
ConsumerWidget과 같이 구성하거나 혹은 업데이트가 크게 필요 없는 값에서 구성합니다.
watch 는 지속적인 갱신입니다.
provider의 state가 갱신되면 watch 에서는 데이터 변경을 감지하고 위젯을 다시 그려버립니다.
보통 ConsumerStatefulWidget의 build 함수내에서 watch를 하고 이에 따라서 build 함수 자체가 다시 호출되는 구조가 됩니다.
만일 Human 을 coptyWith 방식과 같이 구성하지 않았다면 watch를 하더라도 위젯 리빌드가 일어나지 않아서 riverpod 의 제대로된 효과를 얻을 수 없습니다.
이러한 점 잘 사용하시고 사용하시면 됩니다.
'Flutter' 카테고리의 다른 글
[Flutter]Cafe24 Oauth2 - 1 (2) | 2024.08.21 |
---|---|
[Flutter]기본 애니메이션 (1) | 2023.09.17 |
Flutter - 이미지 선택 (1) | 2022.10.11 |
Flutter - 파파고 번역 + 비동기 처리 (1) | 2022.07.28 |
Flutter - Asset 사용(Font, Image) (1) | 2022.07.14 |