Provider를 사용한 페이지간의 데이터 공유시, Error: Could not find the correct Provider<ProviderName> above this <WidgetName> Widget이 발생하는 원인과 해결법

2020. 11. 1. 15:32Programming/Flutter

반응형

깃허브의 샘플 코드

Provider의 특징 중 하나는 여러개의 화면에서 상태값을 공유할 수 있다는 것이다. 실제로 그런 예제를 많이 접해볼 수 있는데, 가장 흔한 예제는 다음과 같은 카운터 앱일 것이다.

  • 첫 번째 화면에서 카운터를 조작(증감)
  • 두 번째 화면에서 카운터를 조회

보통은 별다른 설명 없이 Navigator.push로 두 번째 화면을 띄우고, 두 번째 화면에서는 Context의 Provider를 불러와서 조작을 시도한다. 이 때 뎁스에 따라서 Error: Could not find the correct Provider<[ProviderName]> above this[WidgetName]Widget와 같은 에러를 출력하게된다.

error.png

실제로 lib/problem/problem_main.dartlib/problem/problem_sub.dart를 보면 이러한 에러를 출력한다. 에러의 내용을 살펴보면, BuildContext에서 Provider를 찾을 수 없다는 내용이다. 에러의 내용을 잘 살펴보면 이유를 짐작할 수 있겠지만, DevTools의 Widget Inspector를 사용하면 원인을 분명하게 파악할 수 있다.

problem_tree.png

ProblemPage에서 생성한 ProblemProvider는 ProblemPage에 속해있지만, ProblemPage에서 Navigator.push로 새 창을 띄우기 위해 MaterialPageRoute로 생성한 ProblemSub는 MainApp의 child인 MaterialApp에 속해있다. 따라서 ProblemSub에서 Provider.of(context)를 실행하게되면, MainApp의 child인 MaterialApp에서 Provider를 찾기 때문에 에러가 발생하게 된다. Provider가 InheriteWidget기 때문이다.

문제상황을 해결하려면 route에 Provider를 선언해주면 된다. 만약 앱에서 Provider를 사용하는 경우가 거의 없고, 코드의 분량이 많지 않다면 다음과 같은 방법으로 최상단에 선언된 MaterialApp에 Provider를 선언할 수 있다.

void main() {
  runApp(
    MultiProvider(
      providers: [
        Provider(
          create: (context) => ProblemProvider(),
        )
      ],
      child: MainApp(),
    ),
  );
}

ProblemProvider가 MainApp을 기준으로 생성되기 때문에, MainApp의 아래에서 작성하는 위젯의 BuildContext에는 모두 ProblemProvider를 포함하게된다. 앱의 규모가 적다면 상관없지만, 앱의 규모가 클 때 필요없는 Provider가 포함된다는 것은 딱히 바람직해보이지는 않는다.

이 경우 route는 MaterialApp에 의해서 결정되므로, Provider를 공유할 페이지에 한해서 MaterialApp을 생성해주면 해결이 가능하다.

class SolvePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => SolveProvider(),
      child: MaterialApp(
        home: SolveMain(),
      ),
    );
  }
}

SolvePage에서는 ProblemPage와는 달리 ChangeNotifierProvider를 호출하면서 child로 MaterialApp을 생성하는데, 이렇게 되면 여기서 생성한 MaterialApp의 BuildContext에 SolveProvider가 성생된다. 이후 SolveMain에서 Navigator.push를 사용해 새로운 화면을 출력하더라도, BuildContext 내에는 SolveProvider가 존재하므로 에러가 출력되지 않는다.


위에서 MaterialApp을 사용해 route를 새로 만들어서, Provider의 route를 지정해주는 것에는 약간의 문제가 있다. 이렇게 MaterialApp으로 새로 생성된 route는, 최상단의 route와는 별개이다. 즉, Scope가 다르므로 NamedRoute를 사용하기 어렵다던가하는 문제가 따라오게 된다.

새로 생성된 route 내에서 최상단의 route의 설정을 공유받아 사용하는 방법도 있을수는 있겠지만, 이 경우 새로운 route에서 이동할 때 문제가 발생할 가능성이 있어보여 바람직하진 않아보인다. 기본적으로 D.B.같은 곳에 데이터를 저장해놓는다면, 굳이 여러 화면간에 Provider를 공유해야할 필요가 있는지 생각해볼 필요가 있다.

반응형