[Flutter] Provider Essential - 2

jaehee kim·2022년 1월 30일
1

Flutter

목록 보기
18/20
post-thumbnail

MultiProvider

여러 개의 Proivder를 사용하는 경우 MultiProvider를 이용할 수 있다.

MultiProvider(
	providers: [
    	ChangeNotifierProvider<T>(
          create: (context) => T(),
        ),
        ChangeNotifierProvider<S>(
          create: (context) => S(),
        ),
        ChangeNotifierProvider<R>(
          create: (context) => R(),
        ),
      ],
      child: MaterialApp(
        title: 'Provider',
        home: const MyHomePage(),
      ),
    );

Consumer

builder를 이용한 방식이다. 새로운 위젯에서 Provider.of를 호출하고 build하는 것을 builder에게 위임한다.

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Consumer<Dog>(
        builder: (BuildContext context, Dog dog, Widget? child) {
          return Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              mainAxisSize: MainAxisSize.min,
              children: [
                Text('- name:${dog.name}'),
              ],
            ),
          );
        },
      ),
    );
  }
}

만약 Column 내에서 rebuild될 필요없는 위젯들까지 rebuild되면 비효율 적이다. 이러한 경우 builder의 child parameter를 이용한다.

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Consumer<Dog>(
        builder: (BuildContext context, Dog dog, Widget? child) {
          return Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              mainAxisSize: MainAxisSize.min,
              children: [
                child!,
                Text('- name: ${dog.name}'),
              ],
            ),
          );
        },
        child: Text(
          'This is Child',
          style: TextStyle(fontSize: 20.0),
        ),
      ),
    );
  }
}

ProviderNotFoundException

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: ChangeNotifierProvider<Foo>(
        create: (_) => Foo(),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('${context.watch<Foo>().value}'),
              ElevatedButton(
                onPressed: () => context.read<Foo>().changeValue(),
                child: Text('Change Value'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

위의 예시 코드를 작성하고 restart하면 아래와 같은 에러가 발생한다.

  • You used a BuildContext that is an ancestor of the provider you are trying to read.


    Make sure that MyHomePage is under your MultiProvider/Provider.


    This usually happens when you are creating a provider and trying to read it immediately.
    For example, instead of:
  Widget build(BuildContext context) {
    return Provider<Example>(
      create: (_) => Example(),
      // Will throw a ProviderNotFoundError, because `context` is associated
      // to the widget that is the parent of `Provider<Example>`
      child: Text(context.watch<Example>()),
    ),
  }

consider using builder like so:

Widget build(BuildContext context) {
  return Provider<Example>(
    create: (_) => Example(),
    // we use `builder` to obtain a new `BuildContext` that has access to the provider
    builder: (context) {
      // No longer throws
      return Text(context.watch<Example>()),
    }
  ),
}

에러메시지에서 bulider를 사용해서 context를 얻을 수 있도록 하라고 말해주고 있다.
buliderBuildContext를 제공해주는 역할을 한다. 아래에 있는 코드처럼 bulider를 추가해서 에러를 해결할 수 있다.

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: ChangeNotifierProvider<Foo>(
        create: (_) => Foo(),
        child: Builder(builder: (BuildContext context) {
          return Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('${context.watch<Foo>().value}'),
                ElevatedButton(
                  onPressed: () => context.read<Foo>().changeValue(),
                  child: Text('Change Value'),
                ),
              ],
            ),
          );
        }),
      ),
    );
  }
}

추가로 Consumer를 사용해서 해결할 수도 있다.

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: ChangeNotifierProvider<Foo>(
        create: (_) => Foo(),
        child:
            Consumer<Foo>(builder: (BuildContext context, Foo foo, Widget? _) {
          return Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('${foo.value}'),
                ElevatedButton(
                  onPressed: () => foo.changeValue(),
                  child: Text('Change Value'),
                ),
              ],
            ),
          );
        }),
      ),
    );
  }
}

Selector

Consumer와 비슷한데, 더 세세하게 동작하도록 한다.
두 개의 타입을 지정하는데 첫번째는 여러 개의 property를 가지는 object의 타입이고, 두번째는 그중 선택할 property의 타입이다.

그리고 selector callback에서는 listen하고 싶은 property를 return 한다. builder callback에서는 2번째 parameter selector의 2번째 타입의 parameter를 지정한다.

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Selector<Dog, String>(
        selector: (BuildContext context, Dog dog) => dog.name,
        builder: (BuildContext context, String name, Widget? child) {
          return Column(
            children: [
              child!,
              Text('- name: $name'),
            ],
          );
        },
        child: Text('I like dogs very much'),
      ),
    );
  }
}




Reference

Heavy Fran - Flutter Provider Essential

0개의 댓글