Flutter로 개발을 하다 보면 가장 먼저 부딪히는 선택지가 있다. 바로 상태 관리(State Management) 를 어떻게 할 것인가이다. 상태 관리는 앱의 데이터 흐름과 UI 업데이트를 결정하는 중요한 요소인데, 어떤 라이브러리를 선택하느냐에 따라 프로젝트 구조와 생산성이 크게 달라진다.
처음 Flutter를 접했을 때는 대부분 단순한 앱을 만들기 때문에 setState 만으로도 충분하다. 하지만 앱이 커지고 비즈니스 로직이 복잡해지면 상태 관리 도구의 필요성이 커진다. 나 역시 프로젝트 초반에는 GetX를 선택했다. GetX는 문서가 잘 되어 있고, 코드도 짧으며, 빠르게 결과물을 만들어낼 수 있어서 입문자들에게 매우 매력적으로 보인다. 특히 Obx, Get.put, Get.find 같은 간단한 문법은 러닝 커브를 거의 느끼지 못하게 만든다.
그러나 프로젝트가 성장하고 팀 단위 협업이 본격화되면서 GetX의 한계가 드러났다. 결국 우리 팀은 Riverpod으로 전환하게 되었고, 지금은 왜 더 일찍 마이그레이션하지 않았을까 하는 생각도 든다. 이 글에서는 그 과정을 단계별로 풀어보겠다.
GetX를 쓰면서 마주친 문제는 단순히 “버그가 생겼다” 수준이 아니었다. 구조적이고 반복적인 문제였다.
Get.find()로 불러온 인스턴스가 어디서 만들어졌고 언제 해제되는지 바로 알기 어렵다. 작은 프로젝트에서는 문제가 없지만, 규모가 커질수록 의존성이 꼬여 디버깅이 힘들어진다.
전역 싱글톤 구조가 많다 보니 Mock을 넣기가 힘들었다. 테스트 코드를 못 쓰니 안정성이 점점 떨어졌고, 결국 배포 전에 QA에만 의존하게 되었다.
한 군데 상태를 수정했는데 다른 화면이 예기치 않게 갱신되는 경우가 많았다. 디버깅할 때 “여기 고쳤는데 왜 저기까지 바뀌지?” 하는 순간이 잦았다.
Observable로 감싸는 방식은 단순했지만, 타입 추론이나 null-safety를 적극적으로 활용하기 어려웠다. IDE가 잡아주지 못하는 오류가 런타임에 터져 나오면서 유지보수 비용이 늘어났다.
이런 문제를 해결할 방법을 찾다가 여러 대안을 검토했다. Bloc, Redux, MobX, Provider 등등. 그중에서 Riverpod이 눈에 띈 이유는 다음과 같다.
IDE 단계에서 잘못된 접근을 잡아준다. 실수로 null을 접근하거나 없는 Provider를 호출하면 런타임까지 가지 않고 개발 단계에서 알 수 있다.
Provider로 어떤 상태가 어디서 어떻게 관리되는지 선언적으로 표현한다. 코드만 봐도 구조가 보이고, 추적이 쉬워졌다.
ProviderScope를 통해 특정 Provider만 교체하거나 Mock으로 대체할 수 있다. 테스트 코드 작성이 자연스러워졌고, 버그를 사전에 잡을 수 있었다.
팀 단위에서 중요한 건 예측 가능성과 가독성이다. Riverpod은 boilerplate가 조금 있지만, 그 대신 구조가 명확하다. 새로운 팀원이 들어와도 상태가 어디서 생성되고 주입되는지 바로 이해할 수 있었다.
Riverpod으로 전환하기로 하기 전까지, 팀 내부에서도 의견이 갈렸다.
이런 우려는 당연했다. 그래서 전면적인 교체 대신 점진적 도입을 선택했다. 새로운 기능을 작성할 때는 Riverpod을 쓰고, 기존 기능은 GetX를 유지하는 방식이다. 실제로 이렇게 병행하다 보니 자연스럽게 팀원들이 Riverpod의 장점을 체감하게 되었고, 나중에는 “왜 이제야 바꿨지?”라는 분위기로 변했다.
Riverpod을 쓰면서 가장 크게 와닿았던 건 테스트 가능성과 예측 가능성이었다. API 연동 로직을 Provider로 분리했더니, 같은 API를 여러 화면에서 공유해도 로직이 흩어지지 않고 한곳에서 관리됐다. 또 Provider를 Mock으로 교체해 단위 테스트를 돌릴 수 있게 되니 QA 단계에서 발견하던 버그 상당수를 개발 단계에서 걸러낼 수 있었다.
신규 기능부터 Riverpod으로 작성하고, 기존 로직은 그대로 두는 방식이 현실적이다.
어떤 경우에 Provider를 만들고, 어떤 경우에 Notifier를 쓸지 컨벤션을 정해두면 협업 효율이 훨씬 올라간다.
GetX는 빠르고 간단하다. 개인 프로젝트나 프로토타입에는 여전히 좋은 선택이다. 하지만 프로젝트가 커지고 팀 단위로 협업을 한다면, 그 단순함이 오히려 발목을 잡는다. 상태 추적이 어렵고, 테스트가 불가능에 가까우며, 사이드 이펙트가 잦다.
Riverpod은 진입 장벽이 있지만, 타입 안전성, 테스트 가능성, 협업 친화성 덕분에 장기적으로는 더 안정적인 선택이다. 우리 팀도 결국 Riverpod으로 넘어오면서 코드 품질이 좋아지고, 디버깅과 테스트 비용이 줄어들었으며, 새로운 기능 개발 속도가 안정적으로 유지됐다.
즉, 작은 프로젝트라면 GetX, 협업을 전제로 한 장기 프로젝트라면 Riverpod이 답이라는 결론에 도달했다.