앱/Flutter

[Flutter] 특정 스크린을 보고 있는지 확인하기(with WidgetsBindingObserver visibility_detector)

paran21 2023. 7. 16. 00:02

0. 문제 상황

유저가 특정 스크린을 다시 볼 때 마다 데이터를 업데이트를 하는 기능을 추가하게 되었다.
구체적으로 데이터가 업데이트되는 상황은 다음과 같다.

1. 유저가 다른 스크린에 갔다가 다시 해당 스크린으로 돌아올 때
2. 유저가 해당 스크린에서 앱 바깥으로 나갔다가(background) 다시 앱으로 돌아왔을 때(foreground)`

이 기능을 구현하기 위해서는 먼저, 유저가 현재 해당 스크린을 보고 있다는 걸 확인해야 한다.

WidgetsBindingObservervisibility_detector를 사용해서 구현하였다.


1. WidgetsBindingObserver

공식문서

WidgetsBindingObserver은 mixin으로 사용하며, initState dispose에서 observer를 등록/제거해주어야 한다.

 

WidgetsBindingObserver class - widgets library - Dart API

Interface for classes that register with the Widgets layer binding. When used as a mixin, provides no-op method implementations. See WidgetsBinding.addObserver and WidgetsBinding.removeObserver. This class can be extended directly, to get default behaviors

api.flutter.dev

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }
  @override
  void dispose() {
    controller.dispose();
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }


여러 메서드들을 override에서 사용할 수 있는데, 이 중 내가 필요한 메서드는 didChangeAppLifecyleState이다.

 

AppLifecycleState에서 변경이 일어났을 경우 실행이되며, 현재 state를 매개변수로 받을 수 있다.
AppLifecycleState에는 resumed, inactive, paused, detached가 있다.

앱이 background 상태가 되면 paused가 되고, 다시 foreground 상태가 되면 resumed가 된다.

 

2. visibility_detector

공식문서

현재 화면이 활성화되었는지 알기 위해 visibility_detector라는 패키지를 사용하였다.

VisibilityDetector 의 onVisibilityChanged는 visibility가 변화하면 실행된다. 또, visibilityInfo를 알 수 있다.

 

visibility_detector | Flutter Package

A widget that detects the visibility of its child and notifies a callback.

pub.dev

3. 문제점

처음에 위에서 언급한 WidgetsBindingObservervisibility_detector만을 가지고 구현했을 때 기능은 원하는대로 잘 되었지만 예상하지 못한 문제가 있었다.

1. didChangeAppLifecyleState은 구현되어있는 위젯 뿐만 아니라 다른 위젯에서도 AppLifecycleState가 변화하면 항상 호출되었다. 그래서 다른 스크린에 있을 때도 background에 갔다가 다시 foreground로 돌아오면 실행되었다.
2. onVisibilityChanged도 visibility가 변화하면 항상 호출되기 때문에, 해당 스크린이 보일 때만이 아니라, 다른 스크린으로 이동해서 더 이상 보이지 않을 때도 호출되었다.

특히 문제가 되는 부분은, 콜백이 실행될 때마다 데이터를 업데이트하고 있기 때문에 불필요한 Api 호출이 반복된다는 점이다.
그래서 위의 문제점을 해결하기 위해 구글링을 하다가 비슷한 다른 패키지를 찾았다.

 

4. focus_detector

공식문서

 

focus_detector | Flutter Package

Detects when your widget appears or disappears from the screen.

pub.dev

 

focus_detector는 내가 원하는 기능을 모두 구현하고 있었다.
이름만 봐도 알 수 있듯이 onVisibilityGainedonForegroundGained를 사용하면 될 것 같았고, 됬다!

그런데 일단 마지막 업데이트가 10개월 전이라는게 걸렸다.
그리고 패키지를 적용해서 실행하다가 에러가 발생했는데 관련해서 깃허브 이슈를 찾아보니 이미 오래전에 제기된 이슈였다. 
이슈를 해결하는 방법은 코멘트로 달려있었고, 심지어 에러가 잘 해결도 되었지만 아직까지 업데이트가 되지 않았다는 점이 불안했다.

그래서 코드를 살펴보니 생각보다 간단하게 구현이 되어있었다.
이 패키지는 위에서 사용해본 visibility_detectorWidgetsBindingObserver를 사용해서 대부분의 메서드를 구현하고 있다.


5. 최종 코드!!

focus_detector를 참고해서 결과적으로 구현한 코드는 다음과 같다.

먼저 위젯이 보이는지, foreground인지 알려주는 bool 타입 변수를 만들어서 스크린 위젯에 저장한다.

bool isWidgetVisible = true;
bool isAppInForeground = true;


그리고 build 함수에서 위젯을 VisibilityDetector로 감싸준다.

Widget build(BuildContext context) {
...
return VisibilityDetector(
  onVisibilityChanged: (visibilityInfo) {
    if (!isAppInForeground) return;
    if (!isWidgetVisible && visibilityInfo.visibleFraction == 1) {
      isWidgetVisible = true;
      reloadData();
    }

    if (isWidgetVisible && visibilityInfo.visibleFraction == 0) {
      isWidgetVisible = false;
    }
  },
  child: Scaffold(
  ...
  )}

  
visibilityInfo.visibleFraction == 1이면 현재 스크린이 보이는 상태이고,
visibilityInfo.visibleFraction == 0이면 현재 스크린이 보이지 않는 상태이다.

위젯에 변수로 저장한 isWidgetVisible과 비교해서 이전에는 보이지 않았다가 다시 보이는 경우에만 데이터를 업데이트하였다.

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (!isWidgetVisible) return;
    if (state == AppLifecycleState.resumed && !isAppInForeground) {
      isAppInForeground = true;
      reloadData();
    }

    if (state == AppLifecycleState.paused && isAppInForeground) {
      isAppInForeground = false;
    }
  }

AppLifecycleState도 변수로 저장한 isAppInForeground와 같이 비교해서 이전에는 background였다가 foreground로 올라온 경우에만 데이터를 업데이트하였다.