본문 바로가기

Flutter/07 State - Provider

[Flutter] ChangeNotifierProvider

오늘은 ChangeNotifierProvider 에 대해서 정리했습니다.

개발환경 : 윈도우11, 안드로이드 스튜디오(Arctic Fox 2020.3.1 Patch 4), flutter 2.10

소스코드 위치 - Release setState_to_Provider · mike-bskim/provider_overview (github.com)

 

Release setState_to_Provider · mike-bskim/provider_overview

 

github.com

 

상태관리 패키지들도 다양하고 하나의 패키지 내에서도 다양한 방식으로 상태관리를 할 수 있습니다. 그중에서 오늘은 ChangeNotifierProvider 에 대해서 알아보겠습니다.

 

먼저 화면은 아래와 같습니다. 이전 setState 샘플 코드와 기본 구조는 같고 이해를 위해서 색상 및 구조를 약간 변경하였습니다.

 

 

기본 콘솔 로그는 아래와 같습니다. Middle 위젯에서 하는일이 없다보니, 버튼클릭시 호출순서에 상관없이 먼저 출력됩니다.

 

I/flutter ( 7675): -----------------------------------
I/flutter ( 7675): MyApp >> build
I/flutter ( 7675): ===================================
I/flutter ( 7675): MyHomePage >> build
I/flutter ( 7675): CounterA >> build
I/flutter ( 7675): Middle >> build
I/flutter ( 7675): CounterB >> build
I/flutter ( 7675): Sibling >> build
// 가독성을 위한 임의 줄바꿈
I/flutter ( 7675): Middle >> build
I/flutter ( 7675): CounterA >> build
I/flutter ( 7675): CounterB >> build
I/flutter ( 7675): Sibling >> build
// 가독성을 위한 임의 줄바꿈
I/flutter ( 7675): Middle >> build
I/flutter ( 7675): CounterA >> build
I/flutter ( 7675): CounterB >> build
I/flutter ( 7675): Sibling >> build

 


 

전체 소스 코드는 "더보기" 클릭하면 볼수 있습니다.

더보기
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(const MyApp());
}

class Counter with ChangeNotifier {
  String titleCounterA = 'Counter A';
  String titleCounterB = 'Counter B';
  String titleMiddle = 'Middle';
  String titleSibling = 'Sibling';

  int counter = 0;

  void increment() {
    counter++;
    notifyListeners();
  }
}

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

  @override
  Widget build(BuildContext context) {
    debugPrint('-----------------------------------');
    debugPrint('MyApp >> build');

    return MaterialApp(
      title: 'Counter',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    debugPrint('===================================');
    debugPrint('MyHomePage >> build');

    return Scaffold(
      appBar: AppBar(
        title: const Text('My Counter'),
      ),
      body: ChangeNotifierProvider<Counter>(
        create: (context) => Counter(),
        child: Center(
          child: Container(
            color: Colors.grey,
            padding: const EdgeInsets.all(16.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              mainAxisSize: MainAxisSize.min,
              children: const <Widget>[
                Text('MyHomePage', style: TextStyle(fontSize: 24.0)),
                SizedBox(height: 20.0),
                CounterA(),
                SizedBox(height: 20.0),
                Middle(),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    debugPrint('CounterA >> build');

    return Container(
      color: Colors.red[100],
      padding: const EdgeInsets.all(20.0),
      child: Column(
        children: [
          Text(Provider.of<Counter>(context).titleCounterA),
          Text(
            '${Provider.of<Counter>(context).counter}',
            style: const TextStyle(fontSize: 48.0),
          ),
          ElevatedButton(
            onPressed: Provider.of<Counter>(context).increment,
            child: const Text(
              'Increment',
              style: TextStyle(fontSize: 20.0),
            ),
          ),
        ],
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    debugPrint('Middle >> build');

    return Container(
      color: Colors.grey[200],
      padding: const EdgeInsets.all(20.0),
      child: Column(
        children: [
          Text(Provider.of<Counter>(context).titleMiddle),
          const SizedBox(height: 20),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            mainAxisSize: MainAxisSize.min,
            children: const [
              CounterB(),
              SizedBox(width: 20.0),
              Sibling(),
            ],
          ),
        ],
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    debugPrint('CounterB >> build');

    return Container(
      color: Colors.yellow[100],
      padding: const EdgeInsets.all(10.0),
      child: Column(
        children: [
          Text(Provider.of<Counter>(context).titleCounterB),
          Text(
            // '${Provider.of<Counter>(context).counter}',
            '${context.watch<Counter>().counter}',
            style: const TextStyle(fontSize: 24.0),
          ),
        ],
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    debugPrint('Sibling >> build');

    return Container(
      color: Colors.orange[100],
      padding: const EdgeInsets.all(10.0),
      child: Column(
        children: [
          Text(Provider.of<Counter>(context).titleSibling),
          const Text(
            'Sibling',
            style: TextStyle(fontSize: 24.0),
          ),
        ],
      ),
    );
  }
}

 


 

모든 위젯이 다시 빌드되는것을 볼수 있습니다.

Provider 를 사용하더라도 구조에 따라서 특별한 성능차이가 없을수도 있다는 예시입니다.

 

그럼 여기서 몇가지 수정을 하여 효율적인 코드를 만들어 보겠습니다.

Sibling 위젯을 수정했습니다.

 

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

  @override
  Widget build(BuildContext context) {
    debugPrint('Sibling >> build');

    return Container(
      color: Colors.orange[100],
      padding: const EdgeInsets.all(10.0),
      child: Column(
        children: [
          // Text(Provider.of<Counter>(context).titleSibling),
          Text(context.read<Counter>().titleSibling),
          const Text(
            'Sibling',
            style: TextStyle(fontSize: 24.0),
          ),
        ],
      ),
    );
  }
}

 

위젯이 빌드되지 않는것을 볼수 있습니다.

또는 "Text(Provider.of<Counter>(context, listen: false).titleSibling)," listen을 false 처리해도 된다.

 

I/flutter ( 7675): ===================================
I/flutter ( 7675): Middle >> build
I/flutter ( 7675): CounterA >> build
I/flutter ( 7675): CounterB >> build
I/flutter ( 7675): ===================================
I/flutter ( 7675): Middle >> build
I/flutter ( 7675): CounterA >> build
I/flutter ( 7675): CounterB >> build

 

Middle 위젯도 동일하게 처리하리 할 수 있다.