알쓸전컴(알아두면 쓸모있는 전자 컴퓨터)
[Flutter] FutureBuilder에서 setState 할때 future 초기화 제외 하기 본문
FuterBuilder는
initState 에서 초기화 메소드가 있는것이 아니고 Widget build 단계에서 초기화 코드를 사용할수 있는 Builder 입니다.
https://www.youtube.com/watch?v=ek8ZPdWj4Qo
Flutter 공식 Youtube 에 나온 설명이 제일 직관 적이네요!
하지만 사용중에 버튼 click 이벤트 함수 에서 화면 갱신이 필요해서 setstate 메소드를 실행 하면
다시 builer 가 돌면서 FuterBuilder 의 future 부분(초기화) 을 실행 합니다.
그리고 다시 Widget 을 builder 의 Widget 을 그리고 이부분은 상당히 골치 거리 입니다.
왜냐하면 별것도 아닌것에 화면 갱신이 필요할때마다 초기화 코드를 실행하니까 말이죠.
하지만 해결법은 있습니다.
https://medium.com/saugo360/flutter-my-futurebuilder-keeps-firing-6e774830bc2(참고자료)
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen()
);
}
}
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
bool _switchValue;
@override
void initState() {
super.initState();
this._switchValue = false;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Switch(
value: this._switchValue,
onChanged: (newValue) {
setState(() {
this._switchValue = newValue;
});
},
),
FutureBuilder(
future: this._fetchData(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator()
);
default:
return Center(
child: Text(snapshot.data)
);
}
}
),
],
),
);
}
_fetchData() async {
await Future.delayed(Duration(seconds: 2));
return 'REMOTE DATA';
}
}
먼저 위와 같은 코드가 있습니다.
_fetchData() async {
await Future.delayed(Duration(seconds: 2));
return 'REMOTE DATA';
}
해당 부분에서 2초 뒤에 REMOTE DATA 를 넘겨 주죠
FutureBuilder 는 future 에서 _fetchData() 를 실행 하고 2초뒤에 'REMOTE DATA' 를 들고서는
builder 함수로 가게 됩니다.
그리고 나서 스위치를 누르게 되면 아래와 같이 됩니다. 즉
다시 ConnectionState가none 으로 변하면서 _fetchData() 초기화 함수가 진행 되는것을 볼수가 있습니다.
해당 부분을 수정 하는게 필요할때가 많습니다.
원인은 아래와 같습니다.
StatefulWidgets 에서 rebuilder 할때 아래와 같은 코드가 자동으로 적용 됩니다.
@override
void didUpdateWidget(FutureBuilder<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.future != widget.future) {
if (_activeCallbackIdentity != null) {
_unsubscribe();
_snapshot = _snapshot.inState(ConnectionState.none);
}
_subscribe();
}
}
위에 보면 oldWidget.future != widget.future 결국 rebuild 할때 .future 를 똑같이 실행 합니다.
결국 future에 걸린 CallBack 함수는 같다는 의미 입니다.
여기서 에서 우리는
AsyncMemoizer 클래스에 대해서 알아야 합니다 .
해당 클래스를 사용하기 위해서는
dependencies:
async: ^2.3.0
을 추가 해주셔야 합니다.
해당 클래스는
https://api.flutter.dev/flutter/package-async_async/AsyncMemoizer-class.html
async 를 한번만 실행 하고 그 결과값을 캐쉬에 저장 시켜 다시 async 함수를 실행 시킬때에 이미 실행해 캐쉬에 저장된 값을 리턴해주는 클래스 입니다 .
이것을 응용 하면
코드 상단에
final AsyncMemoizer _memoizer = AsyncMemoizer();
을 해주고
_fetchData() {
return this._memoizer.runOnce(() async {
await Future.delayed(Duration(seconds: 2));
return 'REMOTE DATA';
});
}
_fetchData() 함수를 다음과 같이 작성 하여 FutureBuilder 의 future 에 넣어 줍니다.
그러면 setstate 를 할때 해당 함수는 캐쉬에 저장되어 있는 값을 바로 리턴 합니다. 결국 async 함수 안에 작성된 코드는 실행 하지 않고 캐쉬에 저장한 결과값만 리턴 합니다.
완성된 코드는 아래와 같습니다.
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen()
);
}
}
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final AsyncMemoizer _memoizer = AsyncMemoizer();
bool _switchValue;
@override
void initState() {
super.initState();
this._switchValue = false;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Switch(
value: this._switchValue,
onChanged: (newValue) {
setState(() {
this._switchValue = newValue;
});
},
),
FutureBuilder(
future: this._fetchData(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator()
);
default:
return Center(
child: Text(snapshot.data)
);
}
}
),
],
),
);
}
_fetchData() {
return this._memoizer.runOnce(() async {
await Future.delayed(Duration(seconds: 2));
return 'REMOTE DATA';
});
}
}
'Flutter,Dart' 카테고리의 다른 글
Flutter Execution failed for task ':firebase_core:verifyReleaseResources' 에러 (1) | 2019.10.23 |
---|---|
Flutter 에서 Android Widgets 불러와서 사용하기 (1) | 2019.10.16 |
[Flutter]TabBarView 에서 tab index 변경시 initState 안하기 (0) | 2019.07.24 |
Flutter Json DeSerializable 와 Json List DeSerializable (0) | 2019.07.18 |
[Flutter,Dart](신)동네예보정보조회서비스 기상청 API,위도 경도->좌표 (0) | 2019.07.03 |