조금 매운맛 Provider 입문 1: Provider와 State management
예시) 물고기 잡는 남자1
물고기 키우는 남자가 있었는데 이 남자는 산 중턱에서 양식을 했음
그러다가 자연스래 매운탕집을 했는데 생선만 납품하는게 더 이득이었음
그래서 주변에 매운탕집을 유치하고 양식한
물고기들을 주변 매운탕 집으로 납품을 했는데, 가장 멀리 있는 매운탕집이 1마리만 주문해도 몇십분을 가서 배달해야하니 너무 비효율적이었다.
Provider
1. flutter dev 공식 추천
2. 가장 보편적
3. provider를 통한 riverpd와 연계
State
1. UI에 변화가 생기도록 영향을 미치는 데이터
2. 앱 수준의 데이터 > 서버와 연동해서 데이터를 끌어오는 등 앱화면에 변화를 일으키는 모든 데이터
3. 위젯 수준의 데이터 > 체크박스와 같이 체크박스 수준에서 화면 변화가 생기게 하는 데이터]
setState 메소드
- state가 변해서 UI를 다시 그려야하는 경우 기본적으로 사용
즉, 가장 중요한 기능 : 위젯 라이프 사이클에 빌드 메서드를 호출해 UI 랜더링
- Flutter가 기본 제공하는 state management
예시) 버튼으로 증감하는 앱
> increment 메소드와 increment2 메소드가 호출 될때마다 MyPage 아래에 있는 모든 위젯들이 지속적으로 리빌드 됨 = 비효율적임
> Text / Plus / Minus 위젯은 데이터가 변하지 않거나 데이터를 필요로 하지 않기에 굳이 다시 랜더링 할 필요 없음. 만약 위젯트리가 더 많아지고 최하단의 1개의 데이터를 위해서 전체 리빌드 되는건 너무 비효율적
> setState 메서드 사용 시 UI의 Staate를 state 클래스 내부에서 관리
그래서 특정 A 위젯에서 setState 메서드를 사용해 랜더링되면 같은 state를 공유해야하는 B 위젯에서는 알 길이 없음
setState 메서드 사용시 문제점
1. 비효율성 : 오직 1개의 위젯 변경을 위해서 state 업데이트 할려면 모든 위젯이 리빌드 되면서 불필요한 랜더링 유발
2. 동시에 다른 위젯의 state를 업데이트 시켜주지 못함
State Management 정의
1. 위젯트리 상에서 위치를 막론하고 데이터를 필요로 하는 위젯이 쉽게 데이터에 접근할 수 있는 방법
2. 변화된 데이터에 맞추어서 UI를 다시 그려주는 기능
예시) 물고기 잡는 남자2
남자는 비효율적인 배달방식을 바꾸기 위해서 산부터 산 아래 강까지 가게들이 있는 곳을 지나는 물길을 만듦
그래서 주문이 들어오면 물길을 따라 물고기를 보내줌
효율 굳!
여기서 만든 물길(수로) == Provider
provider 설치
provider | Flutter Package
A wrapper around InheritedWidget to make them easier to use and more reusable.
pub.dev
* const 키워드 붙이는게 귀찮다면 analysis_options.yaml 파일에서 패키지 관련 코드 주석처리
fist_model.dart 생성 및 위젯트리
//fish_model.dart
class FishModel{
final String name;
final int number; //몇마리 보낼지
final String size; //생선크기
FishModel({required this.name, required this.number, required this.size});
}
//main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: FishOrder(),
);
}
}
class FishOrder extends StatelessWidget {
const FishOrder({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Fish Order'),
),
body: Center(
child: Column(
children: [
Text(
'Fish name',
style: TextStyle(fontSize: 20),
),
SizedBox(
height: 20,
),
High()
],
),
),
);
}
}
class High extends StatelessWidget {
const High({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'SpicyA is located at high place',
style: TextStyle(fontSize: 16),
),
SizedBox(
height: 20,
),
SpicyA()
],
);
}
}
class SpicyA extends StatelessWidget {
const SpicyA({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'Fish number: ', //주문한 생선 갯수
style: TextStyle(
fontSize: 16,
color: Colors.red,
fontWeight: FontWeight.bold), //주문한 생선 사이즈
),
Text(
'Fish size: ',
style: TextStyle(
fontSize: 16, color: Colors.red, fontWeight: FontWeight.bold),
),
SizedBox(
height: 20,
),
Middle()
],
);
}
}
class Middle extends StatelessWidget {
const Middle({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'SpicyB is located at middle place',
style: TextStyle(fontSize: 16),
),
SizedBox(
height: 20,
),
SpicyB()
],
);
}
}
class SpicyB extends StatelessWidget {
const SpicyB({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'Fish number: ',
style: TextStyle(
fontSize: 16, color: Colors.red, fontWeight: FontWeight.bold),
),
Text(
'Fish size: ',
style: TextStyle(
fontSize: 16, color: Colors.red, fontWeight: FontWeight.bold),
),
SizedBox(
height: 20,
),
Low()
],
);
}
}
class Low extends StatelessWidget {
const Low({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'SpicyC is located at low place',
style: TextStyle(
fontSize: 16,
),
),
SizedBox(
height: 20,
),
SpicyC()
],
);
}
}
class SpicyC extends StatelessWidget {
const SpicyC({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(children: [
Text(
'Fish number: ',
style: TextStyle(
fontSize: 16, color: Colors.red, fontWeight: FontWeight.bold),
),
Text(
'Fish size: ',
style: TextStyle(
fontSize: 16, color: Colors.red, fontWeight: FontWeight.bold),
),
SizedBox(
height: 20,
),
]);
}
}
남자가 만든 수로를 코드로 표현 했을 때
Provider.of<Salmon>(context)
- provider : 생선 공급 남자
- Salmon : 생선 종류 = 데이터의 타입
- context : 수로
> 수로와 연결된 매운탕집에만 생선을 납품함 = 같은 context를 공유하는 위젯들만 데이터를 받을 수 있음
예시) 물고기 잡는 남자3
생선 공급하는 남자도 매운탕집을 운영했었음즉, 다른 매운탕집과 같은 정체성을 가짐
Provider == Widget
- provider도 하나의 위젯
- 위젯 트리 상, 일반 위젯과 똑같은 특성을 가짐
- 산중턱에서 물고기를 흘려보내듯이 상단에 위치해야함
Widget build(BuildContext context) {
return Provider(
create: (context) => FishModel(name: 'Salmon', number: 10, size: 'big'),
child: MaterialApp(
home: FishOrder(),
),
);
}
> create 메서드 통해서 FishModel 클래스 리턴해주면 Provider의 child가 된 MaterialApp 아래 모든 위젯에서 FishModel 인스턴스에 접근 가능
Text(
'Fish number: ${Provider.of<FishModel>(context).number}', //주문한 생선 갯수
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),
),
> 필요한 데이터를 받을 수 있게 전달
>> State Management 정의 중 1번 위젯트리 상에서 위치를 막론하고 데이터를 필요로 하는 위젯이 쉽게 데이터에 접근할 수 있는 방법
조금 매운맛 Provider 입문 2: ChangeNotifierProvider와 MultiProvider
* 중요
Provider 사용할 때, 어떤 데이터를 사용할지에 대한 정의 담은 클래스에근거해서 새로운 Provider 생성한다는 것
Provider 이용해서 특정 위젯 리빌드 하는 방법
> 플러터의 ChangeNotifier class
Mixin vs. extends(상속)
> 상속 받아서 SmparPhone 클래스는 SmparPhone 클래스의 변수, 메서드 다 쓸수 있음
> main 메서드에서 생성되는 인스턴트는 SmparPhone 타입이자 SmparPhone 타입
class SaveDate{
void addMemory(){
print('add external memory card...')
}
}
class SmartPhone extends ButtonPhone with SaveDate{
String? country;
int? year;
SmartPhone(this.country, this.year);
}
> saveDate 클래스 만들고 smartPhone 클래스에 with 키워드로 연결
> mixin 으로 바꿔도 잘 작동
extends | mixin |
상속 | 혼합해서 넣다?.. |
친족의 관계처럼 결속력이 강함 | 필요에 따라 단순 특정 기능 추가할때 사용 |
플러터는 다중상속을 허용하지 않기에 1개의 자손클래스만 상속 받을 수 있음 | 여러 클래스에 함께 사용 가능 |
![]() |
데이터가 변화 했을 때, 관련된 위젯들에게 자동으로 변화를 알려주고 UI 리빌드 시킬 수 있을까?
> ChangeNotifier class But 한계가 있음
> 그래서 ChangeNotifierProvider 사용
ChangeNotifierProvider
1. 모든 위젯들이 listen할 수 있는 ChangeNotifier 인스턴스 생성
2. 자동으로 필용 없는 ChangeNotifier 제거
3. Provider.of를 통해서 위젯들이 쉽게 ChangeNotifier 인스턴스에 접근할 수 있게 해줌
4. 필요시 UI를 리빌드 시켜줄 수 있음
5. 굳이 UI를 리빌드 할 필요가 없는 위젯을 위해서 listen: false 기능 제공
//fish_model.dart
//추가
void ChangeFishNumber(){
number++;
notifyListeners();
}
//main.dart
//변경
provider => ChangeNotifierProvider
//버튼 추가
ElevatedButton(
onPressed: (){
Provider.of<FishModel>(context).changeFishNumber();
},
child: Text('Change fish number')
)
> 실행시키면 숫자가 증가하지 않고 에러뜸
> 이벤트 핸들러와 관련된 ElevatedButton은 리빌드 될 필요 없음
> listen: false 추가
MultiProvider
- ChangeNotifierProvider가 두개 있을 때 한 위젯에서 둘 다에 접근해야할때 사용하기 위한 방법
> 우측 코드 : Provider를 여러개 쓸때 코드로 표현하면 가독성도 떨어질거임
> 좌측 코드 : MultiProvider 사용시 간결해지고 명확성 증가하고 똑같은 결과 보장
//seafish_model.dart
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 changeFishNumber(){
tunaNumber++;
notifyListeners();
}
}
//main.dart
class SpicyB extends StatelessWidget {
const SpicyB({Key? key}) : super(key: key);
@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<SeaFishModel>(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 Nubmer')
),
Low(),
],
);
}
}
전체코드
// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_1/fist_model.dart';
import 'package:provider_1/seafish_model.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: [
ChangeNotifierProvider(
create: (context) =>
FishModel(name: 'Salmon', number: 10, size: 'big'),
),
ChangeNotifierProvider(
create: (context) =>
SeaFishModel(name: 'Tuna', tunaNumber: 0, size: 'Middle')
)
],
child: MaterialApp(
home: FishOrder(),
),
);
}
}
class FishOrder extends StatelessWidget {
const FishOrder({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Fish Order'),
),
body: Center(
child: Column(
children: [
Text(
'Fish name',
style: TextStyle(fontSize: 20),
),
SizedBox(
height: 20,
),
High()
],
),
),
);
}
}
class High extends StatelessWidget {
const High({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'SpicyA is located at high place',
style: TextStyle(fontSize: 16),
),
SizedBox(
height: 20,
),
SpicyA()
],
);
}
}
class SpicyA extends StatelessWidget {
const SpicyA({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'Fish number: ${Provider.of<FishModel>(context).number}', //주문한 생선 갯수
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,
),
Middle()
],
);
}
}
class Middle extends StatelessWidget {
const Middle({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'SpicyB is located at middle place',
style: TextStyle(fontSize: 16),
),
SizedBox(
height: 20,
),
SpicyB()
],
);
}
}
class SpicyB extends StatelessWidget {
const SpicyB({Key? key}) : super(key: key);
@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<SeaFishModel>(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 Nubmer')
),
Low(),
],
);
}
}
class Low extends StatelessWidget {
const Low({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
height: 20,
),
Text(
'SpicyC is located at low place',
style: TextStyle(
fontSize: 16,
),
),
SizedBox(
height: 20,
),
SpicyC()
],
);
}
}
class SpicyC extends StatelessWidget {
const SpicyC({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(children: [
Text(
'Fish number: ${Provider.of<FishModel>(context).number}',
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<FishModel>(context, listen: false).changeFishNumber();
},
child: Text('Change fish number'),
)
]);
}
}
// seafish_model.dart
import 'package:flutter/material.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();
}
}
//fish_model.dart
import 'package:flutter/material.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();
}
}
'공부 > Flutter' 카테고리의 다른 글
Flutter 스터디 31 MySQL 이용해서 플러터 앱 만들기 (0) | 2023.03.06 |
---|---|
Flutter 스터디 30 Cloud Firestore 기본 구조 및 기본 CRUD (0) | 2023.03.02 |
Flutter 스터디 28 채팅 기능 (0) | 2023.02.27 |
Flutter 스터디 27 Stream, Firebase Rule 및 로그아웃 기능 수정 (0) | 2023.02.23 |
Flutter 스터디 26 Firebase 연동 및 SignUp/SignIn 기능 구현 (0) | 2023.02.23 |