728x90
728x90
SMALL
flutter는 Widget이 StatefulWidget, StatelessWidget으로 나뉠만큼 상태관리가 중요하다
그리고 상태 관리를 편하게 해주기 위한 라이브러리들로 GetX, BLoC 등 여러가지가 존재하지만
내 기준 Provider가 상위호환 중에서는 가장 기본이라는 느낌이 커서 Provider에 대해 간단한 예제를 만들어 보았다.
Provider란?
결과화면
- GIF
- Screen Shot
예제 프로젝트
728x90
구조 및 기본 설정
Pet의 정보를 출력하고 나이, 몸무게를 수정할 수 있는 프로그램의 형태
- 데이터 모델 (pet.dart)
- Pet class는 펫의 이름, 나이, 몸무게 정보를 보유한다
- Provider 패키지 사용 (pubspec.yaml)
- 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
LIST