Search
▪️

On Demand Widget Rendering

여러 아이템들 중 보이는 것들만 Rendering 해두는 것은 굉장히 중요하며, 그 보이는 것들 중에서도 모든 항목들이 fetch 될 때까지 기다렸다가Rendering하는 것은 좋은 방법은 아니다. 이런 것들이 중요한 이유는 Render Timing을 줄여야 하는 것에 있다.
즉, 우리가 원하는 것은 보이는 것들만 Fetching하는 것과 각 아이템들이 Fetching되는 즉시 Rendering되는 것이다. 이는 Bloc 작성 시, StreamBuilder를 이용하여 받은 Stream을 통해 Future<Item>을 return하는 FutureBuilder을 이용하고 ListView.builder를 써야 한다.
StreamController와 유사한 역할을 하는 rxdart는 PublishSubject이다. StreamController는 sink와 stream을 모두 관리한다.
x라는 스크립트에 y라는 스크립트를 import 한 후 export y를 하면, x를 import하는 다른 스크립트에서 y를 import하지 않아도 y를 이용할 수 있다.
bloc의 데이터가 바뀌면 re-render되기 때문에, build function에 bloc의 데이터가 바뀌는 것들을 두면 무한히 데이터 변경 → build → 데이터 변경 → build 무한 루프에 빠질 수 있으므로 주의해야 한다.
또한, streambuilder로 listview 구성 시에 각 아이템 역시 streambuilder로 futurebuilder 구성 후 위젯을 만든다고 할 때, listview에 뿌려지는 stream이 순서대로 각 streambuilder를 거쳐서 자기 position이 맞는지 확인하게 된다. 이 과정에서 자기 자신이 아닌 곳에는 initial state를 두기 때문에, 정상적으로 id를 잘 뿌린 것 같아도 rendering이 안 되는 이슈가 있을 수 있다. (Stateless기 때문에 build 한 위젯은 자신의 state를 알고 있을 수 없다는 것이다.)
그렇다면 위의 이슈는 어떻게 고치면 좋을까? → ScanStreamTransformer (Stream으로 출력된 data가 StreamBuilder의 FutureBuilder로 들어가기 전에 transformer을 통해서 해결하면 된다.)
이 transformer에 거칠 때 cache map에 등록하게 한다. 그리고 StreamBuilder가 위젯을 생성할 때 해당 cache map을 조회하여 자신이 build할 data가 있는지, 올바른지 확인하게 되는 것이다.
ScanStreamTransformer의 callback function 첫 인자인 cache는 ScanStreamTransformer의 두번째 인자의 map을 참조하는 인자이다.
callback function 두 번재 인자는 비교 필드의 값으로 들어갈 인자이다.
세번째 인자는 iterating의 index로 사용할 인덱스 인자이다.
하지만 구현된 transformer를 stream.transform으로 이용하여 stream을 받아오려 할 때 주의할 점은 transform function에 들어간 StreamTransformer가 새로운 instance로 계속 생성되면 separate된 새 instance의 cache에서 비교 작업을 하게 되므로 정상 작동하지 않는다는 점이다. 이는 Single Transformer로 적용할 필요 있다. 또한 이는 Bloc의 Constructor을 통해서 해결함 (단, stream은 고정으로 둬야 한다.)
Stream을 이용할 때는 항상 Subscription을 잘 고려하여 이용하여야 한다... 그렇지 않으면 무한적으로 data fetching & rendering이 일어나면서 이상한 state에 빠질 수 있다.
예제의 경우 item이라는 Stream Transformer가 담긴 Stream에서 event를 넘겨주게 되면 각 항목들이 event를 받은 뒤 event가 다시 subscription을 타고 stream에 반영되는 루프 구조이다.
즉, stream이 transformer를 거쳐 subscription을 판별 한 후, Widget에 반영 되면 안 된다.
stream이 각 subscription에 대해 먼저 판별 한 후, transformer을 거쳐 Widget에 반영되는 구조가 되어야 한다.
따라서 item Fetch를 해오는 부분을 따로 두어야 한다. (another stream)
두개의 stream을 묶어서 구현하고자 할 때, 한 stream의 output을 다른 stream의 input으로 넘기고 싶다면, stream.transform.pipe()가 필요하다.
StreamBuilder의 Rendering 이슈를 해결 했다면, database conflict에 대해서도 해결해야 한다.
State를 읽을 때마다 기존 database에 저장한 내용들이 충돌난다.
db에 insert 코드에 특별한 처리가 필요하다. (두 가지 방법 있다.)
1.
conflictAlgorithm 이라는 옵션을 생성한다.
2.
insert 구문에 직접 관여하는 로직을 구현하다.
null check는 중요함 ?? 기호를 적극 활용한다.
Rendering할 때 현재 보이는 것들만 Rendering하는 것도 중요하지만, 모든 데이터들이 다 load 되어야 Render되는 현상도 해결이 필요하다. 이는 현재 보이는 화면의 데이터들만 Fetching하게 해서 이용하는 것이 필요하다.
해결 방법은 간단하다.
ListView 자체가 보이는 것들만 Rendering하기 때문에, ListView안의 요소들을 정적인 높이 값을 할당하게 하면 얼마나 Render해야하고, 얼마나 Fetch 해야 하는지 알 수 있다.
따라서 Stream이 받아지기 전 높이 값이 명확한 Loading으로 쓸 Widget을 먼저 할당하여 자리를 먼저 잡은 후 Stream을 받아서 Widget으로 Rendering하면 주어진 화면 크기에 맞는 개수의 Widget들만 Render & Fetch가 가능하다.
Stream 받아는 왔는데, 해당 Stream의 데이터가 업데이트 되었을 때 볼 수 있는 것도 중요하다.
Refresh Indicator가 필요하다.
Bloc 로직이 꼬이지 않도록 하는 것이 중요하다.
Refresh 시 새로운 데이터를 받아오게 되므로, 기존의 cache는 날려버리고 다시 cache를 저장하는 것이 좋다.
** data flow의 변화를 감지하여 작업? >> stream이 필요하다.
await for와 snapshot을 이용하여 가능하지만 widget을 이용해도 된다.
async snapshot은 query snapshot을 포괄하는 개념이다.
** Private 속성 부여 하는 방식 및 get을 이용하는 데에는 class의 멤버 변수에 대한 직접적인 수정을 막기 위한 것인데, get을 그대로 쓰면 어쨌든 직접 접근이 가능하므로 바뀌지 않는 data type을 주는 것이 더 좋음 따라서 UnmodifiableListView를 부여한다. (return type을 맞춰줘야 한다.)