Flutter 상태관리 패키지

슬로우플랫폼·2024년 5월 17일
0
post-thumbnail

안녕하세요, Slow Platform에서 Flutter 앱 개발을 맡고 있는 Eden입니다.

Flutter의 상태 관리를 더 효율적으로 하기 위해서 상태관리 패키지에 대해서 정리해봤습니다.

상태 관리란?

상태관리는 애플리케이션의 데이터나 UI 상태를 관리하는 것을 의미합니다. 앱의 데이터 흐름을 관리하고 UI를 업데이트하는데 필요합니다. 상태관리는 앱의 규모가 커지고 복잡해질수록 더 중요합니다.

상태관리는 선언형 프로그래밍 방식에서 매우 중요합니다.

  • 명령형 프로그래밍 vs 선언형 프로그래밍 차이

명령형 프로그래밍

대표적으로 C, Java, Python과 같이 프로그램이 어떻게 실행되어야 하는지 명시하는 방식
코드는 명령어의 연속으로 구성되며, 명령어는 컴퓨터가 실행할 단계별 작업을 정의한다.
명령형 프로그래밍은 코드가 실행되는 순서가 중요하며, 개발자는 프로그램의 상태를 명령어로 변경하고 관리한다.

선언형 프로그래밍

대표적으로 Android Compose, Flutter, iOS Swift, SQL과 같은 DB 쿼리 언어 등이 있다.
프로그램이 어떻게 실행되어야 하는지를 명시하는 것이 아니라, 원하는 결과를 명시하는 방식으로 프로그래밍한다.
명령형 프로그래밍의 반대 개념으로 코드가 어떻게 동작하는지가 아니라 무엇을 원하는지에 중점을 둔다.

현재 안드로이드의 경우 선언형 프로그래밍(Compose)으로 넘어가는 추세

Flutter의 UI 구성 방식은 선언형 프로그래밍 방식으로 앱의 상태를 반영하여 UI를 build 합니다.

즉, 명령형 UI와 다르게 선언형 UI는 UI를 강제적으로 변경할 필요 없이 앱의 상태만 변경해 주면 됩니다.

그러면 UI를 변경하기 위해 자체적으로 rebuild를 트리거하여 새로운 위젯 하위트리를 구성합니다.

상태관리 패키지를 사용해야 하는 이유

  • 복잡한 애플리케이션 상태 관리 - 사용자 입력, 네트워크 요청, 로컬 데이터 변경 등 다양한 요인에 의해 상태가 변할 수 있습니다. 상태 관리 라이브러리를 사용하면 이러한 복잡한 상태를 구조화하고 관리할 수 있습니다.
  • UI 업데이트 관리 - UI는 애플리케이션 상태에 따라 동적으로 변경되어야 합니다. 상태 관리 라이브러리는 상태가 변경될 때 UI를 업데이트하는 방법을 추상화하고 단순화합니다. 이를 통해 개발자는 UI 업데이트에 집중할 수 있으며, 불필요한 코드를 줄이고 코드의 가독성을 향상할 있습니다.
  • 비동기 처리 - 네트워크 요청, 데이터베이스 액세스, 파일 I/O 등의 작업은 모두 비동기적으로 처리되어야 합니다. 상태 관리 라이브러리는 비동기 작업의 결과를 쉽게 처리하고 상태를 업데이트하는 데 도움을 줍니다.
  • 모듈성 및 유지보수성 - 라이브러리는 일반적으로 상태 및 관련 로직을 분리하고 추상화하여 모듈성을 촉진합니다. 이를 통해 코드의 재사용성을 높이고 유지보수성을 향상시킬 수 있습니다.

상태관리 패키지

GetX

장점

  • pub.dev에서 현재 ‘좋아요’ 수가 전체에서 제일 많은 패키지입니다.
  • 다른 상태관리 패키지에 비해 코드양이 적은 편입니다. 즉, 러닝 커브가 낮은 편이라고 할 수 있습니다.
  • 쉽고 직관적인 코드는 개발 속도를 높일 수 있습니다.

단점

  • BuildContext를 참조하는 방식이 아니기에 의존성 주입, 변경에 어려움이 생깁니다.
  • 패키지의 의존성이 커진다. 즉, Flutter 개발을 하는 데 있어서 GetX라는 도구에 지나치게 의존하는 경향이 있다.
  • GetX로 상태 관리하는 프로젝트를 다른 상태관리 패키지로 변경하려고 하는 경우 리소스가 많이 들 수 있습니다.
  • 많은 기능들이 있습니다. 필요로 하지 않는 기능들도 포함되어 있어 패키지 자체가 무겁습니다. 상태관리 외에 Status Management, Route, Utils Widget, Http request 등 다양한 기능이 있습니다.
  • 이슈가 많고 대응이 느리다. 마지막 릴리즈가 2021년 12월 14일 이고, 언제 지원중단이 되어도 이상하지 않습니다.

Provider

  • pub.dev에서 현재 ‘좋아요’ 수가 전체에서 두 번째로 많은 패키지입니다.

Provider에서 업데이트를 선택하지 않고 새 패키지인 Riverpod를 만든 이유

  1. 런타임에 ProviderNotFoundException이 호출 되는 문제
  2. Provider를 더 이상 사용하지 않을 때 수동으로 dispose 해야 하는 문제
  3. 다른 Provider를 의존하는 복잡한 Provider를 생성할 수 없는 문제

Provider의 단점이 너무나 명확하고 이 단점들을 개선한 Priverpod가 있기 때문에 Provider를 크게 다루지 않고 상태관리 패키지 선택함에 있어서 제외했습니다.

Bloc (with Cubit)

BLoC = BussinessLogic Component

장점

  • 개발자가 많은 큰 규모 프로젝트에서는 다른 상태관리 패키지보다 정형화된 Boilerplate를 가지고 있어 코드를 이해하기에 수월합니다.

단점

  • 상태 그룹별로 Cubit을 만들어야 하고, Cubit 간 데이터를 공유하는 경우에는 추가로 Repository가 필요하다.
  • 초기 러닝 커브가 높은 편이고, 규모가 작은 프로젝트에서는 보일러 플레이트 코드가 많아 생산성이 저하된다.

Clean Architecture 기준으로 최상위 계층에서 application 디렉터리를 생성하여 Bloc, Cubit을 관리합니다.

Bloc는 특정 이벤트에 대한 스트림을 구독하고 언제든지 데이터를 받을 수 있는 구조로 구성되어 있습니다.

이 상태에서 비즈니스 로직과 상태 관련 처리는 Bloc 안에서 처리가 됩니다. Bloc에서는 데이터를 처리하고 스트림으로 전송하는 역할만 수행하면 됩니다.

Bloc의 흐름

Bloc에서 각 UI 객체들은 Bloc 객체를 구독합니다.

Bloc 객체의 상태가 변동되면 상태를 구독 중인 UI 들은 그 즉시 해당 상태로 변경합니다.

Bloc 객체는 UI로부터 이벤트를 전달받으면, 필요한 데이터를 받아와 Business Logic을 처리합니다.

Business Logic을 처리한 후 Bloc 객체를 구독 중인 UI 객체들에 상태를 전달합니다.


Riverpod

  • Provider의 단점을 개선한 버전입니다. (동일한 개발자)
  • 또한 Flutter에서 공식적으로 Provider를 쓰라고 권장했으므로, Provider 기반으로 만들어진 Riverpod도 공식적으로 권장하다고 볼 수 있습니다.

장점

  • Provider의 단점으로 꼽히는 BuildContext 의존하는 방식을 BuildContext 없이 Ref를 사용해서 상태에 접근합니다.
  • 위젯을 Rebuild 시 상태가 손실될 걱정 없이 create, observe, dispose를 할 수 있습니다.
  • lazy loading을 지원하여 사용자가 사용하지 않는 Provider를 당장 로딩하지 않습니다.
  • 단방향의 데이터 흐름으로 앱의 확장성을 높일 수 있습니다.

단점

  • GetX보다 배울 것이 많아 생산성과 시간 측면에서 밀립니다.

Riverpod에는 여러 가지의 Provider가 있습니다.

  • Provider - 가장 기본이 되는 Provider로 단순히 값을 읽을 수만 있다.
  • StateNotifierProvider - 상태를 알려주는 StateNotifier의 상태 변화를 관찰하다가 변경된 상태를 알려줍니다. 사용자가 상호 작용이나 이벤트로 계속해서 변화하는 상태를 관리할 때 사용합니다.
  • StateProvider - StateNotifierProvider보다 단순한 상태 관리입니다.
  • FutureProvider - Provider와 같은 역할을 수행하지만 비동기 처리가 가능합니다. 또한, 네트워크 요청, 파일 입출력, 데이터베이스 입출력 등에 사용합니다.
  • StreamProvider - FutureProvider와 유사하지만, Steam 처리에 유용합니다.

ref 사용 방법

  • ref.watch - Provider를 구독해 변화를 관찰하고, 값이 변경되면 위젯을 다시 Build 하거나 구독하고 있는 Provider의 상태를 전달합니다. 상태 변화를 화면에 즉각 반영해야 할 때 사용합니다.
  • ref.read - Provider의 상태를 가져오고 값을 변경할 수 있습니다. 일반적으로 사용자와 상호작용으로 발생할 수 있는 트리거 함수에서 사용합니다.
  • ref.listen - watch와 동일하게 변화를 관찰할 수 있지만 위젯을 Rebuild 하거나 상태를 전달하지 않고 값이 변경될 때 정의한 함수를 실행합니다. 예를 들면 SnackBar, Dialog를 처리하는 데 유용합니다.

라이브러리 별 차이

라인 개발 블로그에서 참고해 만들었습니다.

GetXProviderBLoCRiverpod
화면에서 원하는 부분만 업데이트하는 것이 간편한가?O (Rx, Obx를 이용해 가장 편리하게 지원)O
BuildContext를 전달하는 번거로움이 없는가?O (전역 변수 사용하듯 편하게 접근 가능)XXO
라이브러리 사용법을 배우기 쉬운가?OX
뷰모델 작성이 간편한가?OOX (상태 그룹별로 Cubit을 만들어야 함)O
BuildContext를 이용한 의존성 주입을 사용할 수 있는가?X(위험 요소)OOO
뷰모델에서 일회성 이벤트(경고창 생성 같은 이벤트)를 발행할 수 있는가?XXOO


결론

GetX 는 마지막 릴리즈가 1년 6개월 정도 됐고, 많은 이슈들이 해결되지 않고 사용되고 있다. 그래서 언제 사라질지 모르는 두려움을 가지고 사용해야될 것 같다.

Provider는 Riverpod가 나온 이후로 Provider를 만든 제작자 Remi가 곧 Provider는 Deprecated 될 수 있다고 선언 했다.

BLoC의 경우 초기 러닝커브가 높고 개발자가 많을 때 효율이 더 증대될 것으로 추측된다.


궁극적으로 지금의 경우에는 Riverpod를 선택해서 진행하는게 합리적이라고 생각이 들고, 추후 팀 내에 개발자가 많아지면서 프로젝트의 규모가 방대해지는 경우 상태관리 패키지를 직접 만들거나, BLoC로 Migration 하는 것이 합리적일 것이라고 생각합니다.


Reference

  1. https://engineering.linecorp.com/ko/blog/flutter-architecture-getx-bloc-provider
  2. https://riverpod.dev/ko/
  3. https://bloclibrary.dev/#/
  4. https://velog.io/@dyddn2015/네이티브-앱-플러터로-바꾸기8-기술적-결정사항2
  5. https://blog.arong.info/flutter/2023/01/10/Flutter-BLoC-패턴-1.html
  6. https://techblog.uplus.co.kr/플러터-riverpod로-상태관리-하기-cd164f0644e5
profile
슬로우플랫폼 기술블로그입니다.

2개의 댓글

comment-user-thumbnail
2024년 5월 17일

해외에서도 보통 Bloc VS. RiverPod 두 개 체제로 가는 거 같더라고요. ㅎㅎ Dart 초보 두 명밖에 없는 상황에서 우당탕탕 BloC 공부해 가면서 구현하던 게 생각나네요 초기 러닝커브가 높긴 했던 것 같아요. 글 잘 읽었습니다! 기술을 선택하는 과정이 엿보여서 많은 도움이 되었습니다!

답글 달기
comment-user-thumbnail
2024년 12월 10일

감사합니다 잘 읽었습니다

답글 달기

관련 채용 정보