[Flutter Riverpod] ref.watch에 대해 자세히 알아보자

Yellowtoast·2023년 2월 4일
2

Flutter Riverpod

목록 보기
1/2

해당 문서는 Riverpod 공식 문서에 나오는 watch, read, listen에 대한 설명 중에 나오는 다양한 첨언을 바탕으로 작성된 글입니다.

ref를 사용하여 provider과 상호작용하기

ref로 provider와 상호작용 하는 방법은 3가지가 있다.

1. ref.watch : 상태의 '취득'과 변화를 구독하여 위젯의 재빌드, 상태값 전달을 동시에 - 가장 많이 쓰이는 방법
2. ref.listen : 상태값이 변경되면 특정 행위를 취해야 할 경우 사용
3. ref.read : 상태값을 '취득'하기만 함 - 콜백함수에 유용

ref.watch 같은 경우에는 콜백함수에 사용 불가능함 : 아래에 설명 있음

위의 노트에서, 기능 구현에는 가급적 ref.watch를 사용하라고 되어 있다. 이는 ref.watch가 리엑티브와 선언형에 가까워지고, 앱을 더 유지보수 하기 쉬워진다는 이유이다. 왜 ref.watch는 Reactive, Declarative 하며 그 특성이 어떻게 앱을 유지보수하기 쉽게 만드는 것일까?

ref.watch가 왜 Reactive, Declarative 한가?

선언형(Declarative) 프로그래밍에 간단히 말하자면, 목표를 명시하고 알고리즘을 명시하지 않는 것이다.

이에 반해 명령형 프로그램은 알고리즘을 명시하고 목표는 명시하지 않는다.

  1. Declarative: ref.watch에서는 어떤 상태가 변하였을 때, '어떤 일이 벌어져야 하는지'를 선언하게 되어 있다. 따라서 특정 변화가 어떻게 감지되는지, 어떤 변화가 일어났기 때문에 ui를 어떻게 업데이트 해야 하는지 전 과정을 체크할 필요가 없기 때문이다. 이 때문에 더 분명하고 확실하고, 읽기 쉬운 방식 코드를 짤 수 있게 된다. -> 곧 이는 빠르고 편한 유지보수로 이어진다.
  1. Reactive: ref.watch는, 상태에 따라 앱이 자동으로 reactive하게 변하게 만들어준다. 이 함수는 상태의 변화를 감지하고 그 provider를 구독한 부분들에서 변화에 따라 내부에 선언한 다양한 행위들을 수행하도록 바로 연결시킨다. 이는 코드를 더욱 responsive하고 효과적으로 만들어 준다.

아래의 예시 코드를 한번 살펴보자,

final counter = Ref(0);

counter.watch((previousValue) {
  print('Counter value changed from $previousValue to ${counter.value}');
});

counter.value++;

위의 예에서, counter.watch는 counter가 변화하였을 때 어떤 일이 일어나는지(print문)를 명시하고 있다. 이것은 선언적이면서, 반응적(reative) 한데, 변화를 감지하고, ui를 업데이트 하는 작업을 일일히 체크할 필요가 없기 때문이다.

이에 반해, ref.read 와 ref.listen은 변화를 체크하여 ui를 업데이트 하는 작업을 개별적으로 해주어야 한다. 이는 코드 작성에 들이는 시간 및, 오류 발생률을 높이며 결국 유지보수의 효율성을 떨어트린다.

By using ref.watch, you can focus on what should happen when the value changes, without having to worry about the details of how that change is detected or acted upon.

ref.watch 메소드는 왜 비동기 처리에서 호출해서는 안될까?

  1. Callback called multiple times : ref.watch가 비동기적으로 호출되게 되면, 이전 행위가 끝나기 전에 여러번 콜백 함수가 실행될 수 있다.

아래의 코드를 한번 살펴보자,

final ref = Ref(0);

ref.watch((previousValue) async {
  print('Old value: $previousValue');
  await Future.delayed(Duration(seconds: 2));
  print('New value: ${ref.value}');
});

ref.value = 1;
ref.value = 2;

이 경우 ref.value를 통해 두번의 watch 콜백이 수행된다.
ref.value = 1을 수행하면
첫번째 프린트문
print('Old value: 1'); 이 실행된 후,
2초간 기다리는 동안
다음 ref.value = 2는 기다리지 않고 다시 콜백으로 처들어간다.
그렇게 되면
print('Old value: 2'); 가 실행된다.
이 다음
print('New value: 2');
print('New value: 2'); 가 실행된다.

  1. UI 쓰레드를 방해하게 된다 : ref.watch 콜백 함수가 동기적으로 움직이기 때문에, 만약 이 콜백 함수에 오랜 시간이 걸린다면, ui 쓰레드가 굉장히 느려지거나 응답하지 않는 결과를 초래하게 될 수 있다.
final ref = Ref(0);

ref.watch((previousValue) {
  print('Old value: $previousValue');
  for (int i = 0; i < 1000000000; i++) {
    // Do some long-running computation
  }
  print('New value: ${ref.value}');
});

ref.value = 1;

위의 예처럼, ref.watch이 굉장히 긴 콜백 함수를 실행하여 UI 쓰레드를 오랜 시간동안 막고 있어 결국 앱이 응답하지 않는 결과를 초래하게 될 수 있다.

따라서 ref.watch는 동기적으로, 최대한 빠르고 가벼운 콜백 함수로 구성되어야 한다. 만약 긴 러닝 타임이나 비동기적 처리가 필요하다면, Streams, Future 를 사용하는 것이 좋다.

profile
Flutter App Developer

0개의 댓글