본문 바로가기

Flutter/07 State - Provider

[Flutter] StateNotifierProvider

오늘은 StateNotifierProvider 에 대해서 알아보겠습니다.

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

소스코드 - https://github.com/mike-bskim/state_notifier_test

 

GitHub - mike-bskim/state_notifier_test

Contribute to mike-bskim/state_notifier_test development by creating an account on GitHub.

github.com

 

기본기능은 아래 영상과 같습니다.

appBar 의 색상에 따라서 카운터값 증가분이 다릅니다. 파랑(1씩 증가), 검정(10씩 증가), 빨강(10씩 감소).

카운터값이 ~50까진 body 배경색이 흰색, 51-99는 회색, 100이상 노란색.

 

 

 

프로젝트 구조는 

 

 

bg_color.dart

 

import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:state_notifier/state_notifier.dart';

class BgColorState extends Equatable {
  final Color color;
  const BgColorState({
    required this.color,
  });

  @override
  List<Object> get props => [color];

  @override
  bool get stringify => true;

  BgColorState copyWith({
    Color? color,
  }) {
    return BgColorState(
      color: color ?? this.color,
    );
  }
}

class BgColor extends StateNotifier<BgColorState> {
  BgColor() : super(const BgColorState(color: Colors.blue));

  void changeColor() {
    // state 변수는 super 전달하는 값이 초기값이다.
    // notifyListeners 호출 안함.
    if (state.color == Colors.blue) {
      state = state.copyWith(color: Colors.black);
    } else if (state.color == Colors.black) {
      state = state.copyWith(color: Colors.red);
    } else {
      state = state.copyWith(color: Colors.blue);
    }
  }
}

 

counter.dart - BgColoerState.color 에 의존하는 provider

 

import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:state_notifier/state_notifier.dart';

import 'bg_color.dart';

class CounterState extends Equatable {
  final int counter;
  const CounterState({
    required this.counter,
  });

  @override
  List<Object> get props => [counter];

  @override
  bool get stringify => true;

  CounterState copyWith({
    int? counter,
  }) {
    return CounterState(
      counter: counter ?? this.counter,
    );
  }
}

// Counter 는 BgColor 의 state 를 읽어야 하므로
// ProxyProvider 대신 LocatorMixin 를 사용함.
class Counter extends StateNotifier<CounterState> with LocatorMixin {
  Counter() : super(const CounterState(counter: 0));

  void increment() {
    Color currentColor = read<BgColor>().state.color;
    // debugPrint('-- counter>>increment: ' + currentColor.toString());

    // appBar 의 배경색에 따라서 값 증분이 달라짐.
    if (currentColor == Colors.black) {
      state = state.copyWith(counter: state.counter + 10);
    } else if (currentColor == Colors.red) {
      state = state.copyWith(counter: state.counter - 10);
    } else {
      state = state.copyWith(counter: state.counter + 1);
    }
  }

  // 다른 object의 변경사항을 listen 할수 있음,
  // update 는 의존하는 다른 state 가 변경될때 마다 호출된다.
  // ProxyProvider 가 다른 provider 에 하는것과 동일한 기능
  @override
  void update(Locator watch) {
    // debugPrint('----- counter>>update(BgColor): nothing');
    debugPrint('----- counter>>update(BgColor): ${watch<BgColorState>().color.toString()}');
    debugPrint('----- counter>>update(BgColor): ${watch<BgColor>().state.color.toString()}');
    super.update(watch);
  }
}

 

customer_level.dart - CounterState.count 에 의존하는 provider

 

import 'package:state_notifier/state_notifier.dart';

import 'counter.dart';

enum Level {
  bronze,
  silver,
  gold,
}

class CustomerLevel extends StateNotifier<Level> with LocatorMixin {
  CustomerLevel() : super(Level.bronze);

  // update 는 의존하는 다른 state 가 변경될때 마다 호출된다.
  // 여기서는 CounterState 의 counter 가 변경될때 마다 호출됨.
  @override
  void update(Locator watch) {
    final currentCounter = watch<CounterState>().counter;
    // debugPrint('----- level>>update(counter): ${currentCounter.toString()}');
    // debugPrint('----- level>>update(bgColor): ${watch<BgColorState>().color.toString()}');

    if (currentCounter >= 100) {
      state = Level.gold;
    } else if (currentCounter > 50 && currentCounter < 100) {
      state = Level.silver;
    } else {
      state = Level.bronze;
    }
    super.update(watch);
  }
}

 

main.dart

 

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

import 'providers/bg_color.dart';
import 'providers/counter.dart';
import 'providers/customer_level.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        // ProxyProvider 와 마찬가지로 순서가 바뀌면 오류가 난다.
        // 영향을 주는 provider 는 영향을 받는 provider 보다 먼저 표기해야 함
        StateNotifierProvider<BgColor, BgColorState>(
          create: (context) => BgColor(),
        ),
        StateNotifierProvider<Counter, CounterState>(
          create: (context) => Counter(),
        ),
        StateNotifierProvider<CustomerLevel, Level>(
          create: (context) => CustomerLevel(),
        ),
      ],
      child: MaterialApp(
        title: 'StateNotifier',
        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) {
    final colorState = context.watch<BgColorState>();
    final counterState = context.watch<CounterState>();
    final levelState = context.watch<Level>();

    return Scaffold(
      backgroundColor: levelState == Level.bronze
          ? Colors.white
          : levelState == Level.silver
          ? Colors.grey
          : Colors.yellow,
      appBar: AppBar(
        backgroundColor: colorState.color,
        title: const Text('StateNotifier'),
      ),
      body: Center(
        child: Text(
          '${counterState.counter}',
          style: Theme.of(context).textTheme.headline2,
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            tooltip: 'Increment',
            child: const Icon(Icons.add),
            onPressed: () {
              context.read<Counter>().increment();
            },
          ),
          const SizedBox(width: 10.0),
          FloatingActionButton(
            tooltip: 'Change Color',
            child: const Icon(Icons.color_lens_outlined),
            onPressed: () {
              context.read<BgColor>().changeColor();
            },
          ),
        ],
      ),
    );
  }
}

 

 

 

 

[참고자료] udemy - Flutter Provider Essential 코스 (Korean)