CH02 Provider Overview

necessity of provider

  • Widget A ์—์„œ counter ๋ผ๋Š” ๋ฐ์ดํ„ฐ์™€ increment ๋ผ๋Š” ํ•จ์ˆ˜๊ฐ€ ํ•„์š”ํ•œ ์ƒํ™ฉ์ด๊ณ , Widget B ์—์„œ๋Š” counter ๋ผ๋Š” ๋ฐ์ดํ„ฐ๋งŒ ํ•„์š”ํ•œ ์ƒํ™ฉ.

  • Widget A, Widget B ๋ชจ๋‘์—์„œ ๋™์ผํ•œ ๋ฐ์ดํ„ฐ์ธ counter ๊ฐ€ ํ•„์š”ํ•˜๋ฏ€๋กœ ๊ณตํ†ต๋œ ๋ถ€๋ชจ Widget C ์—์„œ ์ •์˜.

  • counter ์™€ increment ์˜ ๊ฒฝ์šฐ Widget C ์—์„œ ์„ ์–ธํ•˜์ง€๋งŒ ์ •์ž‘ ์ œ์–ด๋Š” Widget A ์—์„œ ํ•˜๋ฏ€๋กœ Inversion of control ๋ฐœ์ƒ.

  • Widget B ์— counter ๋ฅผ ๋„˜๊ฒจ์ฃผ๊ธฐ ์œ„ํ•ด์„œ Widget C ์™€ Widget B ์‚ฌ์ด์˜ Widget ์ด ํ•„์š”ํ•˜์ง€๋„ ์•Š์€ counter ๋ผ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ–๊ฒŒ ๋จ.

managing state without provider

  • Counter B ๋งŒ ๋ด์„œ๋Š” ์–ด๋””์„œ rebuilding ์ด ๋ฐœ์ƒํ•˜๋Š”์ง€ ์ถ”์ ํ•˜๊ธฐ๊ฐ€ ์‰ฝ์ง€ ์•Š๋‹ค.

  • ๊ณตํ†ต ์ƒ๋‹จ ๋ถ€๋ชจ Widget ์—์„œ setState() ํ˜ธ์ถœ๋˜๋ฏ€๋กœ ์“ธ๋ฐ์—†์ด ๋” ๋งŽ์€ Widget ์ด rebuild ๋Œ€์ƒ์ด ๋˜์–ด ํผํฌ๋จผ์Šค๊ฐ€ ๋–จ์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค.

๊ฒฐ๊ตญ StateManagement ๋Š” ์•„๋ž˜ ๋‘ ๊ฐ€์ง€ ํ–‰์œ„๊ฐ€ ํ•ต์‹ฌ์ด๋‹ค.

  1. Dependency Injection (Object๋ฅผ Widget Tree ์ƒ์—์„œ ์‰ฝ๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.)

  2. Synchronizing data and UI

  • Provider ๋Š” Widget ์— Widget ์ด ์•„๋‹Œ ๋ฐ์ดํ„ฐ์™€ method ์— ์‰ฝ๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•œ๋‹ค.

  • ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ ๋˜์—ˆ์„ ๋•Œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ๊ทธ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•œ Widget ์— ์ œ๊ณตํ•ด์„œ ํ•„์š”์‹œ rebuild ๋  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

  • ๊ฒฐ๊ตญ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ํ™”๋ฉด์ด ๋ถ„๋ฆฌ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

dependency injection using provider

์ฃผ๋ชฉํ•ด์•ผํ•  ํฌ์ธํŠธ๋Š” ์•„๋ž˜ ๋‘ ๊ฐ€์ง€์ด๋‹ค.

  • Widget Tree ์ƒ์—์„œ class Dog ๋กœ์˜ ์ ‘๊ทผ์ด ์–ผ๋งˆ๋‚˜ ์‰ฝ๊ฒŒ ๊ฐ€๋Šฅํ•œ์ง€

  • ์ด ๋ฐฉ์‹์ด ์ƒ์„ฑ์ž๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋„˜๊ฒจ์ฃผ๋Š” ๊ฒƒ๋ณด๋‹ค ์–ผ๋งˆ๋‚˜ ๋” ๊ฐ„ํŽธํ•œ์ง€

  • Provider ์—ญ์‹œ Widget ์ด๋‹ค.

  • create ํ”„๋กœํผํ‹ฐ์—์„œ Widget ์ด ํ•„์š”๋กœ ํ•˜๋Š” dog ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ ๋‹ค.

  • create ์— assign ๋˜๋Š” ํ•จ์ˆ˜๊ฐ€ return ํ•˜๋Š” object ์— Provider ํ•˜์œ„ Widget ๋“ค์˜ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

  • Provider ์˜ static ํ•จ์ˆ˜์ธ of ๋ฅผ ์ด์šฉํ•˜๋ฉด Widget Tree ๋ฅผ ์œ„๋กœ traverse ํ•˜๋ฉด์„œ ์›ํ•˜๋Š” Type ์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค.

    • Provider.of<Dog>(context) ์™€ ๊ฐ™์€ ์‹์ธ๋ฐ, ์—ฌ๊ธฐ์„œ context ๋ฅผ ์ฃผ๋Š” ์ด์œ ๋Š” context ๋ฅผ ์ด์šฉํ•ด์„œ Widget Tree ๋ฅผ ์œ„๋กœ ํƒ์ƒ‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  • <T> ๊ฐ€ ๊ฐ™์€ ๋‘ ๊ฐœ ์ด์ƒ์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ์—๋Š” ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.

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

  @override
  Widget build(BuildContext context) {
    return Provider<Dog>(
      create: (context) => Dog(
        name: 'Sun',
        breed: 'Bulldog',
        age: 3,
      ),
      child: MaterialApp(
        title: 'Provider 02',
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const MyHomePage(),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Provider 02'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              '- name: ${Provider.of<Dog>(context).name}',
              style: TextStyle(fontSize: 20.0),
            ),
            SizedBox(height: 10.0),
            BreedAndAge(),
          ],
        ),
      ),
    );
  }
}

ChangeNotifier & addListener

  • addListener ๋Š” ์ž๋™์œผ๋กœ dispose ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ˆ˜๋™์œผ๋กœ ์ง์ ‘ dispose ์‹œ์ผœ์ค˜์•ผํ•œ๋‹ค.

  • ์ฝ”๋“œ ๋‚ด Provider ์ œ๊ฑฐ ํ›„ ChangeNotifier ์™€ addListener ๋ฅผ ๊ฐ™์ด ์“ฐ๋ฉด์„œ Listener ๊ฐ€ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ์„ ๋ณด๊ณ , ๋ถˆํŽธํ•œ ์ ์„ ์‚ดํŽด๋ณด์•˜๋‹ค.(Provider ์‚ญ์ œ ํ›„ ์ƒ์„ฑ์ž๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋„˜๊ฒจ์ฃผ์—ˆ๋‹ค.)

ChangeNotifierProvider

  • Provider.of<T>(context) ๋กœ ์ธ์Šคํ„ด์Šค๋ฅผ ํƒ์ƒ‰ & ์ ‘๊ทผ์‹œ ํ•ด๋‹น ๋ฐ์ดํ„ฐ์˜ ๋ณ€๊ฒฝ์„ listen ํ•ด์•ผํ• ์ง€์˜ ํ•„์š”์„ฑ(๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์— ๋”ฐ๋ฅธ UI rebuild)์ด ์žˆ์„๋•Œ์™€ ์—†์„๋•Œ ๊ฐ๊ฐ ์˜ต์…˜๊ฐ’์ด ๋‹ค๋ฆ„์„ ์ธ์ง€ํ•œ๋‹ค.

    • Provider.of<Dog>(context).age

    • Provider.of<Dog>(context, listen: false).age

๊ฒฐ๋ก ์ ์œผ๋กœ ์•„๋ž˜์™€ ๊ฐ™๋‹ค. ๊ฒฐ๊ตญ ์•„๋ž˜ ๋‘ ๊ฐ€์ง€๊ฐ€ State Management ์ด๋‹ค.

  • ChangeNotifierProvider ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํ•„์š”๋กœ ํ•˜๋Š” Widget ์ด dependency injection ์„ ๋ฐ›์Œ์œผ๋กœ์จ ๋ฐ์ดํ„ฐ(์ธ์Šคํ„ด์Šค)์— ์‰ฝ๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.

  • ๋ฐ์ดํ„ฐ์˜ ๋ณ€๊ฒฝ์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์ด ๋ฐ์ดํ„ฐ์˜ ๋ณ€ํ™”์— ๋งž์ถฐ์„œ ์„ ํƒ์ ์œผ๋กœ Widget ์„ rebuild ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.

import 'package:flutter/foundation.dart';

class Dog with ChangeNotifier {
  final String name;
  final String breed;
  int age;
  Dog({
    required this.name,
    required this.breed,
    this.age = 1,
  });

  void grow() {
    age++;
    notifyListeners();
  }
}

ChangeNotifier ์˜ notifyListeners() ๋Š” ์ด๋ฅผ ๊ตฌ๋…ํ•˜๊ณ  ์žˆ๋Š” ๋ชจ๋“  listener ๋“ค์—๊ฒŒ ๋ณ€๊ฒฝ ์‚ฌ์‹ค์„ ์•Œ๋ฆฌ๊ณ  rebuild ํ•˜๋„๋ก ๋งŒ๋“ ๋‹ค. ๊ณต์‹๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์ž.

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

import 'models/dog.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Dog>(
      create: (context) => Dog(name: 'dog04', breed: 'breed04'),
      child: MaterialApp(
        title: 'Provider 04',
        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) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Provider 04'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              '- name: ${Provider.of<Dog>(context).name}',
              style: TextStyle(fontSize: 20.0),
            ),
            SizedBox(height: 10.0),
            BreedAndAge(),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(
          '- breed: ${Provider.of<Dog>(context).breed}',
          style: TextStyle(fontSize: 20.0),
        ),
        SizedBox(height: 10.0),
        Age(),
      ],
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(
          '- age: ${Provider.of<Dog>(context).age}',
          style: TextStyle(fontSize: 20.0),
        ),
        SizedBox(height: 20.0),
        ElevatedButton(
          onPressed: () => Provider.of<Dog>(context, listen: false).grow(),
          child: Text(
            'Grow',
            style: TextStyle(fontSize: 20.0),
          ),
        ),
      ],
    );
  }
}

read, watch, select extension methods

  • 4.1 ๋ถ€ํ„ฐ Provider ๋ฅผ ๋” ๊ฐ„ํŽธํ•˜๊ฒŒ ์“ธ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” extension ์ด ๋„์ž…๋จ.

  • ์ผ์ข…์˜ shortCut ์ด๋ผ๊ณ  ๋ณด๋ฉด ๋˜๊ณ , ๊ทธ๋ƒฅ ์“ฐ์ง€ ๋ง๊ณ  ์›ํ˜•๋“ค์ด ๋ญ”์ง€ ์ดํ•ดํ•˜๊ณ  ์žˆ๋Š”๊ฒŒ ์ค‘์š”ํ•˜๋‹ค.

  • context.select ๋Š” ๋‹ค์ˆ˜์˜ property ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” object ์˜ ํŠน์ • property ์˜ ๋ณ€ํ™”๋งŒ listen ํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

    • context.watch ๋Š” ํŠน์ • property ํ•˜๋‚˜๋งŒ ๋ฐ”๋€Œ์–ด๋„ rebuild ๋ฅผ ํ•˜๋Š” ๊ฒƒ์— ๋ฐ˜ํ•ด context.select ๋Š” listen ํ•˜๊ณ  ์‹ถ์€ ๊ฒƒ๋งŒ ์„ ๋ณ„์ ์œผ๋กœ listen ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.(ํผํฌ๋จผ์Šค ๊ณ ๋ ค ์ธก๋ฉด์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.)

context.watch vs context.select

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

import 'models/dog.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Dog>(
      create: (context) => Dog(name: 'dog05', breed: 'breed05', age: 3),
      child: MaterialApp(
        title: 'Provider 05',
        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) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Provider 05'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              '- name: ${context.watch<Dog>().name}',
              style: TextStyle(fontSize: 20.0),
            ),
            SizedBox(height: 10.0),
            BreedAndAge(),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(
          '- breed: ${context.select<Dog, String>((Dog dog) => dog.breed)}',
          style: TextStyle(fontSize: 20.0),
        ),
        SizedBox(height: 10.0),
        Age(),
      ],
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(
          '- age: ${context.select<Dog, int>((Dog dog) => dog.age)}',
          style: TextStyle(fontSize: 20.0),
        ),
        SizedBox(height: 20.0),
        ElevatedButton(
          onPressed: () => context.read<Dog>().grow(),
          child: Text(
            'Grow',
            style: TextStyle(fontSize: 20.0),
          ),
        ),
      ],
    );
  }
}

Multiple Provider

  • ํ•˜๋‚˜์˜ Provider ๋ฅผ ์‚ฌ์šฉํ•˜๋”๋ผ๋„ MultiProvider ๋ฅผ ์‚ฌ์šฉํ•ด๋†“์œผ๋ฉด ํ™•์žฅ์„ฑ์ด ์žˆ๋‹ค.

Future Provider

  • ๊ฐ•์‚ฌ๋‹˜์€ ๊ฐœ์ธ์ ์œผ๋กœ ์“ธ ์ผ์ด ๊ฑฐ์˜ ์—†์—ˆ๋‹ค๊ณ  ํ•จ.

  • ๋งŒ์•ฝ ์“ธ ์ผ์ด ์žˆ์œผ๋ฉด FutureBuilder ๋ฅผ ์“ธ ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ํ•จ.

  • Widget Tree ์—๋Š” ๋นŒ๋“œ๊ฐ€ ๋˜์—ˆ๋Š”๋ฐ ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” ๊ฐ’์ด ์•„์ง ์ค€๋น„๊ฐ€ ๋˜์ง€ ์•Š์•˜์„๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

  • Future ๊ฐ€ resolve ๋˜์ง€ ์•Š์•˜์„๋•Œ, initialData ๋กœ build ํ•˜๊ณ  Future ๊ฐ€ resolve ๋˜๋ฉด rebuild ๋œ๋‹ค. ์ด 2๋ฒˆ build ๊ฐ€ ๋œ๋‹ค๋Š” ๊ฒƒ. (๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ฒˆ build ๋ฅผ ์›ํ•œ๋‹ค๋ฉด StreamProvider ์‚ฌ์šฉํ•œ๋‹ค.)

class Babies {
  final int age;
  Babies({
    required this.age,
  });

  Future<int> getBabies() async {
    await Future.delayed(Duration(seconds: 3));

    if (age > 1 && age < 5) {
      return 4;
    } else if (age <= 1) {
      return 0;
    } else {
      return 2;
    }
  }
}
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<Dog>(
          create: (context) => Dog(name: 'dog06', breed: 'breed06', age: 3),
        ),
        FutureProvider<int>(
          initialData: 0,
          create: (context) {
            final int dogAge = context.read<Dog>().age;
            final babies = Babies(age: dogAge);
            return babies.getBabies();
          },
        ),
      ],
      child: MaterialApp(
        title: 'Provider 06',
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const MyHomePage(),
      ),
    );
  }
}
  • ์œ ์‹ฌํžˆ ๋ด๋‘ฌ์•ผํ•  ๋ถ€๋ถ„์€ FutureProvider ์˜ ๋Œ€์ƒ์€ Future ์ด๊ธฐ๋งŒ ํ•˜๋ฉด ๋˜๋Š” ๊ฒƒ์ด๋ผ๋Š” ๊ฒƒ์ด๋‹ค. ์ฆ‰, ์ธ์Šคํ„ด์Šค ๋‚ด์— ์ •์˜๋œ method ๋ฅผ ๋Œ€์ƒ์œผ๋กœ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ธ์Šคํ„ด์Šค ์ „์ฒด๋ฅผ return ํ•  ํ•„์š”๊ฐ€ ์—†๊ณ  ํŠน์ • ๋ฉ”์†Œ๋“œ๋งŒ ์ œ๊ณตํ•˜๋Š” ํ˜•ํƒœ๋กœ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

  • FutureProvider ๋‚ด์—์„œ context.read ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. ์ด์œ ๋Š” ์œ„์—์„œ ChangeNotifierProvider ๊ฐ€ FutureProvider ๋ณด๋‹ค ์ƒ์œ„ Widget ์œผ๋กœ ์ด๋ฏธ ์‚ฌ์šฉ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

StreamProvider

  • ๊ฐ•์‚ฌ๋‹˜ ๊ฐœ์ธ์ ์œผ๋กœ๋Š” FutureProvider ๋ณด๋‹ค StreamProvider ๋ฅผ ์‚ฌ์šฉํ•  ์ผ์ด ๋” ๋งŽ์•˜๋‹ค๊ณ  ํ•จ.

  • ์—ฐ์†๋˜๋Š” Future value ๋ฅผ ํƒ€๊ฒŸ์œผ๋กœ ํ•˜๋Š” Provider

        StreamProvider<String>(
          initialData: 'Bark 0 times',
          create: (context) {
            final int dogAge = context.read<Dog>().age;
            final babies = Babies(age: dogAge * 2);
            return babies.bark();
          },
        ),
  • create ๋Š” ํ•œ ๋ฒˆ๋งŒ called ๋˜๊ธฐ ๋•Œ๋ฌธ์— watch ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ๋…ผ๋ฆฌ์ ์œผ๋กœ๋„ read ๊ฐ€ ๋งž๋‹ค.

class Babies {
  final int age;
  Babies({
    required this.age,
  });

  Future<int> getBabies() async {
    await Future.delayed(Duration(seconds: 3));

    if (age > 1 && age < 5) {
      return 4;
    } else if (age <= 1) {
      return 0;
    } else {
      return 2;
    }
  }

  Stream<String> bark() async* {
    for (int i = 1; i < age; i++) {
      await Future.delayed(Duration(seconds: 2));
      yield 'Bark $i times';
    }
  }
}
  • async ์™€ async* ์˜ ์ฐจ์ด๋Š” ์—ฌ๊ธฐ๋ฅผ ์ฐธ๊ณ ํ•œ๋‹ค. ๊ฐ„๋žตํžˆ ์„ค๋ช…ํ•˜๋ฉด return ํƒ€์ž…์ด Stream<T> ๋ฅผ ์ƒ์‚ฐํ•˜๋ฉด(+ method ๋ฅผ ๋– ๋‚˜์ง€ ์•Š์œผ๋ฉด์„œ) async* ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

  • ์ด๋Ÿฐ ๊ฒฝ์šฐ return ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. method ๋ฅผ ๋– ๋‚˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ข…๊ฒฐ์ฒ˜๋ฆฌ ํ•˜๋ฉด ์•ˆ๋œ๋‹ค. yield ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, yield ์˜ ์‚ฌ์ „์  ์˜๋ฏธ๋Š” โ€˜์ƒ์‚ฐํ•˜๋‹คโ€™ ๋ผ๋Š” ๋œป์ด๋‹ค.

Consumer

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Provider 08'),
      ),
      body: Consumer<Dog>(
        builder: (BuildContext context, Dog dog, Widget? child) {
          return Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              mainAxisSize: MainAxisSize.min,
              children: [
                child!,
                SizedBox(height: 10.0),
                Text(
                  '- name: ${dog.name}',
                  style: TextStyle(fontSize: 20.0),
                ),
                SizedBox(height: 10.0),
                BreedAndAge(),
              ],
            ),
          );
        },
        child: Text(
          'I like dogs very much',
          style: TextStyle(fontSize: 20.0),
        ),
      ),
    );
  }
}
  • Consumer ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ (BuildContext context, Dog dog, Widget? child) ์ค‘ child ๋Š” builder ๋‚ด์—์„œ rebuild ๋  ํ•„์š”๊ฐ€ ์—†๋Š” Widget ์ด ์žˆ๋Š” ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•ด์„œ ์œ„์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•œ๋‹ค. ์œ„ ์˜ˆ์ œ์—์„œ๋Š” ์–ด๋–ค ๊ฒฝ์šฐ๋“  rebuild ๋  ํ•„์š”๊ฐ€ ์—†๋Š” Text Widget ์„ child ๋กœ ๋นผ์ฃผ์—ˆ๋‹ค.

  • ์ดํ•ด๊ฐ€ ์•ˆ๊ฐ€๋ฉด ์œ ํˆฌ๋ธŒ์— ๋‚จ์•„์žˆ๋Š” ๊ฐ•์˜ ์—์„œ ์ด ๋ถ€๋ถ„์„ ๋‹ค์‹œ ๋ณด๊ณ  ์˜ค์ž.

Consumer, builder, ProviderNotFoundException

  • Consumer ๋ฅผ ์“ฐ๋“  builder ๋ฅผ ์“ฐ๋“  ๋‘˜ ์ค‘ ํŽธํ•œ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜์ž.

Selector

  • Consumer ์™€ ์œ ์‚ฌํ•œ๋ฐ Consumer ๋ณด๋‹ค ๋” ์„ธ์„ธํ•œ ์ปจํŠธ๋กค์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ค€๋‹ค.

  • ์•ž์—์„œ ํ•™์Šตํ•œ context.select<T, R>((R selector(T value))) => R ๊ณผ ์œ ์‚ฌํ•œ ๊ฐœ๋…์ด๋‹ค.

  • ์ง€๊ธˆ ํ•™์Šตํ•œ Selector ๋ผ๋Š” Widget ๊ณผ context.select<T, R>((R selector(T value))) => R ๋Š” ๊ณตํ†ต์ ์œผ๋กœ ํŠน์ • property ์˜ ๋ณ€๊ฒฝ์— ๋Œ€ํ•ด์„œ react ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ.

ProviderNotFoundException ๋” ์•Œ์•„๋ณด๊ธฐ, Builder Widget

  • extension ์˜ context ๋Š” build method ์˜ buildContext ์ž„์„ ๋ช…์‹ฌ. ๊ทธ๋ž˜์„œ ๊ทธ๊ฑธ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ฉด ProviderNotFoundException ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ๋ฐ–์— ์—†๋‹ค.

  • ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด โ€˜Consumer, builder, ProviderNotFoundExceptionโ€™ ์—์„œ ํ•™์Šตํ•œ builder ํ”„๋กœํผํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ child ๋กœ Widget ์„ ๋”ฐ๋กœ ๋นผ์„œ ๋ณ„๋„์˜ Widget ์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

  • Builder Widget ์˜ context ๋Š” ์กฐ์ƒ Widget ์˜ BuildContext ๊ฐ€ ์•„๋‹ˆ๊ณ  Builder Widget ์ž์ฒด์˜ BuildContext ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์‚ฌ์šฉํ•ด์„œ Widget Tree ๋ฅผ ์œ„๋กœ ํƒ์ƒ‰ํ•˜๋ฉด ์›ํ•˜๋Š” T(type)์— ๋Œ€ํ•œ Provider ๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค.

  • builder ํ”„๋กœํผํ‹ฐ๋„ ์ด๋ฏธ ํ•™์Šตํ•œ ๋ฐ”์™€ ๊ฐ™์ด syntax sugar ๋กœ Builder Widget ์ด ์‚ฌ์šฉ๋œ ๊ฒƒ๊ณผ ๋™์ผํ•˜๋‹ค.

Provider Access - Anonymous route access

  • Consumer, builder, ProviderNotFoundException ์—์„œ Error Message 2 ์˜ ๊ฒฝ์šฐ๋ฅผ ํ•™์Šตํ•จ.

  • Consumer, builder, ProviderNotFoundException ์—์„œ Error Message 3 ์˜ ๊ฒฝ์šฐ parent-child ๊ด€๊ณ„์—์„œ child ๋กœ์„œ Widget Tree ๋ฅผ ์œ„๋กœ ํƒ์ƒ‰ํ•˜๋ฉด์„œ Provider ๋ฅผ ์ฐพ์•„ํ•˜๋Š”๋ฐ ํƒ์ƒ‰์‹œ ์‚ฌ์šฉํ•˜๋Š” context ๊ฐ€ ํ•ด๋‹น child ์˜ context ๊ฐ€ ์•„๋‹ˆ๋ผ, Provider ์œ„์น˜์™€ ๊ฐ™๊ฑฐ๋‚˜ ํ˜น์€ ๊ทธ ์ด์ƒ์˜ level ์˜ context ์ผ ๊ฒฝ์šฐ ๋ฐœ์ƒํ•œ ์—๋Ÿฌ.

  • Error Message 2 ๋„ parent-child ๊ด€๊ณ„์—์„œ child ๋กœ์„œ ์œ„๋กœ ํƒ์ƒ‰์ด ๋ฐœ์ƒ๋˜์–ด์•ผ ํ•˜๋Š”๋ฐ, ๋‹ค๋ฅธ route ์—์„œ ํƒ์ƒ‰ํ–ˆ์œผ๋ฏ€๋กœ(์—ฌ๊ธฐ์„œ ์˜ˆ์ œ๋Š” sibling ๊ด€๊ณ„) ๋ชป์ฐพ๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒ.

  • ์ด ๋•Œ์˜ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€ value constructor ์ด๋‹ค. ์ƒˆ๋กœ์šด sub-tree ์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” class ์— ๋Œ€ํ•œ access ๋ฅผ ์ œ๊ณตํ•  ๋•Œ ์‚ฌ์šฉ. value constructor ๋Š” class ๋ฅผ ์ž๋™์œผ๋กœ close ํ•˜์ง€ ์•Š๋Š”๋‹ค.

onPressed: () {
  Navigator.push(
    context,
    MaterialPageRoute(builder: (_) {
      return ChangeNotifierProvider.value(
        value: context.read<Counter>(),
        child: ShowMeCounter(),
      );
    }),
  );
},
  • context.read ์‹œ ์‚ฌ์šฉํ•˜๋Š” context ๋Š” Navigator.push ์˜ context ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. MaterialPageRoute ์˜ context ๊ฐ€ ์•„๋‹˜์„ ๋ช…์‹ฌ. ์œ„๋กœ ํƒ์ƒ‰ํ•˜์—ฌ Provider ๋ฅผ ์ฐพ์•„์•ผ ํ•˜๋Š”๋ฐ, MaterialPageRoute ์˜ ๊ฒฝ์šฐ sibling ์ด๊ธฐ ๋•Œ๋ฌธ์— MaterialPageRoute ์˜ context ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ Provider ๋ฅผ ์—ฌ์ „ํžˆ ๋ชป์ฐพ๋Š”๋‹ค.

์•„๋ž˜๋Š” ์ฝ”๋“œ ์ „์ฒด.

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

import 'counter.dart';
import 'show_me_counter.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Anonymous Route',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ChangeNotifierProvider<Counter>(
        create: (context) => Counter(),
        child: const MyHomePage(),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              child: Text(
                'Show Me Counter',
                style: TextStyle(fontSize: 20.0),
              ),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (_) {
                    return ChangeNotifierProvider.value(
                      value: context.read<Counter>(),
                      child: ShowMeCounter(),
                    );
                  }),
                );
              },
            ),
            SizedBox(height: 20.0),
            ElevatedButton(
              child: Text(
                'Increment Counter',
                style: TextStyle(fontSize: 20.0),
              ),
              onPressed: () {
                context.read<Counter>().increment();
              },
            )
          ],
        ),
      ),
    );
  }
}

Provider Access - Named route access

๊ณ„์†ํ•ด์„œ Provider Access ์— ๋Œ€ํ•ด์„œ ํ•™์Šตํ•œ๋‹ค. ๋ฐ”๋กœ ์œ„์—์„œ๋Š” Anonymous route access ๋ฅผ ์‚ดํŽด๋ณด์•˜๋‹ค. ์™œ ์œ„์˜ ์ผ€์ด์Šค๊ฐ€ Anonymous route ๋ƒ๋ฉด Navigator.push ๋ฅผ ์‚ฌ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ด๋ฒˆ์—๋Š” Named route ์—์„œ์˜ Provider ์— ๋Œ€ํ•œ access ๋ฅผ ํ•™์Šตํ•œ๋‹ค.

๋ณต์Šต ์ฐจ์›์—์„œ ์งš๊ณ  ๋„˜์–ด๊ฐ€์ž๋ฉด, Anonymous route ๊ฐ€ Anonymous ์ธ ์ด์œ ๋Š” ๋ง ๊ทธ๋Œ€๋กœ ์ด๋ฆ„์ด ์—†์–ด์„œ๋‹ค. ๋ฐ˜๋Œ€๋กœ ์ด๋ฆ„์ด ์žˆ๋‹ค๋Š” ๋ง์€ MaterialApp ์—์„œ routes property ๋‚ด์— route ์ฃผ์†Œ์™€ Screen ์œผ๋กœ ์‚ฌ์šฉํ•  Widget ์ด ๋“ฑ๋ก๋˜์–ด ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค. ๋ฐ”๋กœ ์œ„ Anonymous route ๋Š” Navigator.push ๋กœ route ์„ค์ •์„ ๋”ฐ๋กœ ํ•ด์ฃผ์ง€ ์•Š๊ณ  ํ™”๋ฉด Stack ์„ ๋ฐ”๋กœ ์Œ“์•„ ์˜ฌ๋ฆฐ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— Anonymous ์˜€๋‹ค.

routes: {
  '/': (context) => ChangeNotifierProvider.value(
        value: _counter,
        child: MyHomePage(),
      ),
  '/counter': (context) => ChangeNotifierProvider.value(
        value: _counter,
        child: ShowMeCounter(),
      ),
},dart

์œ„์™€ ๊ฐ™์ด routes ์„ค์ • ๋ถ€๋ถ„์—์„œ ChangeNotifierProvider.value ๋กœ ๊ฐ์‹ธ์ฃผ๋ฉด ๋˜๋Š”๋ฐ, ์ฃผ์˜ํ•  ์ ์ด ์žˆ๋‹ค. create ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์‚ฌ์šฉํ•  ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•  ๊ฒฝ์šฐ ์ž๋™์œผ๋กœ dispose ๋ฅผ ํ•ด์ฃผ๋Š”๋ฐ ์ง€๊ธˆ ์œ„์˜ ๊ฒฝ์šฐ create ๋กœ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š์•˜๊ณ , ์•„๋ž˜ ์ „์ฒด ์ฝ”๋“œ์— ๋‚˜์™€์žˆ๋‹ค์‹œํ”ผ state ์—์„œ ์ธ์Šคํ„ด์Šค๋ฅผ ์ง์ ‘ ์ƒ์„ฑํ•ด์คฌ๋‹ค.

๋”ฐ๋ผ์„œ dispose ์—ญ์‹œ ์•„๋ž˜์™€ ๊ฐ™์ด ์ง์ ‘ ํ•ด์ค˜์•ผํ•œ๋‹ค.

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

์•„๋ž˜๋Š” ์ „์ฒด ์ฝ”๋“œ์ด๋‹ค.

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

import 'counter.dart';
import 'show_me_counter.dart';

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

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

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

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

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

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              child: Text(
                'Show Me Counter',
                style: TextStyle(fontSize: 20.0),
              ),
              onPressed: () {
                Navigator.pushNamed(context, '/counter');
              },
            ),
            SizedBox(height: 20.0),
            ElevatedButton(
              child: Text(
                'Increment Counter',
                style: TextStyle(fontSize: 20.0),
              ),
              onPressed: () {
                context.read<Counter>().increment();
              },
            )
          ],
        ),
      ),
    );
  }
}

Provider Access - Generated route access, Global access

ํ•™์Šตํ•œ Provider Access ์— ๋Œ€ํ•ด์„œ ๋ฌด์—‡์„ ํ–ˆ๊ณ , ๋ฌด์—‡์ด ๋‚จ์•˜๋Š”์ง€ ๋‹ค์‹œ ์งš์–ด๋ณธ๋‹ค.

  • Provider Access

    • Anonymous route access Navigator.push ๋‚ด์—์„œ ์‚ฌ์šฉํ•  screen ์„ value constructor ๋กœ ๊ฐ์‹ธ์ฃผ๋˜, context ๋ฅผ Navigator.push ์˜ ๊ฒƒ์„ ์‚ฌ์šฉํ•œ๋‹ค.

    • Named route access routes ์„ค์ • ๋ถ€๋ถ„์—์„œ value constructor ๋กœ ๊ฐ์‹ธ์ฃผ๋˜, ์‚ฌ์šฉํ•  ์ธ์Šคํ„ด์Šค๊ฐ€ create ๋กœ ๋งŒ๋“ค์–ด์ง„๊ฒŒ ์•„๋‹ˆ๋ผ ์ž๋™์œผ๋กœ dispose ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ง์ ‘ ํ•ด๋‹น ์ธ์Šคํ„ด์Šค๋ฅผ dispose ์ฒ˜๋ฆฌ ํ•ด์ค€๋‹ค.

    • Generated route access Named route access ๊ณผ ๊ฑฐ์˜ ๋™์ผํ•˜๋‹ค. ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ๋ณด์ž.

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Anonymous Route',
      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;
        }
      },
    );
  }

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

Global access ์˜ ๊ฒฝ์šฐ ์•„๋ž˜์™€ ๊ฐ™์ด ์ตœ ์ƒ๋‹จ์— Provider ๋กœ ๊ฐ์‹ธ๋‘๋ฉด ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

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

ํ•˜์ง€๋งŒ ๋ถ„์—… ํ™˜๊ฒฝ ๋ฐ ์œ ์ง€๋ณด์ˆ˜ ๋“ฑ์„ ๊ณ ๋ คํ•  ๋•Œ ์ ์ ˆ์ง€ ๋ชปํ•œ ๋ฐฉ์‹์ด๋‹ค. ์‹ค๋ฌด์—์„œ ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•  ์ธ์Šคํ„ด์Šค๊ฐ€ ์žˆ์„๊นŒ ์‹ถ๋‹ค. ์ „์— ๋กœ๋”ฉ์Šคํ”ผ๋„ˆ๋ฅผ ์ด๋ ‡๊ฒŒ global access ๊ฐ€๋Šฅํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•ด์„œ ์‚ฌ์šฉํ–ˆ๋˜ ๊ฒƒ ๊ฐ™๊ธฐ๋„ ํ•˜๊ณ .. ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ๋“ค์„ ํƒ๊ตฌํ•  ๋•Œ ํ™•์ธํ•ด๋ณด๋„๋ก ํ•˜์ž.

ProxyProvider - ๊ฐœ์š” 1

  • Provider ์—์„œ ๋‹ค๋ฅธ Provider ์˜ ๊ฐ’์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์— ProxyProvider ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

  • ๋‹ค๋ฅธ Provider ์— ๊ธฐ๋ฐ˜ํ•˜์—ฌ value ๋ฅผ ๋งŒ๋“œ๋Š” Provider (A provider that builds a value based on other provider)

  • ๋‹ค๋ฅธ Provider ์˜ ๊ฐ’์— ์˜์กดํ•ด์„œ value ๋ฅผ ๋งŒ๋“œ๋Š” Provider ์ธ ๊ฒƒ์ด๋‹ค.

  • ๊ผญ ๋‹ค๋ฅธ Provider ์— ์˜์กดํ•˜์ง€ ์•Š๋”๋ผ๋„, ์–ด๋–ค ๋ณ€ํ•˜๋Š” ๊ฐ’์— ์˜์กดํ•ด์•ผ ํ•œ๋‹ค๋ฉด ProxyProvider ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

  • ProxyProvider ๋„ ์ข…๋ฅ˜๊ฐ€ ๋งŽ๋‹ค.

  • create ๋Š” optional ์ด๊ณ  update ๊ฐ€ required ์ด๋‹ค.

  • ProxyProvider ๊ฐ€ ์ž์ฒด์ ์œผ๋กœ ๋งŒ๋“ค๊ณ  ๊ด€๋ฆฌํ•ด์•ผํ•  value ๊ฐ€ ์žˆ๋‹ค๋ฉด create ๊ฐ€ ํ•„์š”ํ•˜์ง€๋งŒ, ๊ทธ๋Ÿฐ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด ๋งŒ๋“ค ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  • ๊ทธ๋ž˜์„œ create ๋Š” ํ•œ ๋ฒˆ๋งŒ called, ๋˜๊ณ  update ๋Š” ์—ฌ๋Ÿฌ๋ฒˆ called ๋œ๋‹ค.

  • ProxyProvider ๋Š” ๋‹ค๋ฅธ Provider ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ฐ’์— ์˜์กดํ•˜๋Š”๋ฐ, ๋‹ค๋ฅธ Provider ๊ฐ€ ์ œ๊ณตํ•˜๋Š” value ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ์ด๋ฅผ ๋ฐ”๋ผ๋ณด๊ณ  ์žˆ๋Š”(์˜์กดํ•˜๊ณ  ์žˆ๋Š”) ProxyProvider ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ฐ’์ด ๋ฐ”๋€Œ์–ด์•ผ ํ•˜๋ฏ€๋กœ update ๊ฐ€ ์—ฌ๋Ÿฌ๋ฒˆ called ๋˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ๋‹น์—ฐํ•œ ์ผ์ด๋‹ค.

update ๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ๊ฒฝ์šฐ๋“ค์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  • ProxyProvider ๊ฐ€ ๊ฐ€์žฅ ์ฒ˜์Œ์œผ๋กœ ์˜์กดํ•˜๊ณ  ์žˆ๋Š” ๋‹ค๋ฅธ Provider ์˜ ๊ฐ’์„ ์–ป์€ ๊ฒฝ์šฐ

  • ProxyProvider ๊ฐ€ ์˜์กดํ•˜๊ณ  ์žˆ๋Š” ๋‹ค๋ฅธ Provider ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ฐ’์ด ๋ณ€๊ฒฝ๋œ ๊ฒฝ์šฐ

  • ProxyProvider ๊ฐ€ rebuild ๋˜๋Š” ๊ฒฝ์šฐ

ProxyProvider - ๊ฐœ์š” 2

  • The instance of MyChangeNotifier is updated whenever myModel changes.

  • The same instance of MyChangeNotifier is used again and again, not created again. (์ƒˆ๋กœ ๋งŒ๋“ค์–ด์ง€์ง€ ์•Š๊ณ  ํ•œ๋ฒˆ ๋งŒ๋“ค์–ด์ง„ MyChangeNotifier ๊ฐ€ ๊ณ„์† ์‚ฌ์šฉ๋œ๋‹ค.)

ProxyProvider - ์‹ค์Šต & ์˜ˆ์ œ


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

class Translations {
  const Translations(this._value);
  final int _value;

  String get title => 'You clicked $_value times';
}

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

  @override
  _WhyProxyProvState createState() => _WhyProxyProvState();
}

class _WhyProxyProvState extends State<WhyProxyProv> {
  int counter = 0;

  void increment() {
    setState(() {
      counter++;
      print('counter: $counter');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Why ProxyProvider'),
      ),
      body: Center(
        child: Provider<Translations>(
          create: (_) => Translations(counter),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ShowTranslations(),
              SizedBox(height: 20.0),
              IncreaseButton(increment: increment),
            ],
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final title = Provider.of<Translations>(context).title;

    return Text(
      title,
      style: TextStyle(fontSize: 28.0),
    );
  }
}

class IncreaseButton extends StatelessWidget {
  final VoidCallback increment;
  const IncreaseButton({
    Key? key,
    required this.increment,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: increment,
      child: Text(
        'INCREASE',
        style: TextStyle(fontSize: 20.0),
      ),
    );
  }
}
  • create ๋Š” ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰๋˜๋ฏ€๋กœ increment ์— ์˜ํ•ด์„œ counter ๊ฐ’์ด ์ฆ๊ฐ€ํ•ด๋„ ๊ฒฐ๊ตญ create: (_) => Translations(counter) ์—์„œ ๋”ฑ ํ•œ๋ฒˆ ๋งŒ๋“ค์–ด์ง„ ์ธ์Šคํ„ด์Šค๋Š” ๋งŒ๋“ค์–ด์ง€๋˜ ๋‹น์‹œ์˜ counter ๋กœ ๋งŒ๋“ค์–ด์กŒ๊ธฐ ๋•Œ๋ฌธ์— final title = Provider.of<Translations>(context).title; ์˜ ๊ฐ’์€ create ๋•Œ ๋งŒ๋“ค์–ด์ง„ ์ธ์Šคํ„ด์Šค ๊ทธ๋Œ€๋กœ๋‹ค.

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

class Translations {
  const Translations(this._value);
  final int _value;

  String get title => 'You clicked $_value times';
}

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

  @override
  _ProxyProvUpdateState createState() => _ProxyProvUpdateState();
}

class _ProxyProvUpdateState extends State<ProxyProvUpdate> {
  int counter = 0;

  void increment() {
    setState(() {
      counter++;
      print('counter: $counter');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ProxyProvider update'),
      ),
      body: Center(
        child: ProxyProvider0<Translations>(
          update: (_, __) => Translations(counter),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ShowTranslations(),
              SizedBox(height: 20.0),
              IncreaseButton(increment: increment),
            ],
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final title = Provider.of<Translations>(context).title;

    return Text(
      title,
      style: TextStyle(fontSize: 28.0),
    );
  }
}

class IncreaseButton extends StatelessWidget {
  final VoidCallback increment;
  const IncreaseButton({
    Key? key,
    required this.increment,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: increment,
      child: Text(
        'INCREASE',
        style: TextStyle(fontSize: 20.0),
      ),
    );
  }
}
  • ์ œ๊ณตํ•ด์•ผํ•  ๊ฐ’์ด ๊ฐ€๋ณ€์ ์ธ ๊ฐ’์ธ counter ์— ์˜์กดํ•˜๋Š” ์ธ์Šคํ„ด์Šค์ด๋ฏ€๋กœ ProxyProvider ๋ฅผ ์‚ฌ์šฉํ•ด์ค€ ์ฝ”๋“œ์ด๋‹ค.

  • ์ด๋ฏธ counter ๊ฐ’์ด 0 ์œผ๋กœ ์ดˆ๊ธฐํ™” ๋˜์–ด ์žˆ์–ด์„œ, create ๊ฐ€ ํ•„์š”๊ฐ€ ์—†๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ œ๊ณตํ•ด์•ผํ•  ๊ฐ€๋ณ€ ์ธ์Šคํ„ด์Šค๊ฐ€ ํ•˜๋‚˜๋‹ค. ๊ทธ๋ž˜์„œ ProxyProvider0 ๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.(๊ณต์‹๋ฌธ์„œ ์ฐธ๊ณ )

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

class Translations {
  late int _value;

  void update(int newValue) {
    _value = newValue;
  }

  String get title => 'You clicked $_value times';
}

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

  @override
  _ProxyProvCreateUpdateState createState() => _ProxyProvCreateUpdateState();
}

class _ProxyProvCreateUpdateState extends State<ProxyProvCreateUpdate> {
  int counter = 0;

  void increment() {
    setState(() {
      counter++;
      print('counter: $counter');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ProxyProvider create/update'),
      ),
      body: Center(
        child: ProxyProvider0<Translations>(
          create: (_) => Translations(),
          update: (_, Translations? translations) {
            translations!.update(counter);
            return translations;
          },
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ShowTranslations(),
              SizedBox(height: 20.0),
              IncreaseButton(increment: increment),
            ],
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final title = context.watch<Translations>().title;

    return Text(
      title,
      style: TextStyle(fontSize: 28.0),
    );
  }
}

class IncreaseButton extends StatelessWidget {
  final VoidCallback increment;
  const IncreaseButton({
    Key? key,
    required this.increment,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: increment,
      child: Text(
        'INCREASE',
        style: TextStyle(fontSize: 20.0),
      ),
    );
  }
}
  • create ์—์„œ ์ƒ์„ฑ์„ ๋จผ์ € ํ•œ ์ผ€์ด์Šค๋‹ค.

  • update ์—์„œ create ์—์„œ ์ƒ์„ฑ๋œ ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„ .update() ๋ฅผ call ํ•˜๊ณ  return ํ•œ๋‹ค.

  • ์—ฌ๊ธฐ์„œ ๋˜ update ๋ฐœ์ƒํ•˜๋ฉด ์ฒ˜์Œ create ๋ฐœ์ƒ์‹œ ๋งŒ๋“ค์–ด์ง„ ์ธ์Šคํ„ด์Šค(= ์ฒ˜์Œ update ๋•Œ ์‚ฌ์šฉ๋œ)๋ฅผ ๊ณ„์† ์ฐธ์กฐํ•˜์—ฌ ์žฌํ™œ์šฉํ•œ๋‹ค. (update ํ•œ๋‹ค๊ณ  ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์„œ return ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ๋Š” ๊ฒƒ์„ ๋ช…์‹ฌ)

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

class Translations {
  const Translations(this._value);
  final int _value;

  String get title => 'You clicked $_value times';
}

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

  @override
  _ProxyProvProxyProvState createState() => _ProxyProvProxyProvState();
}

class _ProxyProvProxyProvState extends State<ProxyProvProxyProv> {
  int counter = 0;

  void increment() {
    setState(() {
      counter++;
      print('counter: $counter');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ProxyProvider ProxyProvider'),
      ),
      body: Center(
        child: MultiProvider(
          providers: [
            ProxyProvider0<int>(
              update: (_, __) => counter,
            ),
            ProxyProvider<int, Translations>(
              update: (_, value, __) => Translations(value),
            ),
          ],
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ShowTranslations(),
              SizedBox(height: 20.0),
              IncreaseButton(increment: increment),
            ],
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final title = context.watch<Translations>().title;

    return Text(
      title,
      style: TextStyle(fontSize: 28.0),
    );
  }
}

class IncreaseButton extends StatelessWidget {
  final VoidCallback increment;
  const IncreaseButton({
    Key? key,
    required this.increment,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: increment,
      child: Text(
        'INCREASE',
        style: TextStyle(fontSize: 20.0),
      ),
    );
  }
}
  • ๋‘ ๊ฐœ์˜ ProxyProvider ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ผ€์ด์Šค.

  • ProxyProvider0 ๋Š” ๊ฐ€๋ณ€์ ์ธ int value ๋ฅผ return

  • ProxyProvider ๋Š” ProxyProvider0 ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ฐ€๋ณ€์ ์ธ int ๋ฅผ ๋ฐ›์•„์„œ ์ƒˆ๋กœ์šด ์ธ์Šคํ„ด์Šค๋ฅผ return

  • ์ด ๊ฒฝ์šฐ์— ProxyProvider ๊ฐ€ ํ•ญ์ƒ ์ƒˆ๋กœ์šด ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค์–ด์„œ return ํ•˜๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ช…์‹ฌ. ๋งค๋ฒˆ ์ƒˆ๋กœ์šด Translations ๋ฅผ ๋งŒ๋“ค์–ด์„œ return ํ•˜๊ณ  ์žˆ๋‹ค.

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

class Counter with ChangeNotifier {
  int counter = 0;

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

class Translations with ChangeNotifier {
  late int _value;

  void update(Counter counter) {
    _value = counter.counter;
    notifyListeners();
  }

  String get title => 'You clicked $_value times';
}

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

  @override
  _ChgNotiProvChgNotiProxyProvState createState() =>
      _ChgNotiProvChgNotiProxyProvState();
}

class _ChgNotiProvChgNotiProxyProvState
    extends State<ChgNotiProvChgNotiProxyProv> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ChangeNotifierProvider ChagneNotifierProxyProvider'),
      ),
      body: Center(
        child: MultiProvider(
          providers: [
            ChangeNotifierProvider<Counter>(
              create: (_) => Counter(),
            ),
            ChangeNotifierProxyProvider<Counter, Translations>(
              create: (_) => Translations(),
              update: (
                BuildContext _,
                Counter counter,
                Translations? translations,
              ) {
                translations!..update(counter);
                return translations;
              },
            ),
          ],
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ShowTranslations(),
              SizedBox(height: 20.0),
              IncreaseButton(),
            ],
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final title = context.watch<Translations>().title;

    return Text(
      title,
      style: TextStyle(fontSize: 28.0),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => context.read<Counter>().increment(),
      child: Text(
        'INCREASE',
        style: TextStyle(fontSize: 20.0),
      ),
    );
  }
}
  • ์ •๋ง ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฐฉ์‹์œผ๋กœ ๋™์ผํ•œ ๊ฒฐ๊ณผ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • Counter ๊ฐ€ Value Object ๋กœ ์‚ฌ์šฉ ๋˜์—ˆ๋‹ค.

  • IncreaseButton ์—์„œ๋Š” ๋‹จ์ง€ Counter ์— access ๋งŒ ํ–ˆ๋‹ค. increment() ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ๋งŒ ํ•  ๋ชฉ์ ์ด๊ธฐ ๋•Œ๋ฌธ.

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

class Counter with ChangeNotifier {
  int counter = 0;

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

class Translations {
  const Translations(this._value);
  final int _value;

  String get title => 'You clicked $_value times';
}

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

  @override
  _ChgNotiProvProxyProvState createState() => _ChgNotiProvProxyProvState();
}

class _ChgNotiProvProxyProvState extends State<ChgNotiProvProxyProv> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ChangeNotifierProvider ProxyProvider'),
      ),
      body: Center(
        child: MultiProvider(
          providers: [
            ChangeNotifierProvider<Counter>(
              create: (_) => Counter(),
            ),
            ProxyProvider<Counter, Translations>(
              update: (_, counter, __) => Translations(counter.counter),
            ),
          ],
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ShowTranslations(),
              SizedBox(height: 20.0),
              IncreaseButton(),
            ],
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final title = context.watch<Translations>().title;

    return Text(
      title,
      style: TextStyle(fontSize: 28.0),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => context.read<Counter>().increment(),
      child: Text(
        'INCREASE',
        style: TextStyle(fontSize: 20.0),
      ),
    );
  }
}
  • ์ง์ „์— ์‚ดํŽด๋ณธ ChangeNotifierProvider, ChangeNotifierProxyProvider ์กฐํ•ฉ์€ ๊ธฐ์กด์˜ Translations ๋ฅผ mutation ์‹œ์ผœ์„œ ์žฌํ™œ์šฉ ํ•˜๋Š” ๋ฐ˜๋ฉด ์ด ๋ฐฉ์‹์€ Counter ๊ฐ€ ๋ณ€ํ•  ๋•Œ๋งˆ๋‹ค ๋งค๋ฒˆ ์ƒˆ๋กœ์šด Translations ์ธ์Šคํ„ด์Šค๋ฅผ return ํ•œ๋‹ค.

  • ๊ฐ•์‚ฌ๋‹˜ ์ด์•ผ๊ธฐ๋กœ๋Š” ๋‹จ์ˆœํžˆ computed state ๋ฅผ ๋งŒ๋“ค์–ด ๋‚ด๋Š” ๊ฒฝ์šฐ ChangeNotifierProvider ๋ฅผ ์“ฐ์ง€ ์•Š๊ณ  ProxyProvider ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋งค๋‰ด์–ผ์—๋„ ๋‚˜์™€์žˆ๋Š” preferred way ๋ผ๊ณ  ํ•œ๋‹ค.

๋‹จ์ˆœํžˆ computed state ๋ฅผ ๋งŒ๋“ค์–ด ๋‚ด๋Š” ๊ฒฝ์šฐ ChangeNotifierProvider ๋ฅผ ์“ฐ์ง€ ์•Š๊ณ  ProxyProvider ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋งค๋‰ด์–ผ์—๋„ ๋‚˜์™€์žˆ๋Š” preferred way ๋ผ๊ณ  ํ•œ๋‹ค.

Errors & addPostFrameCallback - 1

Errors & addPostFrameCallback - 2

  • ์œ„์˜ ์—๋Ÿฌ๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ์ฝ”๋“œ์—์„œ ๋ฐœ์ƒ๋˜์—ˆ๋‹ค.

  • flutter ๊ฐ€ Widget ๋“ค์„ ๊ทธ๋ฆฌ๊ณ  ์žˆ๋Š” ์ค‘์— ๋‹ค์‹œ build ์š”์ฒญ์„ ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ.

class Counter with ChangeNotifier {
  int counter = 0;

  void increment() {
    counter++;
    notifyListeners();
  }
}
@override
void initState(){
  super.initState();
  context.read<Counter>().increament();
  myCounter = context.read<Counter>().counter + 10;
}

WidgetsBinding.instance!.addPostFrameCallback((_) {
  context.read<Counter>().increment();
  myCounter = context.read<Counter>().counter + 10;
});
  • addPostFrameCallback ์€ ํ˜„์žฌ์˜ Frame ์ด ์™„์„ฑ๋œ ํ›„ ๋“ฑ๋ก๋œ callback ์„ ์‹คํ–‰์‹œํ‚ค๋„๋ก ํ•œ๋‹ค.

  • add(์ถ”๊ฐ€) + PostFrameCallback(Frame ๋‹ค ๊ทธ๋ ค์ง„ ํ›„ ๋ถˆ๋ ค์งˆ Callback)

  • UI์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” action ์˜ ์‹คํ–‰์„ ํ˜„์žฌ์˜ Frame ์ดํ›„๋กœ ์ง€์—ฐ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. โ€˜ํ˜„์žฌ์˜ Frame ์ดํ›„โ€™ ๋ผ๋Š” ๋œป์€ ๊ฒฐ๊ตญ ํ˜„์žฌ ์‹ธ์ดํด์˜ build ๊ฐ€ ๋๋‚œ ํ›„๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

  • ๋‹ค์‹œ ๋งํ•˜๋ฉด ํ•ด๋‹น Widget ์˜ build ๊ฐ€ ๋๋‚œ ํ›„ ์‹คํ–‰๋  action ์„ ์˜ˆ์•ฝํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

addPostFrameCallback ์™ธ์— ์•„๋ž˜์™€ ๊ฐ™์€ ์ฒ˜๋ฆฌ๋„ ๋™์ผํ•œ ์›๋ฆฌ์ด๋‹ค.

Future.delayed(Duration(seconds: 0), () {
  context.read<Counter>().increment();
  myCounter = context.read<Counter>().counter + 10;
});
Future.microtask(() {
  context.read<Counter>().increment();dartdddd
  myCounter = context.read<Counter>().counter + 10;
});
  • ์œ„ error ๋Š” context.read ์—์„œ read ๋ฅผ watch ๋กœ ๋ฐ”๊พธ๋ฉด ๋ณผ ์ˆ˜ ์žˆ๋Š” ์—๋Ÿฌ์ด๋‹ค.

  • Widget Tree ๋ฐ–์—์„œ listen ์„ ํ•˜๋ ค ํ–ˆ๋‹ค๋Š” ๊ฒƒ.

  • ์‹œ๊ฐ„์ฐจ๋ฅผ ๋‘๊ณ  ํ˜„์žฌ Frame ๋๋‚˜๊ณ  ์ด๊ฑธ ์‹คํ–‰ํ•ด๋‹ฌ๋ผ๋Š” ์˜ˆ์•ฝ ํ–‰์œ„์—์„œ listen ์„ ํ•œ๋‹ค๋Š” ๊ฒƒ์ด ๋…ผ๋ฆฌ์ ์œผ๋กœ ๋งž์ง€ ์•Š๋‹ค. ์ด๋ ‡๊ฒŒ ์ดํ•ดํ•˜์ž.

Errors & addPostFrameCallback - 3

Errors & addPostFrameCallback - 3 ์—์„œ๋Š” ํŠน์ • ํ™”๋ฉด์— ์ง„์ž…ํ–ˆ์„๋•Œ ํ˜น์€ ๊ฐ€๋ณ€์ ์ธ ๊ฐ’์ด ํŠน์ • ์กฐ๊ฑด์— ๊ฑธ๋ ธ์„ ๋•Œ Dialog ๋ฅผ ๋„์šฐ๋Š” ๊ฒฝ์šฐ์— ์žˆ์–ด์„œ ์ฒ˜๋ฆฌ ๋ฐฉ์‹ ๋“ค์„ ์‚ดํŽด๋ณธ๋‹ค.

  • Dialog ๋Š” ํ•ด๋‹น Screen ์ด ๋‹ค ๊ทธ๋ ค์ง€๊ณ  ๋‚˜์„œ ๊ทธ ์œ„์— ๊ทธ๋ ค์ง€๋Š” overlay Widget.

  • ๋”ฐ๋ผ์„œ initState ์—์„œ ๊ทธ๋ƒฅ ํ˜ธ์ถœ์‹œ ์œ„์™€ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ด ๋‹น์—ฐํ•˜๋‹ค.

  • ์ด ๊ฒฝ์šฐ๋„ ์•„๋ž˜์™€ ๊ฐ™์ด ํ•ด๋‹น Frame ์ด ๋‹ค ๊ทธ๋ ค์ง€๊ณ  ๋‚˜์„œ ๊ทธ ์ดํ›„์— ๊ทธ๋ ค์ง€๋„๋ก ์ฒ˜๋ฆฌํ•ด์•ผ ํ•œ๋‹ค.

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance!.addPostFrameCallback((_) {
      showDialog(
        context: context,
        builder: (context) {
          return AlertDialog(
            content: Text('Be careful!'),
          );
        },
      );
    });
  }

  • ๊ฐ™์€ ๋งฅ๋ฝ์—์„œ ๊ฐ€๋ณ€์ ์ธ ๊ฐ’์„ ๋ฐ”๋ผ๋ณด๊ณ  ํŠน์ • ์กฐ๊ฑด์—์„œ Dialog ๋ฅผ ๋„์šฐ๊ณ  ์‹ถ์„๋•Œ๋„ ์•„๋ž˜์™€ ๊ฐ™์ด addPostFrameCallback ๋กœ ์ฒ˜๋ฆฌํ•ด์ค˜์•ผํ•œ๋‹ค.

  • ํ•ด๋‹น Frame ์ด ๋‹ค ๊ทธ๋ ค์ง€๊ณ  ๋‚˜์„œ ์ดํ›„์— ์กฐ๊ฑด ๊ฒ€์‚ฌ๋ฅผ ํ•œ ํ›„ overlay ํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  • ์กฐ๊ฑด์‹์„ ํฌํ•จํ•œ Dialog call ์ž์ฒด๋ฅผ ๋ชจ๋‘ addPostFrameCallback ๋กœ ์˜ˆ์•ฝํ•  ์ˆœ ์žˆ์œผ๋‚˜ ์กฐ๊ฑด์— ๋ถ€ํ•ฉํ•˜์ง€๋„ ์•Š๋Š” ๊ฒฝ์šฐ์—๋„ ๋ถˆํ•„์š”ํ•˜๊ฒŒ action ์ด register ๋˜๋ฏ€๋กœ ์ข‹์ง€ ์•Š์€ ์ฝ”๋“œ๊ฐ€ ๋œ๋‹ค.

  @override
  Widget build(BuildContext context) {
    if (context.read<Counter>().counter == 3) {
      WidgetsBinding.instance!.addPostFrameCallback((_) {
        showDialog(
          context: context,
          builder: (context) {
            return AlertDialog(
              content: Text('Count is 3'),
            );
          },
        );
      });
    }

    return Scaffold(
      appBar: AppBar(
        title: Text('Handle Dialog'),
      ),
      body: Center(
        child: Text(
          '${context.watch<Counter>().counter}',
          style: TextStyle(fontSize: 40.0),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          context.read<Counter>().increment();
        },
      ),
    );
  }

Errors & addPostFrameCallback - 4

Errors & addPostFrameCallback - 4 ์—์„œ๋Š” State ๊ฐ€ ๋ณ€ํ•  ๋•Œ Navigate ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์‚ดํŽด๋ณธ๋‹ค.

  • Navigate ํ•˜๋Š” ๊ฒƒ๋„ ๊ฒฐ๊ตญ Stack ์œ„์— ์˜ฌ๋ฆฌ๋Š” Overlay ํ–‰์œ„์ด๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ž˜์™€ ๊ฐ™์ด ์˜ˆ์•ฝ์„ ๊ฑธ์–ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

  • ๋งŒ์•ฝ addPostFrameCallback ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด build ํ•˜๋Š” ์™€์ค‘์— Navigator.push ๊ฐ€ ์ฒ˜๋ฆฌ ๋˜๋Š” ๊ฒƒ์ด๊ณ  ์ด๋Š” ๋ณธ ํ™”๋ฉด์ด ๋‹ค ๊ทธ๋ ค์ง€๊ธฐ๋„ ์ „์—(์ •ํ™•ํžˆ๋Š” ๊ทธ๋ ค์ง€๋Š” ์ค‘๊ฐ„์—) Overlay ์š”์ฒญ์„ ํ•œ ๊ฒƒ๊ณผ ๊ฐ™๋‹ค. ๊ทธ๋ž˜์„œ ์œ„์™€ ๊ฐ™์€ ์—๋Ÿฌ๋ฅผ ๋งŒ๋‚˜๊ฒŒ ๋œ๋‹ค.

    if (context.read<Counter>().counter == 3) {
      WidgetsBinding.instance!.addPostFrameCallback((_) {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => OtherPage(),
          ),
        );
      });
    }
  • Dialog, Navigate, BottomSheet ๋ชจ๋‘ Overlay Widget ์ด๋ฏ€๋กœ ํ•ด๋‹น Widget ์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ์ด๋ฒˆ์— ํ•™์Šตํ•œ ๋‚ด์šฉ๋“ค์„ ์ž˜ ํ™œ์šฉํ•˜๋„๋ก ํ•œ๋‹ค.

ChangeNotifier ์˜ addListener ๋ฅผ ์ด์šฉํ•œ action ์ฒ˜๋ฆฌ

  • ๊ฐ•์‚ฌ๋‹˜์€ ๋‘๋ฒˆ์งธ ํ˜น์€ ์„ธ๋ฒˆ์งธ ๋ฐฉ์‹์„ ์ถ”์ฒœ.

  • ๋‘๋ฒˆ์งธ ๋ฐฉ์‹๋งŒ ์•„๋ž˜์— ์ฝ”๋“œ๋กœ ์ •๋ฆฌ.

  Future<void> getResult(String searchTerm) async {
    _state = AppState.loading;
    notifyListeners();

    await Future.delayed(Duration(seconds: 1));

    try {
      if (searchTerm == 'fail') {
        throw 'Something went wrong';
      }

      _state = AppState.success;
      notifyListeners();
    } catch (e) {
      _state = AppState.error;
      notifyListeners();
      rethrow;
    }
  }
  void submit() async {
    setState(() {
      autovalidateMode = AutovalidateMode.always;
    });

    final form = formKey.currentState;

    if (form == null || !form.validate()) return;

    form.save();
    try {
      await context.read<AppProvider>().getResult(searchTerm!);
      Navigator.push(context, MaterialPageRoute(
        builder: (context) {
          return SuccessPage();
        },
      ));
    } catch (e){
      showDialog(
        context: context,
        builder: (context) {
          return AlertDialog(
            content: Text('Something went wrong'),
          );
        },
      );
    }
  }

Last updated