아래와 같은 앱에서 TasksScreen
state를 ListTile로 보내려면, 쓰건 안 쓰건 무조건 TaskList와 TaskTile에도 전달해주고 rebuild 해줘야 비로소 ListTile에서 사용할 수 있다.
이렇게 전달해주는 방식 말고 간단히 TasksScreen에서 바로 ListTile로 보내는 것을 가능하게 해주는 패키지가 provider 패키지라고 한다.
provider package
provider package 한국어 소개 & 사용법
provider 패키지를 사용해보기 위해 새로운 프로젝트를 생성했다.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final String data = 'Data';
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: TextWidget(data),
),
body: Column(
children: [
UpperTextWidget(data),
],
),
),
);
}
}
class TextWidget extends StatelessWidget {
final String data;
TextWidget(this.data);
Widget build(BuildContext context) {
return Text(
data,
style: TextStyle(
fontWeight: FontWeight.bold,
),
);
}
}
class UpperTextWidget extends StatelessWidget {
final String data;
UpperTextWidget(this.data);
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(),
SizedBox(
height: 10.0,
),
OtherTextWidget(data),
],
),
);
}
}
class OtherTextWidget extends StatelessWidget {
final String data;
OtherTextWidget(this.data);
Widget build(BuildContext context) {
return Text(
data,
style: TextStyle(
fontSize: 20.0,
),
);
}
}
대충 이렇게 생긴 앱이다.
MyApp 의 데이터인 final String data;
를 파란색 OtherTextWidget
에 전달해주기 위해 data
가 필요치 않은 초록색 TextUpperWidget
에도 생성자를 만들어 data
를 받아오고 rebuild 되어야한다.
provider를 사용하면, 이런 불필요한 rebuild 과정이 생략되고 바로 데이터를 보낼 수 있다.
provider 사용법은 다음과 같다.
우선 사용을 위해 pubspec.yaml
파일의 dependencies에 provider를 추가해준다.
위에 뜨는 pubget을 눌러주고
provider를 import 해준다.
그리고 state를 사용하고자 하는 제일 높은 위젯을 provider로 감싸준다.
이 경우 MyApp이므로 MyApp을 감쌌다.
만약 데이터가 TextUpperWidget
에 존재하고 그 하위의 위젯들에서만 사용될 것이라면 TextUpperWidget
만 감싸면 된다.
공식문서를 읽어보니, Provider로 감싼 후에 아래와 같이 사용하라고 한다.
나의 경우 create: (_) => MyApp(),
를 넣어주면 된다.
그 후 value를 읽기위해 아래 공식문서에 나와있는 것처럼 context.watch<T>()
혹은 context.read<T>()
를 사용하면 된다.
(혹은 static method인 Provider.of<T>(context)
또는 Provider.of<T>(context, listen: false)
를 사용하면 된다고 한다.)
watch는 위젯이 변화를 감지하게 하고 read는 변화를 감지하지 않는다고 한다.
이제 불필요한 생성자들을 모두 삭제하고 data
가 사용되는 TextWidget
과 OtherTextWidget
에 우리가 사용할 data
, 즉 context.watch<MyApp>().data
를 넣어주면 된다.
data
를 잘 읽고있는지 확인하기 위해 data
의 값을 'Data' 에서 'Provided Data'로 바꿔봤다.
둘 다 잘 바뀌는 모습을 확인할 수 있었다.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final String data = 'Provided Data';
Widget build(BuildContext context) {
return Provider(
create: (_) => MyApp(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(
title: TextWidget(),
),
body: Column(
children: [
UpperTextWidget(),
],
),
),
),
);
}
}
class TextWidget extends StatelessWidget {
//불필요한 생성자가 사라졌다.
Widget build(BuildContext context) {
return Text(
context.read<MyApp>().data,
style: TextStyle(
fontWeight: FontWeight.bold,
),
);
}
}
class UpperTextWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(),
SizedBox(
height: 10.0,
),
OtherTextWidget(),
],
),
);
}
}
class OtherTextWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Text(
context.read<MyApp>().data, // MyApp에서 바로 전달되는 모습
style: TextStyle(
fontSize: 20.0,
),
);
}
}
이제 바뀌는 것을 확인했으니, Input에 넣은 값을 data에 저장해볼것이다.
Provider
는 고정된 하나의 값을 사용하는 것에 적합하지만, 바뀌는 값에는 적합하지 않다.
따라서 감싸줬던 Provider
를 ChangeNotifierProvider<T>
로 바꿔주었다.
바뀔 값도 따로 class로 빼준 뒤 with ChangeNotifier
를 덧붙여야한다.
이는 notifyListenders();
를 사용하기 위함인데, data
를 받는 위젯들에게 notifyListeners();
를 통해 값이 변했음을 알려줘야 값이 제대로 바뀌기 때문이다.
또한 위와 같은 이유로 보통 onChanged 함수에서 쓰듯이 간단히 data = newValue
만을 적게되더라도 화면엔 아무런 변화도 나타나지 않을것이다.
notifyListeners();
를 통해 변화를 듣고있는 위젯들에게 변화가 생겼다고 알려줘야만 값이 바뀌기 때문이다.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return ChangeNotifierProvider<Data>( //Provider 에서 ChangenotifierProvider로 바꿔줬다.
create: (_) => Data(), // data가 새로운 class인 Data에 들어있으므로 Data로 바꿔준다.
child: MaterialApp(
home: Scaffold(
appBar: AppBar(
title: TextWidget(),
),
body: Column(
children: [
UpperTextWidget(),
],
),
),
),
);
}
}
class Data with ChangeNotifier {
String data = 'Provided Data'; // 변하지 않는 값이 아니므로 final을 제거해준다.
void changeValue(newValue) {
data = newValue;
notifyListeners();
}
}
class TextWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Text(
context.watch<Data>().data, // 가져오는 값이 변하기에 watch 사용
style: TextStyle(
fontWeight: FontWeight.bold,
),
);
}
}
class UpperTextWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
onChanged: (newValue) {
context.read<Data>().changeValue(newValue); // 변하지 않는 값이기에 read 사용
},
),
SizedBox(
height: 10.0,
),
OtherTextWidget(),
],
),
);
}
}
class OtherTextWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Text(
context.watch<Data>().data, // 가져오는 값이 변하기에 watch 사용
style: TextStyle(
fontSize: 20.0,
),
);
}
}
위의 코드는 아래와 같이 잘 동작한다.
일단 현재 필요한 Provider 공부는 이정도니까...
오늘은 여기서 끝이다!