Flutter 개발 상자

[Flutter] Riverpod의 Provider에서 BuildContext를 매개변수로 받으면 안되는가? 본문

Flutter

[Flutter] Riverpod의 Provider에서 BuildContext를 매개변수로 받으면 안되는가?

망고상자 2024. 1. 22. 16:54
728x90

순수UI 코드(View)와 UI상태&로직(ViewModel)의 분리

 

플러터는 기본적으로 stf위젯, UI안에 상태가 담겨져 있는 구조입니다.

하지만 이는 좀 불편합니다. 아무리 위젯단위로 쪼개고 또 쪼개도 분명 하나의 위젯에 여러가지 복잡한 상태와 로직들이 들어갈 수 밖에 없는 구조가 나올 수 있습니다.

 

우리는 이를 확실하게 구분하고 싶습니다.

하지만 Riverpod에서는 TextEditingController나 BuildContext는 UI 위젯쪽에서만 다루라고 합니다.

 

 

안드레아의 철학을 따라서 약간의 편의성을 얻자면...

 

https://codewithandrea.com/tutorials/

 

안드레아는 Riverpod을 사랑하는 플러터 블로거 및 강의 제작자입니다.

리버팟의 문서가 부족할때부터 안드레아는 이해하기 쉬운 다양한 리버팟 활용글을 작성해주어서 플러터 업계에서는 꽤 유명한 강사로 손꼽힙니다.

 

안드레아가 주장하는 플러터의 아키텍쳐는 다음과 같습니다.

 

 

자 여기서 주목해야할점은 프레젠테이션 레이어 영역의 Controller라는 영역입니다.

저 Controller는 안드레아의 게시물을 살펴보면 알 수 있지만 기존의 MVVM패턴의 VM에 해당하는 영역입니다.

저곳에서 UI의 상태를 저장하고 로직을 실행시키며 Controller는 보통 Riverpod NotifierProvider로 구현합니다.

 

하지만 안드레아 역시 Provider 영역에서 BuildContext를 사용하면 안된다는걸 잘 알고 있습니다.

그런 고민때문에 나온게 바로 아래의 글입니다.

 

https://codewithandrea.com/articles/flutter-navigate-without-context-gorouter-riverpod/

 

 

위 내용은 goRouter 이용하기 위해 context를 사용해야할때 발생하는 문제를 적은 내용입니다.

저렇게 비즈니스 로직을 실행하고 그 결과값을 받아서 context를 사용하여 UI 처리를 해야할 경우 무턱대고 UI코드라고 UI영역에 코드를 작성해버리면 순수 UI 로직코드와 비즈니스 로직이 뒤섞여서 가독성이 떨어집니다.

 

 

이에 안드레아는 3가지 방법을 제안합니다.

1. Provider에 BuildContext를 전달

2. Callback 사용

3. ref.read로 goRouter 정보 얻기

 

자세한 내용은

https://codewithandrea.com/articles/flutter-navigate-without-context-gorouter-riverpod/

 

How to Navigate Without Context with GoRouter and Riverpod in Flutter

Let's explore some techniques to separate our business logic and navigation code from the UI, using GoRouter and Riverpod.

codewithandrea.com

 

링크에서 직접 보시는걸 추천드립니다.

 

일단 2번의 방법은 비추천합니다.

가독성을 얻기위해 가독성을 해쳐버리는(?) 방법입니다.

저는 저렇게 콜백을 쓰느니 아예 UI단에서 함수를 따로 빼고 코드를 다 집어넣는걸 더 선호합니다.

 

3번의 방법은 좋은 방법이지만 결국 한계가 있습니다.

1. 위의 글에서도 나온건데 Future 이후에 context가 유효한 상태인지 정확하게 판단하기가 어렵습니다. context.mounted를 사용할 수 없기 때문

2. 고라우터는 저렇게 한다고 치고 다른 context가 필요한 다국어작업이나 간단한 다이얼로그를 띄우는 작업은 처리할 수 없습니다.

 

네. 그래서 저는 그냥 과감하게 1번의 방법

Provider에 BuildContext를 직접 전달하는것을 좋아하는 편입니다.

 

단, 두가지 원칙을 무조건 지킵니다.

 

1. BuildContext를 받는 NotifiderProvider는 'Controller' 라는 접미사를 붙여줍니다.

2. 이 Controller는 위젯과 1:1로 대응해야하며, 위젯과 동일한 생명주기를 가져야 합니다. (keepAlive 옵션을 사용하지 않기)

 

 

이렇게 하면 위젯에서 context를 받더라도 안전한 코드를 짤 수 있고

UI코드와 비즈니스 로직 코드를 완전히 분리할 수 있습니다.

일종의 ViewModel을 만들었다고 보면 됩니다.

 

만약 이렇게 하지 않을경우

 

UI쪽에서는 조금만 복잡한 로직이 들어오면 콜백 지옥에 시달리게 되고 코드를 처음 보는 사람 역시 코드의 흐름을 파악하기가 어렵습니다.

 

 

그리고 한가지 재밌는것은 UI단에서 context를 사용할때도 비동기 작업이 들어가면 무조건 context.mounted를 체크해주어야한다는 것입니다.

 

저는 이것에 굉장한 회의감을 느꼈습니다. UI단에서 context를 쓰는건데 이게 살아있는지 죽어있는지를 검증을 해야하네?

그럼 이 context를 다른곳으로 들고가서 mounted 검증하고 쓰는거랑 뭐가 다른거지? 라는 생각이 들었죠.

 

 

그래서 저는 안드레아의 아키텍쳐를 따라서 Controller라는 ViewModel을 만들어서 과감하게 context를 전달해주고 있습니다.

물론 절대 남용하고 있진 않습니다. 어찌됐건 플러터는 화면단위로 코드를 짜는것보단 위젯하나하나 단위로 쪼개서 코드를 짜는게 이상적이기에 쪼갤 수 있는건 최대한 쪼개고, 그래도 Controller 가 필요하다면 만들어주는 편입니다.

 

 

결론

 

일단 제목에 대한 답은

 

'권장하지 않는다.' 입니다.

 

Bloc과 Riverpod 모두 context는 UI영역에서만 사용해야한다고 고지하고 있고, 실제로 riverpod_lint에서도 그렇게 쓰면 안된다고 경고합니다.

 

하지만 안정성에 문제가 없다면 가장 좋은 코드는 가독성이 좋은 코드라고 생각합니다.

BuildContext를 넘겼을때 발생하는 문제가 없고, 가독성을 향상시킬 수 있다면 NotifierProvider에 전달하는건 나쁘지 않은 선택이라고 봅니다.

 

 

 

728x90