본문 바로가기
관리자

Programming-[CrossPlatform]/Flutter

Flutter Provider: 2. ChangeNotifierProvider, MultiProvider

728x90
반응형

Youtube 코딩셰프님의 강의를 요약 정리한 글이다. dart 언어나 이론 부분은 자바와 유사하여 대부분 제외하였고, flutter 기초 위주로 정리한다.

https://www.youtube.com/@codingchef

 

코딩셰프

향후 대세가 될 플러터를 단계별로 맛있게 학습하실 수 있습니다!

www.youtube.com

 

 

 

 


 

 

ChangeNotifier

어떤 데이터를 담을지에 대한 클래스에 근거해서 provider들을 만든다. 지난 글에서 Provider.of<FishModel>(context)를 사용한 것처럼 FishModel이 변화하는 데이터로 지정된다.

 

이 변화하는 데이터를 추적하는 역할을 ChangeNotifier가 한다. 아래처럼 with 구문을 통해 mixin으로 FishModel에 ChangeNotifier를 추가해줄 수 있다.

 

import 'package:flutter/cupertino.dart';

class FishModel with ChangeNotifier{
  final String name;
  final int number;
  final String size;

  FishModel({required this.name, required this.number, required this.size});
}

 

 

extends(상속) 과 mixin의 차이

 

 

강의에서의 예시처럼, 상속은 강한 결합관계를 갖는다. 또한 Dart에서는 Java처럼 다중 상속을 허용하지 않기 때문에 여러 클래스들을 상속할 수 없다.

 

그러나 class 대신 mixin으로 정의한 클래스는 다른 클래스에서 with 구문으로 얼마든지 여러 개 참조할 수 있다. 그리고 단순히 기능을 추가한다는 개념으로 이해하면 될 것 같다.

 

ChangeNotifier의 notifyListeners 메서드를 사용하면 ChangeNotifier를 Listen하고 있는 모든 위젯들에게 데이터의 변경 사실을 알려줄 수 있게 된다. 그리고 addListener 라는 메서드를 통해 데이터의 변경 사실을 알고 싶은 위젯들에서 콜백 메서드로 등록하면 값의 변경을 알 수 있게 되는 구조이다. 또한 addListener는 자동으로 제거되지 않기 때문에 removeListener라는 메서드를 통해서 필요없는 addListener를 dispose 시켜주어야한다.

 

ChangeNotifierProvider

위 번거로움 때문에 ChangeNotifierProvider를 사용한다.

 

아래처럼 FishModel에 메서드를 하나 만든다. ChangeNotifier를 mixin으로 받아왔으므로 notifyListeners 메서드를 사용할 수 있다.

import 'package:flutter/cupertino.dart';

class FishModel with ChangeNotifier{
  final String name;
  int number;
  final String size;

  FishModel({required this.name, required this.number, required this.size});

  void changeFishNumber() {
    number++;
    notifyListeners();
  }
}

 

그리고 main.dart 파일에서 Provider 대신 ChangeNotifierProvider를 적용한다.

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => FishModel(name: 'Salmon', number: 10, size: 'big'),
      child: MaterialApp(
        home: FishOrder(),
      ),
    );
  }
}

 

 

맨 아래 SpicyC로 이동해서 다음과 같이 ElevatedButton을 추가해준다. 여기서 context 옆에 listen:false 인자가 추가되었는데, 이를 추가해주지 않으면 ElevatedButton 자체도 changeFishNumber()를 적용하는 State management의 관리 대상이 되어 pointeless rebuild라는 에러가 뜬다. 불필요한 위젯 rebuild를 없애기 위해 listen: false를 적용하는 것이다.

 

//... 중략
SizedBox(
    height: 20,
  ),
  ElevatedButton(onPressed: () {
    Provider.of<FishModel>(context, listen: false).changeFishNumber();
  }
      , child: Text('Change fish number'))
],

 

 

 

MultiProvider

Provider가 계층적으로 여러 개인 경우, 여러 번 ChangeNotifierProvider를 사용하면 가독성이 떨어질 수 있다. 한 번에 관리할 수 있게 해주는 것이 MultiProvider이다.

 

실습을 위해 SeaFishModel 클래스를 만든다.

import 'package:flutter/cupertino.dart';

class SeaFishModel with ChangeNotifier{
  final String name;
  int tunaNumber;
  final String size;

  SeaFishModel({required this.name, required this.tunaNumber, required this.size});

  void changeSeaFishNumber() {
    tunaNumber++;
    notifyListeners();
  }
}

 

 

그리고 ChangeNotifierProvider 부분을 아래처럼 변경해준다. MultiProvider는 리스트 형태로 Provider들을 인자로 받는다. 이제 해당 위젯은 FishModel, SeaFishModel의 2가지 데이터를 listen할 수 있게 되었다.

@override
Widget build(BuildContext context) {
  return MultiProvider(
    providers: [
      ChangeNotifierProvider(
        create: (context) =>
            FishModel(name: 'Salmon', number: 10, size: 'big'),
      ),
      ChangeNotifierProvider(
        create: (context) =>
            SeaFishModel(name: 'Tuna', tunaNumber: 0, size: 'middle'),
      )
    ],
    child: MaterialApp(
      home: FishOrder(),
    ),
  );
}

 

 

SpicyB, Low 위젯 쪽에 아래처럼 ElevatedButton을 추가한다.

@override
Widget build(BuildContext context) {
  return Column(
    children: [
      Text(
        'Tuna number: ${Provider.of<SeaFishModel>(context).tunaNumber}',
        style: TextStyle(
            fontSize: 16, color: Colors.red, fontWeight: FontWeight.bold),
      ),
      Text(
        'Fish size: ${Provider.of<FishModel>(context).size}',
        style: TextStyle(
            fontSize: 16, color: Colors.red, fontWeight: FontWeight.bold),
      ),
      SizedBox(
        height: 20,
      ),
      ElevatedButton(
          onPressed: () {
            Provider.of<SeaFishModel>(context, listen: false)
                .changeSeaFishNumber();
          },
          child: Text('Sea fish Number')),
      Low()
    ],
  );
}

 

 

 

728x90
반응형