일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- jszip
- Three js
- code editor
- identifierForVender
- typescript
- RouteObserver
- Completer
- Raycasting
- methodChannel
- Excel
- webrtc
- react
- node
- Three-fiber
- userevent_tracker
- FirebaseAnalytics
- REST API
- Game js
- swagger-typescript-api
- Flutter
- web track
- uint16array
- KakaoMap
- Image Resize typescript
- Prism.js
- Redux
- babel
- Babel standalone
- androidId
- uint8array
- Today
- Total
Never give up
Flutter - addPostFrameCallback 본문
build가 끝나지 않은 시점에서 데이터의 변경이 일어나고
notifyListeners를 사용해서 UI를 변경시키면 레드스크린을 볼 수 있습니다
예제로 initState에서 build가 끝나지 않은 상태에서 다음과 같이 값을 변경하면
@override
void initState() {
super.initState();
Provider.of<TestInt>(context, listen: false).setNum(2);
}
class TestInt extends ChangeNotifier {
int _num = 0;
int get getNum => _num;
void setNum(int value) {
_num = value;
notifyListeners(); //문제가 되는 부분
}
}
다음과 같은 에러가 출력됩니다
════════ Exception caught by foundation library ════════════════════════════════════════════════════
The following assertion was thrown while dispatching notifications for TestInt:
setState() or markNeedsBuild() called during build.
This _InheritedProviderScope<TestInt> widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: _InheritedProviderScope<TestInt>
value: Instance of 'TestInt'
listening to value
The widget which was currently being built when the offending call was made was: Builder
축약하자면 build가 끝나기전에 setState 혹은 markNeedsBuild가 호출돼서 오류 발생
오류 발생이유는 lifecycle을 참조하시면 어느정도 이해가 되지않을까 합니다
(참조 : https://devmemory.tistory.com/8?category=935233)
해결방안으로 여러가지가 있습니다만 여기서는 두가지 방법을 알아보겠습니다
첫째로 콜을 하되 notifyListeners를 사용하지 않는 방법
Consumer가 시작되기 전에 값을 변경하면
변경한 상태로 출력되기때문에 notifyListeners가 필요하지 않습니다
그리고 build가 끝나는 시점에 callback함수를 호출하는 방법
메인
class CallBackExample extends StatefulWidget {
@override
_CallBackExampleState createState() => _CallBackExampleState();
}
class _CallBackExampleState extends State<CallBackExample> {
@override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) => _updateData());
return Consumer<TestInt>(
builder: (context, value, child) {
return Scaffold(
appBar: AppBar(
title: Text('Callback'),
),
body: Center(
child: Text('${value.getNum}', style: TextStyle(fontSize: 30))),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => value.setNum(value.getNum + 1),
),
);
},
);
}
void _updateData() {
Provider.of<TestInt>(context, listen: false).setNum(1);
}
}
해당앱을 빌드하면 처음에 Text위젯에 value.getNum이 콜이 되고
초기값은 0이 되어야 하지만 _updateData콜백함수에서
build가 끝난 후 초기값을 1로 설정하기 때문에 1이 출력됩니다
해당 예제에서는 첫번째 방법이 더 간단하고 성능상 이점도 있지만
어떤 화면을 먼저 보여준 후 UI를 변경해야될 때 사용하면 좋을거 같습니다
주의할 점은 build에서 콜을 하게되면 rebuild가 될 때 다시 콜되기 때문에
bool을 이용해서 한번만 콜 되도록 처리를 해주던가
stateful위젯을 사용한다면 initstate에서 콜을 하는것이 좋을거 같습니다
-- 추가로 해당 동작이 화면이 바인딩 된 후 한번만 실행되면 좋겠다 싶을 때에는 initState에 넣어주시면 됩니다
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_updateData();
});
}
'Flutter' 카테고리의 다른 글
Flutter - SharedPreferences set and get Color (0) | 2020.08.03 |
---|---|
Flutter - Provider listen : false with http (0) | 2020.08.03 |
Flutter - StatefulWidget Lifecycle (0) | 2020.08.03 |
Flutter - Listview with Listener (0) | 2020.08.03 |
Flutter - Listview with ScrollController (2) | 2020.08.03 |