Search
▪️

State Managements

Widget의 Depth가 깊어지면 깊어질수록 child가 어떤 data를 원하면 일반적으로 가장 윗단의 Widget이 data를 관리하기 때문에 constructor을 통해서 일일이 data를 pushing 해줘야 한다. (굉장히 긴 chain들이 되며, 안 쓰는 데이터를 받기도 해야 한다. 심지어 child에서 이용하는 data의 state가 바뀌면 그에 맞춰서 부모 Widget들도 해당 데이터를 넘겨 받았기 때문에 다 rebuild 되어야 한다.) 따라서 State Management가 필요하다.
State라 함은 UI에 영향을 끼치는 모든 데이터들을 말한다. 그리고 User Interface라 함은 해당 데이터들을 이용하는 함수이고 이를 통해 보여지는 것들을 말한다.
이런 State도 App-wide State vs Widget State로 나뉜다.
App-wide State
어플 전체나 상당수에 영향을 끼치는 State
Authentication이나 중요한 main data들
Widget State
매우 적은 부분만 영향을 끼치는 State
Form Input과 같은..
그래서 이런 State들에 대해서 어떻게 관리하는가? Pattern이 필요하다. 예를 들면 Provider Package를 이용한 Pattern들 말이다.
어플에 Data Provider라고 하는 central storage 혹은 data container가 global하게 있다. 이 Provider는 어플에 있는 Widget에 붙일 수 있음 (root Widget이 아니어도 됨) 이렇게 Provider가 붙으면 모든 child Widget들은 Listener을 달았을 때 Provider을 계속 Listen할 수 있게 된다.
Listener가 달리게 되면, constructor을 통해서 data를 밀어 넣는 것과는 달리 Inherited Widget과 같은 역할을 하는 Listener를 통해 data를 받을 수 있다. (of(context) trick이다.) 이전과 같이 부모 Widget을 통해 직접적으로 받는 것이 아니라 Provider을 통해서 간접적으로 받지만 직접 받는 매개체가 되는 것이다.
build()함수는 Provider에 있는 데이터가 변할 때마다 수행된다. (즉, 모든 Widget들을 rebuild 할 필요가 없어지고 해당 데이터에 영향을 받는 Listener가 달린 Widget만 build할 수 있게 되는 것이다.)
Flutter에서는 Navigator을 통해서 화면 전환을 하게 되면, 메모리를 참조하고 있지 않은 Widget들에 대해서는 자동으로 메모리에서 clean up 해준다. 그렇지만 Provider로 제공한 것들도 지워야 하는데 그렇지 않다! 따라서 Provider넘겨주고 받은 data들에 대해서도 clean up이 필요하다. 이는 ChangeNotifierProvider가 value든 아니든 사용하고 있지 않은 data들에 대해서는 자동으로 clean up 해준다.
Provider를 쓰고 있는 부분에서 왜 StatefulWidget을 쓰지 않는가? → 영향 받는 Widget이 극도로 적다면 Provider를 쓰는 것보다는 StatefulWidget을 써도 무방하다. 하지만 적은 부분의 Widget이 영향을 받는 것 같지만 Widget에 쓰이는 data들이 여러 Widget에 걸쳐 있다면 StatefulWidget을 쓰는 것은 좋지 않다.
class에서 import 할 때, 해당 스크립트 내에서 특정 class만 추출해서 쓰고 싶다면 show 키워드를 이용하면 됨 (클래스 중복의 경우에도 마찬가지로 show를 통해서 방지)
GridView도 ListView와 마찬가지로 builder를 쓸 수 있고 특성도 비슷하다. 또한 ListTile처럼 GridTile을 이용할 수 있다.
Provider들은 multiple하게 생성할 수 있고 해당 Provider들은 같은 Widget이나 다른 Widget에 달 수 있다. mixin은 class에 with를 통해서 추가 가능하다. extends와 다른 점은 부모 Widget에게 별도로 return 할 필요가 없다는 것이다. 즉, Provider의 기본은 Data가 변하는지 확인하는 것이기 때문에 ChangeNotifier를 mixin한다. 이 때, 바뀌는 data에 대해서 notifyListener()로 알린다.
정리하자면 이용할 Data Class에 ChangeNotifier를 달아서 Provider Package가 이용할 수 있도록 하고, 해당 Data Class을 이용하는 class에 Provider와 Listener를 부여하는 것이다.
Provider는 해당 Data Class을 이용하는 class들 보다 한 Depth 더 높은 곳에 위치시켜야 한다.
Provider Package와 Provider Package가 사용할 Data class을 import한다.
MaterialApp과 같은 root로 둘 Widget을 ChangeNotifierProvider로 포장한다.
Provider을 통해서 state에 영향을 끼치는 data들을 이용하기 위해서 listener가 달린 class들에서는 해당 data들을 Provider.of<Type>(context)로 받는다.
notifyListeners()로 알린 변화를 받을 때, Provider.of<Type>(context) 이외의 방법으로 Consumer<Type>를 이용한 방법이 있다.
Provider에서 제공하고 있는 data를 사용하는 Widget Tree를 Consumer<Type>로 둘러싼다.
단순히 둘러싸는 것이 아닌 Consumer의 builder의 인자로 context, dynamic, widget을 받는데 해당 widget에 나머지 child들을 할당한다.
일반적으로 전자나 후자나 같은 것이지만, Provider.of를 쓰게 되면 data가 바뀔 때마다 관련되어 있는 모든 build들이 rerun됨 하지만 Consumer를 이용하면 data가 바뀌었을 때 관련되어 있는 모든 build를 rerun하지 않고 그 중에서도 일부분의 build만 rerun하게 할 수 있다. 즉, 바뀔 부분만 consumer로 감싸면 성능 향상에 도움이 된다는 것이다.
상위 Widget Tree에서 listen을 false로 해도 하위 Widget Tree에서 Consumer이용 시에 listen 가능하다. Consumer로 감싼 Widget 중에서도 data에 의해서 영향을 받고 싶지 않다면 builder의 child인자를 이용하면 된다.
ChangeNotifierProvider의 builder에서 context를 이용하지 않는다면 인자를 낭비하는 것이기 때문에 ChangeNotifierProvider.value를 통해서 state관리를 하게 된다.
ChangeNotifierProvider는 class와 같이 object를 사용하는 mixin에 대해서만 이용한다. 그렇지 않다면 Provider를 이용 (즉, class와 같은 object가 아니라면 ChangeNotifier이용하지 않는다.)
Multiple Provider를 둬야할 때, 같은 스크립트에서 다른 Widget에 ChangeNotifierProvider를 매기면 굉장히 보기에 지저분하다. grouping 하는 Provider를 제공하기 때문에 MultiProvider를 이용하면 된다.
Provider.of에서 listen을 false로 한다는 것은 data를 한 번만 가져오면 된다거나 현재 페이지에서 변화를 반영하지 않고 다른 action을 취하려고 한다거나 할 때 사용한다.
문자열 정렬 관련하여 Padding이 Margin보다 나은 듯하다.
Provider.of<Type>(context)을 통해서 data가 바뀌면 계속 rebuild되면서 rendering 된다. 이런 현상을 막기 위해 of(context)에는 special한 method가 있다. of(context, listen: false)를 통해 notifyListener()의 호출로 인한 listen을 하지 않음을 명시할 수 있다.
{Object}.containsKey는 <List>.contains랑 비슷하다. 단, containesKey를 호출하는 Object는 null이면 안되므로 빈 Object에 대해서는 초기화를 해줘야 한다.
{Object}.putIfAbsent(key, (Object) {return Object;})로 {Object}에 추가한다.
{Object}.update(key, () {return Object;})로 {Object}를 수정한다.
{Object}.forEach((key, item) {})
{Object}와 같이 Map으로 묶여 있는 것들을 index를 통해 List처럼 접근하고 싶다면 해당 {Object}.values.toList()로 바꿔줘야 한다.
{Object}.remove()함수를 통해서 특정 항목을 쉽게 지울 수 있다.
import 'dart:math';를 통해 min() 함수를 쓸 수 있다.