Provider Access

소밍·2023년 9월 6일
0
post-thumbnail

context가 available하지 않을 때 changeNotifierProvider에 builder콜백이나 flutter의 Builder 위젯을 사용해 우리가 원하는 provider에 access할 수 있음

.value 생성자의 usage는 프로바이더 엑세스와 밀접한 관련이 있고
다시 프로바이더 액세스는 navigation과 밀접한 관련이 있음
Navigation엔 크게 세가지 방법이 있음

  • anonymous route
  • named route
  • generated route

1. anonymous route

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Anonymous Route',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

내가 원했던 동작은 다음과 같다.
1. MyHomePage내에 있는 첫번째 ElevatedButton을 누르면 Navigator.push를 통해 ShowMeCounter로 이동한다.
2. ShowMeCounter 위젯에선 context.watch()<Counter>().counter 를 이용하여 카운터 값을 받아 화면에 띄운다.

ChangeNotifierProvider의 child로 MyHomePage 위젯을 작성했고, 해당 위젯의 Children인 ElevatedButton 동작으로 페이지 이동을 하기 때문에 ShowMeCounter 위젯에서 에 접근할 수 있을 거라 생각했지만 ElevatedButton 을 클릭하니 다음과 같은 에러가 발생했다.

Error: Could not find the correct Provider above this ShowMeCounter Widget

위젯트리 구조를 확인해보니 ShowMeCounter 위젯은 ChangeNotifierProvider의 child위젯이 아니고 ChangeNotifierProvider의 sibling으로써 materialApp의 child이다. 때문에 ShowMeCounter에서 context.watch()<Counter>().counter를 해도 Counter를 찾을 수 없었던 것.

  ElevatedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) {
                    return ShowMeCounter();
                  }),
                );
              },
ElevatedButton(
            onPressed: () {
              Navigator.push(
                context, // * 
                MaterialPageRoute(builder: (_) {
                  // 이 컨텍스트는 Navigator.push에 사용한 context값이어야함.
                  // 하지만 그냥 (context)라고 작성시 서로 무관한 context가 됨.
					// (counterContext) 으로 작성하면 정상 동작함.
				    // 이 때 어차피 counterContext값이 사용되지 않으니 (_)으로 작성해도 정상동작
                  return ChangeNotifierProvider.value(
                    value: context.read<Counter>(),
                    child: ShowMeCounter(),
                  );
                }),
              );
            },

MaterialApp이란

  • Navigator.push하면 child위젯 생기는게 아니고 새로운 위젯트리가 생긴 것. 때문에 ShowMeCounter에서 를 액세스하려면 별도의 작업이 필요함. 이 때 사용되는 것이 .value 네임드 constructor
    value constructor는 class를 자동으로 클로즈 하지 않음

2. named route

class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'NamedRoute',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      routes: {
        '/': (context) => MyHomePage(),
        '/counter': (context) => ShowMeCounter(),
      },
    );
  }
}

MyHomePage, ShowMeCounter 모두에게 인스턴스를 전달하고 싶다면?
ChangeNotifierProvider를 이용해 ChangeNotifierProvider 클래스를 create하면 필요없어지면 자동으로 클로즈함.
하지만 해당코드는 counterChangeNotifier를 ChangeNotifierProvider를 통하지 않고 create했기 때문에 클로스도 별도로 해주어야함.
해당 문제를 해결하기 위해 MyApp을 stateful 위젯으로 수정하고 stateful 위젯의 dispose 메서드에서 dispose 실시

class MyApp extends StatefulWidget {

MyApp({Key? key}) : super(key: key);


State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
final Counter _counter = Counter();


Widget build(BuildContext context) {
  return MaterialApp(
    title: 'AnonymousRoute',
    debugShowCheckedModeBanner: false,
    theme: ThemeData(
      primarySwatch: Colors.blue,
    ),
    routes: {
      '/': (context) => ChangeNotifierProvider.value(
            value: _counter,
            child: MyHomePage(),
          ),
      '/counter': (context) => ChangeNotifierProvider.value(
            value: _counter,
            child: ShowMeCounter(),
          ),
    },
  );
}


void dispose(){
  _counter.dispose();
  super.dispose();
}
}

3. generated route

  class _MyAppState extends State<MyApp> {
  final Counter _counter = Counter();

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'AnonymousRoute',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      onGenerateRoute: (RouteSettings settings) {
        switch (settings.name) {
          case '/':
            return MaterialPageRoute(
              builder: (context) => ChangeNotifierProvider.value(
                value: _counter,
                child: MyHomePage(),
              ),
            );
          case '/counter':
            return MaterialPageRoute(
              builder: (context) => ChangeNotifierProvider.value(
                value: _counter,
                child: ShowMeCounter(),
              ),
            );
          default:
            return null;
        }
      },
    );
  }

  
  void dispose() {
    _counter.dispose();
    super.dispose();
  }
}

Global Access

그냥 최상위 위젯을 provider로 감싸면 provider access 이슈가 쉽게 해결되는 거 아닐까? 라고 생각

  return Provider<T>(
  	create: (_) => T(),
  	child: MaterialApp(
  	...,
	),
  );

하지만 앱이 커지고 여러팀이 분할하여 앱을 개발할경우 바람직하지 않음.

profile
생각이 길면 용기는 사라진다.

0개의 댓글

관련 채용 정보