코딩셰프 강의를 듣고 정리한 내용
State란
UI에 변화가 생기도록 영향을 미치는 데이터
- 앱수준의 데이터 : 서버와 연동해서 사용자 인증 or 서버에 저장된 정보를 가져와 화면에 보여주는등 앱 화면에 변화를 일으키는 모든 데이터
- 위젯수준의 데이터 : 빈 체크박스에 사용자가 체크하면 체크표시가 생기게되고 이 상태를 위젯차원에서 해결해서 화면에 변화가 생기게끔 해주는것
setState
위젯라이프사이클의 빌드 메서드를 호출해서 UI를 렌더링 하게 해준다. → 플러터에 기본적으로 내장된 State Management 방법중 하나
단점
비효율성 : 가령 플러스, 마이너스 라는 두가지 버튼 위젯이 존재하고 플러스 버튼을 누르면 플러스 포인트를 + 1, 마이너스 버튼을 누르면 마이너스 포인트를 -1 해주는 앱이 있다고 가정하자.
플러스버튼을 눌렀을때는 플러스 위젯만빌드해주면 되지만 setState를 사용하면 오직 한개의 위젯에대한 state를 업데이트 하려고 해도 그 이하 모든 위젯들을 리빌드 하기 떄문에 불필요한 마이너스 위젯 부분까지 렌더링이 발생한다.
동시성 : setState는 동시에 다른 위젯들의 state를 업데이트 시켜주지 못한다
StateManagement
State 관리를 하기 위해 필요한 조건
- 위젯트리상에서의 위치를 막론하고 데이터를 필요로 하는 위젯이 쉽게 데이터에 접근 가능해야한다.
- 변화된 데이터에 맞추어서 UI를 다시 그려주는 기능이 뒤따라야한다.
Provider
StateManagement를 위해 사용하는 플러그인
사용법
데이터 넘겨주는법
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Provider( // - 1
create: (context) => FishModel(name: 'Salmon', number: 10, size: 'big'), // - 2
child: MaterialApp(
home: FishOrder(),
),
);
}
}
- Provider는 데이터를 필요로 하는 위젯들보다 항상 상위에 위치해야 하므로 최상위 위젯인 MaterialApp을 Provider로 감싸준다
- create 메서드를 통해 클래스를 리턴해주면 Provider의 차일드가된 MaterialApp 아래모든 위젯에서 해당 인스턴스에 접근할 수 있게 된다. 즉 FishModel이라는 클래스를 모든 하위 위젯에서 접근 가능
데이터 받아서 사용하는법
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()
],
);
}
}
만약 Low이라는 하위 위젯에서 create메서드에서 리턴해준 FishModel에 접근하고자 한다면 Provider.of<DataType>(context).value 형식으로 클래스에 접근해서 원하는 데이터를 가져올 수 있게 된다.
.of 메서드는 주어진 컨텍스트를 거슬러 올라가면서 가장 가까이 있는 원하는 타입의 인스턴스를 찾아서 반환하라는 의미
원래대로라면 상위 위젯에서 부터 생성자를 통해 순차적으로 데이터(FishModel 인스턴스)를 계속해서 보내줬어야 하지만 Provider를 통해서 이러한 절차를 거치지 않고 쉽게 Low위젯에서 상위 데이터에 접근할 수 있게 된다.
✔️ 위젯트리상에서의 위치를 막론하고 데이터를 필요로 하는 위젯이 쉽게 데이터에 접근 가능해야 한다는 첫번째 조건 성립
ChangeNotifier
setState의 비효율성 문제를 해결하기 위해 모든 위젯들이 다시 리빌드 될 필요가 없고 변경된 State를 변경하기위한 특정 위젯만이 리빌드 되어야 하므로 해당 특정 위젯만 State의 변화에 귀를 기울이고 있으면 된다.
따라서 이처럼 데이터의 변화에 관심있는 위젯들에게 변화를 알려주고 필요시 UI를 업데이트 할 방법이 필요한데 ChangeNotifier를 활용하면 된다.
class FishModel with ChangeNotifier{
final String name;
final int number;
final String size;
FishModel({required this.name, required this.number, required this.size});
}
with는 extends와 비슷한 역할을 하지만 조금 더 결속력이 낮고, 상속의 경우 dart에서 다중상속을 지원하지 않지만 with는 중복해서 사용 가능하다. 정리하자면 with는 단순한 특정 기능을 추가하고 싶을때 사용한다.
단점
- ChangeNotifier는 notifyListeners() 메서드를 사용해 addListener로 ChangeNotifier 클래스를 listen하고있는 모든 위젯들에게 데이터가 변경 될 떄마다 이사실을 알려줄 수 있는데 매번 수동으로 addListener를 등록해주어야한다
- addListener는 자동 dispose가 되지 않으므로 마찬가지로 removeListener를 통해 매번 수동으로dispose해주어야한다.
- 인스턴스(예제의 경우 FishModel)를 매번 생성자를 통해 전달해야함
- UI 리빌드도 수동으로 해주어야한다
ChangeNotifierProvider
이러한 단점들을 해결하기 위해 ChangeNotifierProvider를 사용한다
장점
- 모든 위젯들이 listen할 수 있는 ChangeNotifier 인스턴스를 생성할 수 있다
- 자동으로 필요없는 ChangeNotifier Dispose시켜줌
- Provider.of를 통해 위젯들이 쉽게 ChangeNotifier 인스턴스에 접근 할 수 있게 해준다.
- 필요시 UI를 리빌드 시켜 줄 수 있다.
- ChangeNotifier에 접근은 필요하지만 리빌드할 필요는 없는 위젯들을 위해 listen:false 기능을 제공해준다.
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => FishModel(name: 'Salmon', number: 10, size: 'big'),
child: MaterialApp(
home: FishOrder(),
),
);
}
class SpicyC extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ...
ElevatedButton(
onPressed: (){
Provider.of<FishModel>(context, listen: false).changeFishNumber();
},
child: Text('Change Fish Number'),
)
]);
}
Provider와 마찬가지로 ChangeNotifierProvider로 최상위 위젯을 감싸주고 Provider.of<FishModel>(context).value 를 통해서 데이터를 가져온다.
이때 ElevatedButton은 굳이 리빌드 될 필요가 없는 위젯이므로 위와같이 listen:false를 사용하라는 에러가 발생하게되는데 context옆에 *listen*: false 를 넣어주면 해결
MultiProvider
두개의 다른 타입의 ChangeNotifierProvider가 필요하고 한 위젯에서 이 모두에게 접근해야 할 때 사용하기 위한 방법
다중 Provider 구조를 가진 경우 오른쪽과같이 계층구조를 가진다면 가독성이 떨어지고 코드가 비효율적이다. 대신 왼쪽 처럼 MultiProvider를 사용하는것이 더 좋다.
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: 10, size: 'middle'),
),
],
child: MaterialApp(
home: FishOrder(),
),
);
}
}
MultiProvider위젯으로 최상위 위젯을 감싸주고 List<provider> 타입의 providers 파라미터로 생성하고자 하는 Provider들을 넣어준다.
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<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(),
],
);
}
spicyB클래스 내에서 FishModel 타입의 프로바이더와 SeaFishModel 타입의 프로바이더 두개 모두에 접근 할 수 있게 된다.
출처 - 코딩셰프 유튜브 강의https://www.youtube.com/watch?v=-3iD7f3e_SU
'플러터 공부 > 프로젝트기록' 카테고리의 다른 글
플러터 - 구글API 지도앱 만들기 (0) | 2023.06.13 |
---|---|
플러터 - 넷플릭스 클론 (0) | 2023.06.12 |