[flutter] 플러터 Riverpod와 메인 스레드 관리 방법

플러터는 크로스 플랫폼 모바일 앱 개발을 위한 인기있는 프레임워크입니다. Flutter는 UI를 빌드하고 상태를 관리하는 데 있어서 많은 도구와 라이브러리를 제공합니다. 그 중 하나가 Riverpod입니다.

Riverpod는 상태 관리 라이브러리로서, 의존성 주입 및 상태 공유를 간단하게 만들어줍니다. 그러나 Riverpod를 사용할 때 주의해야 할 중요한 사항 중 하나는 메인 스레드 관리입니다.

메인 스레드란 무엇인가요?

메인 스레드는 앱의 UI와 관련된 작업을 처리하는 스레드입니다. 사용자 입력 처리, UI 업데이트 등과 같은 작업은 모두 메인 스레드에서 수행되어야 합니다. 이는 앱의 반응성과 성능에 중요한 역할을 합니다.

Riverpod와 메인 스레드

Riverpod는 상태 관리를 위해 ProviderConsumer를 사용합니다. 그러나 Riverpod의 상태 변경이 발생할 때, 변경된 상태를 UI에 반영하기 위해서는 메인 스레드에서 이루어져야 합니다.

일반적으로 UI 업데이트는 build 메서드 내에서 이루어집니다. Provider를 읽고 상태를 업데이트하는 코드 역시 build 메서드 내에서 이루어집니다. 이 때문에 Riverpod를 사용할 때 주의해야 할 점은, 변경된 상태를 build 메서드 외부에서 액세스하는 것입니다.

예를 들어, 다음과 같은 코드를 생각해보겠습니다.

final countProvider = Provider<int>((ref) => 0);

class Counter extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final count = watch(countProvider);

    return Text('Count: $count');
  }
}

class IncrementButton extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final count = watch(countProvider);

    return RaisedButton(
      onPressed: () {
        // 상태 변경
        context.read(countProvider).state++;
      },
      child: Text('Increment'),
    );
  }
}

위의 코드에서 Counter 위젯은 count를 읽고, IncrementButton 위젯은 count를 변경합니다. IncrementButton 위젯의 onPressed 메서드에서 상태가 변경되는데, 이 때 build 메서드 외부에서 상태 변경이 일어나게 됩니다.

이는 문제가 될 수 있습니다. 왜냐하면 Provider는 이전 상태와 새로운 상태를 비교하여 UI를 업데이트하는데, 이 과정은 메인 스레드에서 수행되어야 합니다. 따라서 메인 스레드에서 이루어지지 않는 상태 변경은 예측할 수 없는 결과를 낳을 수 있습니다.

메인 스레드 관리 방법

메인 스레드에서 상태 변경이 필요한 경우, Future.delayedSchedulerBinding.instance.addPostFrameCallback를 사용하여 build 메서드가 실행된 후에 상태를 변경하도록 할 수 있습니다.

예를 들어, 위의 코드에서 IncrementButton 위젯의 onPressed 메서드를 다음과 같이 변경할 수 있습니다.

// ... 이전 코드 생략

class IncrementButton extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final count = watch(countProvider);

    return RaisedButton(
      onPressed: () {
        Future.delayed(Duration.zero, () {
          // 상태 변경
          context.read(countProvider).state++;
        });
      },
      child: Text('Increment'),
    );
  }
}

위와 같이 Future.delayed(Duration.zero, ...)를 사용하면, build 메서드가 실행된 후에 상태를 변경할 수 있습니다. 이를 통해 메인 스레드에서 안전하게 상태를 변경할 수 있습니다.

또 다른 방법은 SchedulerBinding.instance.addPostFrameCallback를 사용하는 것입니다. 이 방법은 build 메서드가 실행된 후 한 번만 호출되는 콜백 함수를 등록할 수 있습니다.

// ... 이전 코드 생략

class IncrementButton extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final count = watch(countProvider);

    return RaisedButton(
      onPressed: () {
        SchedulerBinding.instance.addPostFrameCallback((_) {
          // 상태 변경
          context.read(countProvider).state++;
        });
      },
      child: Text('Increment'),
    );
  }
}

addPostFrameCallbackbuild 메서드가 실행된 후에 한 번 호출되므로, 메인 스레드에서 안전하게 상태를 변경할 수 있습니다.

결론

Riverpod를 사용할 때, 메인 스레드를 적절히 관리하는 것이 중요합니다. UI 업데이트와 상태 변경은 메인 스레드에서 수행되어야 하며, build 메서드 외부에서 상태 변경이 발생할 때는 Future.delayedaddPostFrameCallback를 사용하여 메인 스레드에서 다루도록 해야 합니다.

위의 방법을 따르면서 Riverpod를 사용하여 효율적이고 안정적인 상태 관리를 할 수 있습니다.