현재 작동방식은 이렇게 되어있다.
라이브하다보면 여러이유로 서버가 죽는걸 많이 발견할 수 있다.
현재 문제는 Flutter에서 계속 Django api를 호출하여 api만드는 로직에서 DB connection 이 너무 많이 되는 문제이다.
DRF에서 serializers와 ORM을 사용하는데, 이 ORM을 사용하는 모든 스레드는 새로운 데이터베이스 연결을 만들어낸다고 한다.🤔 그리고 Django는 그 연결을 자동으로 관리해주지 않는다.
그말인 즉슨 ORM을 사용하고 나면 db connection을 끊어줘야한다는 말씀.🤭
Every thread that uses Django ORM creates a new database connection. And Django will not manage the connection automatically that created by your own thread. So you should manage it.
출처 : https://stackoverflow.com/a/17311359/13159805
serializers도 지금 쓰는 방식으로 사용하면 안됀다고 JK가 그러는데 이건 JK가 해결하고 알려주면 그때 쓰도록하겠다(협업의 올바른예)
This error happens if you call setState() on a State object for a widget
that no longer appears in the widget tree
(e.g., whose parent widget no longer includes the widget in its build).
This error can occur when code calls setState() from a timer or an
animation callback. The preferred solution is to cancel the timer or
stop listening to the animation in the dispose() callback.
Another solution is to check the “mounted” property of this object before
calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called
because another object is retaining a reference to this State object
after it has been removed from the tree.
To avoid memory leaks, consider breaking the reference to this object
during dispose().
영알못이 대충 감으로 해석해보자면 대충 이렇다
setState()함수안에 timer나 animation작업을 했으면 그걸 중단시켜야해. setState에서 "mounted"를 체크해서 사용하도록하고 dispose()로 멈춰. 안그러면 너의 메모리가 안녕할거야
여기서 잠깐 mounted가 뭐지하고 찾아보니
현재 떠있는게 이 화면인지 아닌지 판별을 해주는 거라고 한다. 현재 화면이면 true 아니면 false 반환인듯
페이지가 넘어가도 setState는 계속 작동하기 때문에 그 페이지가 떠있을때만 작동해야 되는 코드들은 mounted로 묶어주는게 좋겟지이~?
안그러면 setState가 작동하는 페이지(A)에서 또 setState가 작동하는 페이지(B)로 가고 다시 A로 갈때 저런 에러를 만나게 되고 그 setState는 엉키게 되겠지~?
그래서 원하는 대로 해주도록한다.
일단 난 timer를 쓰는곳이 initState() 함수였다 그래서 timer 함수 안에 mounted를 체크하는 로직을 추가로 넣어주었다.
timer = Timer.periodic(Duration(seconds: 10), (Timer t) {
if (mounted) { // 이부분!!!
setState(() {
_listenLocation();
print(_location.toString());
geolocationAPIData(_location.latitude, _location.longitude, workLog, place, device, position);
});
}
});
여기서 dispose()를 추가로 넣어주면되는데,
잠깐 dispose를 설명하기 전에 life cycle을 설명하도록 하겠다.
life cycle란 한 Activity(또는 view controller, 또는 Widget)가 보여지고 사라지는까지의 주기를 말한다.
Android를 한번이라도 배워본사람은 Activity에 lifecycle을 들어본적이 있을것이다.
Android를 해봤는데 들어본적없다고? lifecycle은 다음과 같다
이제 기억나지않는가?
이런게 iOS에도 있다 한번 살펴보자.
UIViewController에 lifecycle이다
Flutter의 Widget은 StatelessWidget과 StatefulWidget 두가지가 있는데, StatefulWidget이 Android와 iOS와 동일하니 살펴보도록하자
대충 보다보면 이해가 될것이다.
Activity(또는 view controller, 또는 Widget)가 보여지고 사라지기까지 저런 메서드가 실행되고,
한 Widget이 사라질때(더이상사용하지않을때) dispose()를 사용하는구나!라고
이렇게 생각한다면 앞에서 말한 dispose()로 멈추라는 말이 이해가 되지? 느낌 RGRG~?
그렇다면 이제 문제의 코드에 넣어보도록한다.
void dispose(){
timer?.cancel(); // timer가 null이면 null을 반환하고, 아니면 cancel() 호출
timer = null;
super.dispose();
}
나같은 경우에는 Bloc fetch을 하고자 setState() 안에다가 Bloc.fetch()를 넣었다.
알고보니 이렇게 하니까 계속 fetch를 호출해서 계속 api를 호출하는 것이 아닌가?
void setState(fn) {
historyReportGetBloc.fetch();
super.setState(fn);
}
혹시나 해서 mounted 체크를 추가해봤다
void setState(fn) {
if (mounted) {
historyReportGetBloc.fetch();
super.setState(fn);
}
}
그래도 같은 오류라 setState안에 fetch부분은 빼버리고 일단보류 ㅡㅡ;
void setState(fn) {
super.setState(fn);
}
새로고침 할려는 부분은 수동으로 버튼을 눌러서 fetch하게 할지 아니면 local DB로 저장해서 보여줄지 고민중이다.