Study/Dart,Flutter

15. [Flutter] 플러터 성능향상을 위한 꿀팁(2부GestureDetect와 BuildContext sync)

코딩 잘 할거얌:) 2022. 9. 12. 22:21
반응형

오늘은 Flutter에서 버튼 혹은 사용자의 이벤트 발생에 의해 실행되는 비동기 호출에 대해서 알아보자.

 

우리는 GestureDetector 혹은 ElevatedButton을 이용해서 사용자의 클릭이벤트를 받곤 한다. 예를 들어 버튼을 클릭하면 서버와 통신을 해서 특정 데이터를 가져와서 화면을 새로 고치거나 새로운 화면을 보여준다.

 

만약 여기서 onTap에다가 async를 달아서 비동기로 만들어서 실행을 하는데, BuildContext가 변경된다면 어떤일이 생길까? 그리고 Context의 종속성에 자유로운(?) GetX를 이용했을 때에도 어떻게 되는지 알아보자.

 

화면이 사라졌는데 그 화면을 새로고침 한다면 이런경우가 발생하지 않을까?


참조 및 버전

 

https://www.youtube.com/watch?v=bzWaMpD1LHY 

 


Flutter 3.0.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision f1875d570e (8 weeks ago) • 2022-07-13 11:24:16 -0700
Engine • revision e85ea0e79c
Tools • Dart 2.17.6 • DevTools 2.12.2


본론

 

우리는 플러터로 코딩하다 보면 아래와 같은 코드를 짜게 된다.

플러터를 조금이라도 코딩한 사람은 너무나도 익숙할 것이다.

사용자의 클릭이벤트를 GestureDetector의 onTap으로 받아서, onTap은 async를 준 후 그 안에서 await으로 서버와 통신을 처리하고 화면 이동을 하는 코드이다.

이미지를 보면 딱 한 곳에서만 파란 줄이 그어져있다. 바로 onTap에서 await 이후에 있는 'Navigator.of(context).pop();'이다. 이 파란줄은 linter라는건데 코드에서 좋지않은 상황일때 우리에게 알려주는 역할을 한다. 플러터를 처음제작할 때 기본적인 linter가 적용되어있다.

그렇다면 여기에 왜 파란줄이 되어있을까? 바로 사용자가 onTap이벤트를 발생하고 async await를 한 후에는 BuildContext를 보장할 수 없기 때문이다.

 

왜일까?

우선 우리는 BuildContext가 무엇인지 간단하게 알고 가야 한다. 아래의 이미지는 플러터 공식 홈페이지에 적혀있는 걸 가져왔다.

https://api.flutter.dev/flutter/widgets/BuildContext-class.html

'위젯 트리에서 위젯의 위치를 나타내는 것'으로 해석하면 좋아 보인다.  statefulWidget혹은 statelessWidget의 Widget build(BuildContext context)는 위젯의 위치를 나타내는 건데, 우리는 BuildContext를 이용해서 다음 화면을 이동하거나 Dialog를 띄우거나 Snackbar를 띄운다. 

그래서 이게 뭐가 문제일까? 

BuildContext는 synchronous(동기)이기 때문이다.

우리가 사용자의 이벤트(onTap)가 발생하고 비동기로 처리를 한 후 BuildContext를 사용하게 되면, 동기로 사용하는 BuildContext와 일치한다는 보장을 할 수가 없다.

 

예시를 들어보자.

  1. 사용자가 onTap이벤트를 발생시킨다.
  2. onTap가 async로 되어있고, await Future.delay가 실행되며 3초간 기다린다.
  3. 3초가 되기 전, 사용자가 appbar에 있는 뒤로 가기 버튼을 클릭한다.
  4. 현재 페이지가 종료되고 await Future.delay가 종료되고 다음 코드 'Navigator pop'이 실행된다.

이러한 일련의 프로세스를 진행하게 되면 어떻게 될까?

바로 Exception이 발생하며 오류를 뱉어내게 된다.

BuildContext는 동기적으로 진행이 되는데 onTap은 비동기로 되어있어서 같다는 걸 보장하지 못한다. 더 쉽게 말하자면, await Future.delay 후 사용되는 context에 대해서 현재 화면에 보여주고 있는 BuildContext와 같다는 보장을 할 수가 없다.

 

그러면 어떻게 해야 할까?

간단하게 현재 context가 mount 되어있는지 확인해주면 이 문제를 해결할 수 있다.

if(!mounted)return;

이렇게 한 줄 추가해주면 linter도 해결이 되고 오류도 잡아진다. 

여담으로 코드 중간에 return 이 있다면, early return이라고 한다.

다만 주의해야 할 점은 StatelessWidget은 state상태라는 게 immutable, 불변이기 때문에 mounted라는 것이 없다. 따라서 StatefulWidget에서는 주의하며 코딩을 하도록 하자. (가급적이면 사용자가 기다려야 하는 비동기일 때는 dialog로 로딩 창을 보여주자..)

 

혹은 현재 Route를 확인하는 방법이 있다. ModalRoute를 이용한다면,

///Null safety 적용
ModalRoute.of(context)?.settings.name
///아닐경우
ModalRoute.of(context).settings.name

이렇게 사용해서 현재 라우트의 이름을 확인하여 context를 사용하도록 하자!


오류, 지적사항 그리고 궁금한 것은 댓글 부탁드립니다.

728x90