Bloc Pattern을 단순한 구조로 만들어주는 Provider Pattern.

 

 

1. CountProvider.dart

import "package:flutter/material.dart";

class CountProvider extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void plus() {
    _count++;
    notifyListeners();
  }

  void minus() {
    _count--;
    notifyListeners();
  }
}

import "package:flutter/foundation.dart";

class CountProvider with ChangeNotifier, DiagnosticableTreeMixin {
  int _count = 0;
  int get count => _count;

  void plus() {
    _count++;
    notifyListeners();
  }

  void minus() {
    _count--;
    notifyListeners();
  }

  /// Makes `Counter` readable inside the devtools by listing all of its properties
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(IntProperty('count', count));
  }
}

 

 

2. main.dart

import "package:flutter/material.dart";
import "package:provider/provider.dart";
import "package:provider_example/CountProvider.dart";
import "package:provider_example/home.dart";

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Provider Sample",
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity
      ),
      // home: ChangeNotifierProvider(
      //   create: (BuildContext context) => CountProvider(),
      //   child: const Home(title: "Provider example"),
      // ),

      home: MultiProvider(
        providers: [
          ChangeNotifierProvider(create: (BuildContext context) => CountProvider()),
        ],
        child: const Home(title: "Provider example"),
      ),
    );
  }
}

 

 

3. home.dart

Provider.of<T>(context)를 이용한 Provider객체 Method호출 방법

import "package:flutter/material.dart";
import "package:provider/provider.dart";
import "package:provider_example/CountProvider.dart";
import "package:provider_example/CountView.dart";

class Home extends StatelessWidget {
  final String title;

  const Home({super.key, required this.title});

  @override
  Widget build(BuildContext context) {
    print("Widget : Home");

    // listen: false는 현재 Widget을 Rendering하지 않도록 한다.
    // But 하위 Widget은 여전히 다시 Rendering된다. 하위 Widget의 Rendering을 방지할려면 하위 Widget에서 Consumer를 사용해야 한다.
    CountProvider countProvider = Provider.of<CountProvider>(context, listen: false);

    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: const CountView(),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          IconButton(
              // 아래 두가지 모두 정상 작동된다.
              onPressed: () => countProvider.plus(),
              //onPressed: () => context.read<CountProvider>().plus(),
              icon: const Icon(Icons.add)),
          IconButton(
              // 아래 두가지 모두 정상 작동된다.
              onPressed: () => countProvider.minus(),
              //onPressed: () => context.read<CountProvider>().minus(),
              icon: const Icon(Icons.remove))
        ],
      ),
    );
  }
}

context.read<Provider T>는 현재 시점의 상태 값을 한번만 읽어오고, Provider객체에 접근은 하지만 구독하지는 않는다. 

Provider객체의 상태가 변경되어도 read를 호출한 위젯은 rebuild되지 않는다.

 

Provider.of<CountProvider>(context, listen: true); 는 context.watch<CountProvider>(); 와 같다

Provider.of<CountProvider>(context, listen: false); 는 context.read<CountProvider>(); 와 같다.

기본값 : listen: true, 값이 변할 때 UI도 같이 변해야 하면 true, 단순히 기능(함수)만 쓸 거라면 false를 선택

 

 

4. CountView.dart

Provider객체를 이용한 상태값 참조

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:study/provider/CountProvider.dart';

class CountView extends StatelessWidget {
  const CountView({super.key});

  @override
  Widget build(BuildContext context) {
    print("Widget : CountView");

    // 1. Provider.of<T>(content)
    // 현재 Widget을 반복하여 Rendering한다.
    // return Center(
    //   child: Text(
    //     Provider.of<CountProvider>(context).count.toString(),
    //     style: const TextStyle(fontSize: 80),
    //   ),
    // );

    // 2. context.watch<T>()
    // 현재 Widget을 반복하여 Rendering한다.
    // return Center(
    //   child: Text(
    //     context.watch<CountProvider>().count.toString(),
    //     style: const TextStyle(fontSize: 80),
    //   ),
    // );

    // 3. Consumer<T>(builder: (BuildContext context, T provider, Widget? child) {}
    // 현재 Widget을 반복하여 Rendering하지 않는다.
    return Center(
      child: Consumer<CountProvider>(
        builder: (context, provider, child) {
          return Text(
            provider.count.toString(),
            style: const TextStyle(fontSize: 80),
          );
        },
      ),
    );

    // 4. context.select<T, R>()
    // 현재 Widget을 반복하여 Rendering한다.
    // return Center(
    //   child: Text(
    //     context.select<CountProvider, int>((provider) => provider.count).toString(),
    //     style: const TextStyle(fontSize: 80),
    //   ),
    // );

    // 5. Selector<T, R>
    // 현재 Widget을 반복하여 Rendering하지 않는다.
    return Center(
      child: Selector<CountProvider, int>(
        selector: (context, provider) => provider.count,
        builder: (context, value, child) {
          return Text(
            '$value',
            style: const TextStyle(fontSize: 80),
          );
        },
      ),
    );

  }
}
1. Provider.of<CountProvider>(context).count.toString()

2. context.watch<CountProvider>().count.toString() 

위 두가지 방법 모두 widget rebuild 발생한다.

3. Consumer객체를 이용해야 Provider객체를 구독하는 Widget이 rebuild되는 것을 방지할 수 있다.

4. context.select<CountProvider, int>() Widget rebuild 발생

5. Selector객체를 이용하면 Consumer객체를 이용한것과 같이 구독하는 Widget이 rebuild되는 것을 방지할 수 있다.

 

Selector와 Consumer의 차이는

Consumer는 Provider객체의 모든 맴버의 변화를 모두 감지하여 Consumer내부 전체가 Rebuild되지만,

Selector는 Provider객체의 특정 맴버의 변화만을 감지하게 할 수 있어 성능이 더 좋다.

 

 

 

context.read<CountProvider>().count.toString() 로 하면 작동안된다.

context.read와 context.watch 기능 차이