앱 개발을 하다보면 (물론 다른 코딩도...) UI 와 로직들이 얽히고 설키는 상황들이 많다. 이것을 어떻게 가독성 있게 풀어내느냐가 개발자의 실력...
이거를 해결하기 위해서 구글 개발자가 만든 BLoC 패턴
이 각광을 받았었지만, 진입장벽도 높고 한 로직마다 4개의 클래스를 구현해야 한다는 점이 아쉬웠다.
이를 해소시켜준 것이 Provider
인데, BLoC 패턴
보다 더 쉽게 적용하고 로직 간의 분리도 간단하다.
provider 4.3.2 | Flutter Package
$ flutter pub add provider
or
pubspec.yaml
파일 dependencies
에 추가해준다.
(이후 터미널에서 flutter pub get
명령어 실행 추천)
dependencies:
provider: ^4.3.2
lib/provider
하위에 Provider
생성
파일 이름은 {로직명}_provider.dart
로 통일 권장
lib
├── main.dart
├── provider
│ └── count_provider.dart
└── screens
...
extends ChangeNotifier
를 상속받아 클래스를 생성해준다.
import 'package:flutter/material.dart';
class CounterProvider extends ChangeNotifier {
...
}
Provider
의 가장 Key 포인트는 notifyListeners();
인데, 이것을 호출해야 해당 Provider
를 구독하고 있던 State
들이 새로운 값으로 변경을 한다.
class CounterProvider extends ChangeNotifier {
int _count = 0; // 상태
int get count => _count;
add() {
_count++; //상태 변경
notifyListeners(); // 상태 변경 된 것을 알림
}
decrese() {
_count--; //상태 변경
notifyListeners(); // 상태 변경 된 것을 알림
}
}
Provider
를 선언한 순간 그 Provider
에 속한 children
모두 Provider
에 접근이 가능하다. 선언 방식에도 단일/다중에 따라 나눠진다.
ChangeNotifierProvider
에서는 한개의 Provider
를 선언이 가능하다. 이제 child
위젯에서 선언된 Provider
를 사용할 수 있다.
MaterialApp(
title: 'Flutter Provider Demo',
home: ChangeNotifierProvider(
create: (BuildContext context) => CounterProvider(),
child: Home(),
),
);
MultiProvider
에서는 여러 개의 ChangeNotifierProvider
를 선언이 가능하다.
MaterialApp(
title: 'Flutter Provider Demo',
home: MultiProvider(
providers: [
ChangeNotifierProvider(
create: (BuildContext context) => CounterProvider(),
),
ChangeNotifierProvider(
create: (BuildContext context) => TodoProvider(),
),
],
child: Home(),
),
);
해당 상태를 UI에 연동하는 방식에서 2가지가 있다. 각각의 목적이 다르니, 고민해서 활용하자.
Provider
에 직접 접근해서 받아오는 방식이다. 주의할 점은 이렇게 받아오면 현 위젯 전체가 리로드 된다. 가벼운 위젯이라면 상관없다. 현재 위젯 전체라는 것이 애매모호 하다면, 다음 방식을 보자.
build(BuildContext context) {
CounterProvider counter = Provider.of<CounterProvider>(context);
return Center(
child: Text(
counter.count.toString(),
),
);
}
Widget
상태가 변경이 되면 Center
를 제외한 builder
부분만 리로드 된다. Provider.of
와는 성능상 같은 역할이지만, 전체 위젯이 아니라 부분부분의 위젯만 변경시키기에 활용에 따라 퍼포먼스 차이가 크기 난다.
return Center(
child: Consumer<CounterProvider>(
builder: (context, provider, child) {
return Text(
provider.count.toString(),
);
},
),
);
builder
부분에 보면 3가지 인자가 있는데, 마지막 child
를 활용하면 더 좋은 코드를 만들 수 있다. child
는 Provider
에서 변경이 생겨도 다시 리빌드 하지 않도록 구성할 수 있다. Consumer
에 child
를 지정해주면 된다.
이러면 provider
가 변해도 Text('hello')
는 다시 빌드하지 않는다. 이러한 사소한 차이들이 모여서 프로젝트 성능을 좌우하니, 알아두면 좋은 팁인듯 하다.
return Center(
child: Consumer<CounterProvider>(
builder: (context, provider, child) {
return Column(
children: [
Text(
provider.count.toString(),
),
Container(child: child),
],
);
},
child: const Text('hello'),
),
);