본문 바로가기

EXPERIENCE/Flutter

[Flutter/Dart] 간단하게 Provider 사용해보기 (with. watch와 select의 차이점)

728x90
728x90

 

 

 

 

 

 

flutter는 Widget이 StatefulWidget, StatelessWidget으로 나뉠만큼 상태관리가 중요하다

그리고 상태 관리를 편하게 해주기 위한 라이브러리들로 GetX, BLoC 등 여러가지가 존재하지만

내 기준 Provider가 상위호환 중에서는 가장 기본이라는 느낌이 커서 Provider에 대해 간단한 예제를 만들어 보았다.

 

 

 

 

Provider란?

 

 

 

[Xcode/iOS] 간단한 예제로 Protocol 구현해보기 (with. SwiftUI)

프로토콜(Protocol)이란? 특정 작업 혹은 기능에 적합한 메소드, 프로퍼티, 기타 요구사항들의 청사진 특징 클래스, 구조체, 열거형에서 채택될 수 있음 여러개의 프로토콜을 동시에 채택할 수 있

s-o-h-a.tistory.com

 

 

 

 

결과화면

 

  • GIF

 

 

  • Screen Shot

 

 

 

예제 프로젝트

 

 

GitHub - sohay19/Flutter_Provider: Practice Flutter_Provider

Practice Flutter_Provider. Contribute to sohay19/Flutter_Provider development by creating an account on GitHub.

github.com

 

 

 

 

 

728x90

 

 

 

 

 

구조 및 기본 설정

 

Pet의 정보를 출력하고 나이, 몸무게를 수정할 수 있는 프로그램의 형태

 

 

  • 데이터 모델 (pet.dart)

- Pet class는 펫의 이름, 나이, 몸무게 정보를 보유한다

 

 

  • Provider 패키지 사용 (pubspec.yaml)
 

provider | Flutter Package

A wrapper around InheritedWidget to make them easier to use and more reusable.

pub.dev

- Provider 사용을 위해 pubspec.yaml 파일에 dependencies 하위로 "provider: ^버전"을 명기
- "Pub get"을 클릭하여 적용한다

 

 

  • Provider 설정
  • main_provider.dart
class MainProvider with ChangeNotifier {
	// Provider에서 사용할 변수 및 함수 작성
}
- 다음과 같은 형태로 Provider class를 생성한다

 

  • main.dart

- Provider 사용을 위해서는 다양한 방법이 있지만 개인적으로 다음과 같은 방법을 선호한다
- 우선 import 'package:provider/provider.dart'; 를 해야 Provider를 사용할 수 있다
- runApp의 인자로 ChangeNotifierProvider를 직접 전달해주어 하위 MyApp 어디서든 MainProvider를 사용할 수 있도록 설정한다

 

 

 

  • Provider 사용법
watch
> Provider의 변화를 감지해 데이터를 얻고 해당 메소드가 포함된 위젯을 재빌드

read

> Provider의 데이터를 읽고 변경할 수 있음(접근은 하지만 재빌드는 하지않음)

select

> watch와 유사하게 변화를 감지하고 위젯을 재빌드 하지만 Provider내의 특정 속성의 변화만을 감지할 수 있음

 

 

 

 

소스코드

 

  • main.dart
void main() {
  runApp( ChangeNotifierProvider(
    create: (_) => MainProvider(),
    child: const MyApp(),
  ));
}

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

  final String title = 'Provider';

  @override
  Widget build(BuildContext context) {
    if (Platform.isIOS || Platform.isMacOS) {
      return CupertinoView(title);
    } else {
      return MaterialView(title);
    }
  }
}

/// Material
class MaterialView extends StatelessWidget {
  String title;

  MaterialView(this.title);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        brightness: Brightness.light,
        primarySwatch: Colors.indigo,
      ),
      home: MaterialMainPage(
        title: title,
      ),
    );
  }
}

/// Cupertino
class CupertinoView extends StatelessWidget {
  String title;

  CupertinoView(this.title);

  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      theme: CupertinoThemeData(
        brightness: Brightness.light,
        primaryColor: CupertinoColors.systemIndigo,
      ),
      home: CupertinoMainPage(
        title: title,
      ),
    );
  }
}
- MyApp: App의 Root가 되는 부분으로 Platform을 체크해 iOS 또는 MacOS가 아닌 경우 Material View를 보여준다.
- MaterialView: iOS/MacOS가 아닐 경우 보여지는 Widget으로 MaterialApp을 리턴하여 Material Style의 App을 실행한다.
- CupertinoView: iOS/MacOS의 경우 보여지는 Widget으로 CupertinoApp을 반환하며 하위의 모든 Widget은 Cupertino Style로 작성한다

 

 

  • main_provider.dart
class MainProvider with ChangeNotifier {
  late Pet _myPet;

  MainProvider() {
    _myPet = new Pet('bobo', Random().nextInt(7)+1, Random().nextInt(11)+6);
  }

  Pet get pet => _myPet;
  int get age => _myPet.age;
  int get weight => _myPet.weight;

  addAge() {
    _myPet.age += 1;
    _endProcess();
  }

  subAge() {
    _myPet.age -= 1;
    _endProcess();
  }

  addWeight() {
    _myPet.weight += 1;
    _endProcess();
  }

  subWeight() {
    _myPet.weight -= 1;
    _endProcess();
  }

  _endProcess() {
    notifyListeners();
  }
}
- Pet 타입의 변수를 보유
- pet변수와 pet의 나이, 몸무게를 반환하는 Get 프로퍼티 존재
- addAge, addWeight => 펫의 나이, 몸무게 증가
- subAge, subWeight => 펫의 나이, 몸무게 감소

 

 

  • main_material_view.dart
class MaterialMainPage extends StatefulWidget {
  const MaterialMainPage({super.key, required this.title});
  final String title;

  @override
  State<MaterialMainPage> createState() => _MaterialMainPageState();
}

class _MaterialMainPageState extends State<MaterialMainPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SafeArea(
        minimum: EdgeInsets.fromLTRB(15, 30, 15, 30),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            Text('Age'),
            SizedBox(
              height: 5,
            ),
            _AgeInput(),
            SizedBox(
              height: 30,
            ),
            Text('Weight'),
            SizedBox(
              height: 5,
            ),
            _WeightInput(),
            SizedBox(
              height: 20,
            ),
            SizedBox(
              height: 40,
            ),
            Text(
                'My Pet Info',
              style: TextStyle(
                fontWeight: FontWeight.bold,
              ),
            ),
            SizedBox(
              height: 10,
            ),
            _PetInfo(),
          ],
        ),
      ),
    );
  }
}
Material Style 화면으로 상태를 가지고 있는 Widget이다
Age를 컨트롤 하는 부분, Weight를 컨트롤 하는 부분, Pet의 정보를 출력하는 View로 구분되어 있다.

 

class _AgeInput extends StatelessWidget {
  late TextEditingController controller;

  @override
  Widget build(BuildContext context) {
    final age = context.select<MainProvider, int>((provider) => provider.age).toString();
    controller = TextEditingController(text: age + '살');

    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        ElevatedButton(
          onPressed: context.read<MainProvider>().subAge,
            child: Text('-'),
        ),
        SizedBox(
          width: 20,
        ),
        Container(
          width: 100,
          child: TextField(
            readOnly: true,
            controller: controller,
            textAlign: TextAlign.center,
          ),
        ),
        SizedBox(
          width: 20,
        ),
        ElevatedButton(
          onPressed: context.read<MainProvider>().addAge,
          child: Text('+'),
        ),
      ],
    );
  }
}
Age를 컨트롤 하는 Widget이다
+, -버튼을 클릭하면 MainProvider 내부의 Pet의 나이 정보가 변경되며 select 메소드를 통해 변화를 감지하고 바로 정보가 갱신된다

 

class _WeightInput extends StatelessWidget {
  late TextEditingController controller;

  @override
  Widget build(BuildContext context) {
    final weight = context.select<MainProvider, int>((provider) => provider.weight).toString();
    controller = TextEditingController(text: weight + 'kg');

    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        ElevatedButton(
          onPressed: context.read<MainProvider>().subWeight,
          child: Text('-'),
        ),
        SizedBox(
          width: 20,
        ),
        Container(
          width: 100,
          child: TextField(
            readOnly: true,
            controller: controller,
            textAlign: TextAlign.center,
          ),
        ),
        SizedBox(
          width: 20,
        ),
        ElevatedButton(
          onPressed: context.read<MainProvider>().addWeight,
          child: Text('+'),
        ),
      ],
    );
  }
}
Weight를 컨트롤 하는 Widget이다
+, -버튼을 클릭하면 MainProvider 내부의 Pet의 몸무게 정보가 변경되며 select 메소드를 통해 변화를 감지하고 바로 정보가 갱신된다

 

class _PetInfo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final petInfo = context.watch<MainProvider>().pet;

    return Container(
      height: 200,
      width: MediaQuery.of(context).size.width * 3/4,
      padding: EdgeInsets.all(15),
      decoration: BoxDecoration(
        border: Border.all(
          color: Colors.black,
          width: 0.5,
        ),
        borderRadius: BorderRadius.circular(5),
      ),
      child: Text(
          'Name: ' + petInfo.name +
              '\nAge: ' + petInfo.age.toString() + '살' +
              '\nWeight: ' + petInfo.weight.toString() + 'kg'
      ),
    );
  }
}
Pet의 정보를 출력하는 Widget이다
watch가 사용되므로 Main Provider에 변화가 생기면 정보를 갱신받아 Widget을 재빌드한다
따라서 상위 컨트롤을 이용해 나이, 몸무게를 변화 시키는 내역이 바로 반영될 수 있다

 

 

 

 

 

개인적으로 watch와 select의 차이를 잘 알고 이를 알맞게 사용하는 것이 중요하지 않을까 생각된다.

아직 멀티 Provider를 사용한 적은 없지만 추후 예제 프로젝트르 진행하며 한번 사용해보려고한다 :)

 

 

 

 

 

 

 

 

 

728x90
728x90