Flutter 상태관리 라이브러리 직접 만들어 보기

으라차차·2023년 7월 4일
2
post-thumbnail

이 글은 medium에 포스팅된 글(Create a Flutter state management library with 100 lines of code을 번역하였습니다.

flutter의 상태관리 라이브러리들이 어떻게 동작하는지 이해에 큰 도움이 되리라 생각됩니다.


0. 들어가면서

상태관리는 flutter 커뮤니티에서는 매우 관심 많은 주제입니다. 좀 지난 통계이긴 하지만 Reddit에 따르면 우리가 선택할 수 있는 관련 라이브러리가 42개나 된다고 하네요.

각 라이브러리 모두 많은 사용자가 있고 훌륭한 솔루션들입니다. 하지만 어떤 것이 자신의 프로젝트에 가장 적합한 지 초보자가 판단하기란 쉽지 않습니다.

이번 글을 통해, 자신만의 상태관리 라이브러리를 처음부터 만들어 보겠습니다. 마술을 보여드리는게 아니닙니다. 아주 간단합니다. 일단 만들어 보면, "이렇게 간단한거 였어?"라며 놀랄 정도고, 다른 상태관리 라이브러리들이 어떻게 동작하는지 이해하는데 큰 도움이 됩니다.

100줄 정도의 코딩으로 완벽히 동작하는 상태관리 라이브러리를 만들 수 있습니다.

만든 라이브러리를 통해 counter, todo 앱을 만들 수 있고 App Theme를 동적으로 바꾸거나 그 외 다른 많은 일들을 할 수 있습니다. Provider나 Bloc 이용해서, 버튼 Widget을 누르면 다른 위치 또는 다른 화일에 있는 Widget의 text 내용이 업데이트 되도록 할 수 있습니다.

1. 상태변화 : 상태 + 전파

상태관리 라이브러리를 이해하는데 있어 가장 중요한 점은 "상태 변화는 어떻게 전파되는가"를 이해하는 것입니다.

우선 몇가지 사례를 보도록 하죠.

  • Stream을 사용하는 방법 : flutter_bloc, flutter_redux, getx
  • Flutter의 InheritedWidget을 활용하는 방법 : provider
  • 개량형 구독모델을 사용하는 방법 : riverpod

먼저, counter app을 생각해보죠. 무엇이 필요할까요? 아마 아래 정도가 아닐까요?

  • counter(숫자)를 담을 공간
  • counter를 수정하면 counter의 값을 표시하는 Widget도 변경해야겠죠. Text('0') 👉 Text('1') 이렇게요.

의존성과 의존성 그래프

위 내용을 보고 아래 그림이 수긍이 가신다면 여러분은 가장 중요한 점을 벌써 이해하신 겁니다! 바로 "의존성 dependency" 입니다.

그헐다면 의존성을 표현하는 가장 단순한 방법은 무엇일까요? 바로 "의존성 그래프 dependency graph"입니다. 보통 그래프는 노드(node)와 연결선(edge)로 구성됩니다.

  • 노드 node는 상태(숫자, 문자열, 객체, Widget 등등)를 표현합니다.
  • 연결선 edge은 "상태 변화""누구"에게 전달 되는지를 표현합니다.

위 그림은 Counter 노드(int) 1개, Widget 노드(Widget) 1개를 갖고 있고, Counter 노드 숫자가 바뀌면 Widget(Text Widget)의 내용도 연이어 바뀌는 의존 관계를 그래프로 표현한 것입니다. 즉, Counter(int)의 상태가 0에서 1로 바뀌면 의존성이 있는 Widget 노드는 Text('0')에서 Text('1')로 바뀌는 것이죠.

바로 여러분이 상태관리 라이브러리를 사용할 때, 상태변수를 업데이트하면 관련 Widget도 함께 변하는것을 보셨을텐데요, 위 과정이 딱 그 경우입니다.

3. 상태관리 라이브러리 만들기

자 그렇다면, 위 내용으로 어떻게 우리 자신만의 상태관리 라이브러리를 만들수 있을까요?

이제부터 노드를 "Creator", 그래프를 "Ref"라고 부르겠습니다. 뭐.. 완전히 다른 이름을 사용할 수도 있습니다. 아래는 그래프 Ref의 정의입니다.

참고로, Set<> 클래스가 보이는데요. 우리가 학교에서 배운 집합 Set과 유사한 개념을 Dart에서 기본 클래스로 만들어 둔 것입니다.
임의의 자료형 객체들을 중복없이 빠르게 저장하고 관리할 수 있는 클래스입니다.

Set<>을 사용하는 이유는 특정 노드는 복수의 노드들이 의존할 수 있기때문입니다.

노드 Creator는 어떻게 정의할 수 있을까요? creator A가 다른 creator B의 상태에 의존해야 한다면, B creator에게 A Creator의 Ref를 제공하면 되겠네요.

그리고 유저가 creator 상태를 직접 조작할 수 없도록 하기 위해 creator는 immutable로 만드는게 좋겠습니다. 다른 private 객체인 element를 만들어 그곳에 유저가 만든 상태를 담아두고요. 플러터의 StatefulWidget를 만드실 때 많이 보셨죠^^. 이때 만들어지는 상태는 element이고, 유저가 제공하는 "create" callback 함수의 결과값으로 초기값을 만듭니다. 갑자기 조금 복잡해 졌지만 바로 뒤 설명으로 이해가 되실 겁니다.

여기까지 내용을 이용해서 counter app을 만들어 보겠습니다. 벌써?😅

[1] 우선 노드를 만들어야 겠죠. 이전 내용에서 counter app은 두 개의 노드가 필요 했었죠? "Counter""Text"요.

노드를 만들때 생성자 함수의 parameter로 초기값을 만드는 callback을 전달한다고 했죠.

[2] 노드들을 만들었으면 이젠 그들간에 의존성 counter 👉 text을 만들어 보겠습니다. Ref.watch method를 통해 구현해 보도록 하겠습니다. 이렇게 하면 하나의 creator를 여러 개의 creator들이 의존하도록 할 수도 있습니다. 즉, 한 노드가 변경되는 여러 노드들이 함께 수정되는거죠. 많이 보셨죠?

그리고, Element가 여러분이 만든 상태를 담을 수 있도록 Ref를 수정해 보도록 하겠습니다.

다음, creator의 상태를 수정했을 때 상태를 전파하는 API를 만들어 봅시다.

Element의 recreate() 구현도 잊지맙니다. 내가 바뀌었으니 나를 의존하는 모든 노드들의 상태도 바꾸고, 연쇄적으로 해당 노드들을 위존하던 노드들의 상태로 수정합니다.

좋습니다. 이제 상태가 바뀌면 바뀐 상태를 전파할 수 있는 의존성 그래프가 만들어 졌습니다.

이제 작동하는지 확인해 볼까요? 아래 코드를 dartpad에 넣고 실행해보세요.


4. 라이브러리 사용

이젠 의존성 그래프를 Flutter Widget에서 사용해 보도록 하겠습니다. 세가지 정도 수행해야 하는데요.

  • Widget이 의존성 그래프에 접근할 수 있도록 해야 합니다.
  • 의존성 상태가 변하면 Widget도 수정(rebuild)되어야 합니다.
  • 더이상 필요없는 노드는 자동으로 해제되도록 해야 합니다.

자 해봅시다.

1. Widget이 의존성 그래프를 접근할 수 있도록 해야 합니다.
여러분이 이미 InheritedWidget을 알고 계신다면 쉽습니다.

여러분의 app을 CreatorGraph로 감싸면 context.ref로 Ref를 접근할 수 있습니다.

2. 의존성 상태가 변하면 Widget도 수정(rebuild)되어야 합니다.
우리의 counter app은 Creator가 필요하다는 것을 기억하세요. 이것을 StatefulWidget에 넣어봅시다. 이제 의존성 상태가 변경되면 setState만 하세요. 나머지는 flutter가 알아서 합니다.

3. 더이상 필요없는 노드는 자동으로 해제되도록 합시다.
위코드에서는 widget이 dispose될 때 ref.dispose가 호출됩니다. 더이상 의존하는 노드가 없다면 그 노드는 dispose할 필요가 있습니다.

어떤가요? 모두 끝났습니다. 이제 counter app을 만들어 봅시다. dartpad에서 실행해보세요.


우리가 만든 패키지가 단순해 보여도 완벽하고 매우 우아하게 동작합니다.

몇몇 샘플은 아래를 참고하세요. App Theme의 컬러를 동적으로 바꾸거나 여러분만의 todo app을 만들어 보실수 있습니다.


5. 추가할 사항들

지금까지 즐겁게 보셨나요? 물론 상용수준까지는 여전히 많은 것들이 필요합니다. 하지만 지금은 거의 70% 수준은 됩니다.

물론 추가할 기능들이 더 있습니다.

  • 표기법에서 self 없애기: Creator((ref, self) => ref.watch(number, self) 2 이것은 Creator((ref) => ref.watch(number)2 이렇게 하는것이 더 안전하고 읽기 좋습니다.
  • 아래와 같이 동적 의존성 제공
    final C = Creator((ref) {
    			final value = ref.watch(A);
    			return value >= 0 ? value : ref.watch(B);
    		});
  • Creator<Future>을 개선하기. 지금은 설령 같은 T를 만들더라도 Future가 "상태 변경"용으로 여겨집니다.

그리고 몇가지 작업이 필요하기도 합니다.

  • CreatorGraph 조차도 dispose 될 수 있도록 하기
  • Watch는 현재 다소 중복된 build 호출을 만들어냅니다.
  • logging, 오류처리, 테스팅 등등

자.. 이제 뭘 해야할까요? 다음 게시물을 차분히 기다려 주세요~~
아니면 다음 기사? https://github.com/terryl1900/creator

그리고 미리 말하지 못해 미안하지만, 저는 이번에 새로 만들어진 상태 관리 라이브러리의 개발자입니다. 이 글은 제가 상태 관리에 대해 어떻게 생각하는지 그리고 라이브러리의 첫번째 버전을 어떻게 만들었는지 보여줍니다.

핵심 라이브러리는 고작 500라인 정도 밖에 안됩니다. 이 글을 읽고 이해가 되었다면 여러분은 라이브러리 소스코드를 아주 빨리 살펴볼 수 있을겁니다.시도해 보시고 여러분이 생각하는바를 알려주세요.

다음으로 다른 상태 관리 라이브러리를 골라서 소스코드를 살펴보세요. 그때는 다음을 염두해두고 시도해 보세요.

"상태"가 어디에 저장되어 있고 상태변화가 어떻게 "전파"되는지요..

.끝.

profile
만만세~

0개의 댓글