Never give up

Flutter - Looking up a deactivated widget's ancestor is unsafe. 본문

해왔던 삽질..

Flutter - Looking up a deactivated widget's ancestor is unsafe.

대기만성 개발자 2020. 12. 26. 05:35
반응형

제목에 해당되는 에러는 많은 이유로 생길 수 있습니다

 

E/flutter (19092): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
E/flutter (19092): At this point the state of the widget's element tree is no longer stable.
E/flutter (19092): To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.

 

간단하게 축약해보자면

 

비활성된 위젯의 상태가 안전하지 않고, dispose()를 안전하게 참조하기 위해 didChangeDependencies() 메소드에서 dependOnInheritedWidgetOfExactType()를 호출해서 참조값을 저장해주세요

 

인데 위젯 dispose가 되지 않은 상태에서 deactive 된 경우 발생할 가능성이 있습니다

 

여기서 생각해 볼 수 있을건 다른 context를 참조해서 사용하는 AlertDialog, Snackbar 등등이 있을 수 있는데

 

이런 부분은 context예제에서 한 방법대로 scaffold의 key를 이용하면 간단하게 해결할 수 있습니다

(예제 : devmemory.tistory.com/31)

 

그런데.. 필자의 삽질은 상상을 초월했습니다

 

< 이건 좀 아닌듯.. >

 

왜냐면.. 아래 에러를 더 보면 답이 있습니다.. <asynchronous suspension>

 

네 비동기처리중에 Navigator를 사용해버린겁니다..

 

원래대로라면 에러가 뜨지는 않겠지만 context를 참조하고 있다면 얘기는 달라집니다

 

해당 상황을 간단하게 구현해보겠습니다

 

Main

class NaviExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Navi Example'),
      ),
      body: Center(
        child: RaisedButton(
          child: Text('Main page'),
          onPressed: () => Navigator.push(
              context, MaterialPageRoute(builder: (context) => TestPage())),
        ),
      ),
    );
  }
}

Test page

class TestPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Test page'),
      ),
      body: Center(
        child: RaisedButton(
            child: Text('Go to Main'),
            onPressed: () => showDialog(
                context: context,
                builder: (context) => AlertDialog(
                      title: Text('Test'),
                      actions: [
                        FlatButton(
                            onPressed: () => Navigator.pop(context),
                            child: Text('No')),
                        FlatButton(
                            onPressed: () {
                              LoadingHud hud = LoadingHud(context);
                              test(hud);

                              Navigator.pushAndRemoveUntil(
                                  context,
                                  MaterialPageRoute(
                                      builder: (context) => NaviExample()),
                                  (route) => false);
                            },
                            child: Text('Yes'))
                      ],
                    ))),
      ),
    );
  }

  void test(LoadingHud hud) async {
    hud.showHud();

    await Future.delayed(Duration(seconds: 2)).then((value){
      print('done');
    });

    hud.hideHud();
  }
}

Loading hud

class LoadingHud {
  BuildContext context;

  LoadingHud(this.context);

  void showHud() {
    showDialog(
      barrierDismissible: false,
      barrierColor: Colors.transparent,
      context: context,
      builder: (context) => Dialog(
          backgroundColor: Colors.transparent,
          elevation: 0,
          child: Column(
            mainAxisSize: MainAxisSize.min,
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              CircularProgressIndicator(
                  strokeWidth: 6,
                  backgroundColor: Colors.black26,
                  valueColor: AlwaysStoppedAnimation(Colors.amber)),
              SizedBox(height: 10),
              Text('Loading',
                  style: TextStyle(color: Colors.amber, fontSize: 16))
            ],
          )),
    );
  }

  void hideHud() {
    Navigator.pop(context);
  }
}

간단하게 페이지 중앙에 raisedbutton을 사용해서 메인과 test페이지를 왔다갔다 하는 예제입니다

 

TestPage부분을 조금 살펴보자면 AlertDialog에서 yes를 누르면

 

test 메소드가 실행되고, 실행되는동안(딜레이) hud를 보여주고 완료 하면 hud를 hide하고

 

메인페이지로 push합니다 여기서 pushAndRemoveUntil을 쓴 이유는

 

하위루트 alertdialog, testpage를 제거하고 main으로 push하기 위해서입니다

 

근데.. 계속 에러가 발생해서 필자는 여기서 애꿎은 하위루트들과 context만 건드렸는데

 

사실 정말 간단한건데 필자가 놓치고 있었습니다..

 

test함수랑 alertdialog의 yes onpress부분이 어떻게 처리되어있는지보고

 

이 사람 빡대가리다.. 하셔도 됩니다ㅠㅠ

< 진짜 빡대가리인가봐 ㅠㅠ >

네... test 동작이 안끝났는데 Navigator를 사용했어요..

 

일반적인 상황이라면 발생하지 않겠지만.. Loading hud를 인스턴스화 할 때

 

context를 넘겨주는것을 다시한번 체크했어야됐는데 안해서 시간만 날렸어요...

 

부연설명을 해보자면 Navigator push and remove route를 실행하면 Test페이지의 context가 deactivated될텐데

 

비동기 작업으로 인스턴스화 된 Loading hud에서 context를 아직 사용중이니 에러가 안날 수가 없죠..ㅠㅠ

 

해결방법은 아주 간단하게 비동기 처리 작업만 확실하게 해주면 됩니다

FlatButton(
    onPressed: () async {
    LoadingHud hud = LoadingHud(context);
    await test(hud);

    Navigator.pushAndRemoveUntil(context,
    MaterialPageRoute(builder: (context) => NaviExample()),
    (route) => false);
}, child: Text('Yes'))
Future<void> test(LoadingHud hud) async {
    hud.showHud();

    await Future.delayed(Duration(seconds: 2)).then((value) {
      print('done');
    });

    hud.hideHud();
}

네 끝입니다.. 정말 간단하게 해결 가능하죠..

 

정말 할말이 없습니다..

 

< 노답 >

반응형
Comments