Flutter 개발일지 day 8
MultiProvider 도입
Provider를 여러 번 호출하는 경우를 위해, multiprovider 형태로 수정했다. 주간, 월간 화면으로 전환할 때 매번 인자를 전달하는 게 까다로워서 IsMonth 변수를 Provider로 선언했다. 이때, 잘 보면 MaterialApp 위에 한 번 더 builder를 구현해둔 것을 볼 수 있다. 여기에 build를 하지 않으면 오류가 뜨는데, 이는 home에서 Provider.of<IsMonth>(context).isMonth
를 호출할 때 Provider 그 아래에 build를 통해 생성된 context가 없어서 생긴 문제인 것 같다. 대충
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//최초
Widget build(BuildContext context)
return Provider(
create : context ~,
child : MaterialApp(
home: MonthCal(),
)
)
// monthCal.dart
class MonthCal extends StatefulWidget{...}
class _MonthCalState extends State<MonthCal>{
Widget build(BuildConext context){
//여기서 Provider.of<Cursor>(context) 부름
}
}
//--------------------------------------------
// 수정 이후
Widget build(BuildContext context)
return Provider(
create : context ~,
child : MaterialApp(
home: Provider.of<IsMonth>(context).isMonth ? MonthCal() : WeelCal(), // 여기서 context 부름
)
)
위를 살펴보면, 최초로 Provider를 호출한 곳은 MonthCal에서 Widget Build를 한 이후이다. 이때는 context가 제대로 전달 되었기 때문에 문제가 발생하지 않는다. 하지만 두 번째를 보면, isMonth를 가져올 때 참조할 context가 Provider보다 상위에 존재한다. 그렇기에 Provider가 제대로 context를 읽어올 수 없는 것이다. 따라서 해결책으로, MaterialApp을 builder를 감싸서 Provider 아래에 context를 하나 추가해 주었다. 이렇게 하면 Provider를 불러오는 모든 Provider.of ~ 요소들이 Provider보다 하위 context에 존재하게 돼서 문제가 해결된다.
Appbar 수정
monthCal.dart에서 appbar에 이것저것 버튼을 추가하다보니 appbar가 너무 길어져서 이를 calenderElement.dart로 이동했다. 다른 widget들과 마찬가지로 appbar도 그냥 불러오면 될 줄 알았는데 얘는 PreferredSizeWidget를 implements해서 특정 사이즈를 주고 가져와야하는 것 같더라.
1
2
3
4
5
6
// MonthAppbar class
@override
Size get preferredSize => Size.fromHeight(appbar.preferredSize.height);
// MonthCal에서 호출 시에
appBar : MonthAppbar(appbar: AppBar())
이런 형태로만 가져올 수 있었다. 이제 Appbar에 어떤 기능을 추가했는지 살펴보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//AppBar actions 부분
actions: [
IconButton(
visualDensity: const VisualDensity(horizontal: -4.0),
onPressed: () {
Provider.of<Cursor>(context, listen: false).plusWeek(false);
}, icon: const Icon(Icons.arrow_back_ios_new_rounded, color: Colors.black54,),
splashRadius: 20,
),
IconButton(
visualDensity: const VisualDensity(horizontal: -4.0),
onPressed: () {
Provider.of<Cursor>(context, listen: false).plusWeek(true);
}, icon: const Icon(Icons.arrow_forward_ios_rounded, color: Colors.black54)
),
IconButton(
visualDensity: const VisualDensity(horizontal: -4.0),
onPressed: () {
Provider.of<IsMonth>(context, listen: false).changeIsMonth();
}, icon: const Icon(Icons.change_circle_rounded), color: Colors.black54,),
IconButton(
visualDensity: const VisualDensity(horizontal: -4.0),
icon: const Icon(Icons.today, color: Colors.black54,),
onPressed: () {
Provider.of<Cursor>(context, listen: false).changeCursor(DateTime.now());
},
),
]
actions 부분은 appbar에 여러가지 기능들을 넣을 때 사용된다. 여기에 actions를 통해 4가지 버튼을 넣었다.
- 이전 달로 이동 : plusWeek(false)를 통해 이전 달로 이동시켰다.
- 다음 달로 이동 : plusWeek(true)를 통해 다음 달로 이동시켰다.
- 주간<->월간 전환 : changeIsMonth()함수를 통해 isMonth의 value를 바꾸어 weekCal 화면과 monthCal 화면을 전환할 수 있게 했다.
- 오늘로 이동 : calender에서 다른 날을 보다가도, 이 버튼을 누르면 Cursor에 DateTime.now()를 할당해서 오늘로 이동하도록 했다.
WeekCal 추가
맨 위에 MultiProvider 도입 부분에서 볼 수 있는데, isMonth 변수를 ChangeNotifierProvider를 통해 선언했기 때문에 isMonth 변수를 listen하고 있다가 notifyListener를 통해 변화를 감지하면 이를 listen해서 WeekCal과 MonthCal을 바꾸어준다.
WeekCal에서는 MonthCal에서 했던 것과 동일하게, row에 7개의 WeekDays widget을 넣어서 한 주를 보여준다. WeekDays widget은 MonthDays 위젯과 완전히 동일하다(아직까지는).
Cursor.dart 수정
1
2
3
4
5
6
7
8
9
10
11
12
class IsMonth with ChangeNotifier{
late bool isMonth;
IsMonth({
required this.isMonth
});
void changeIsMonth(){ //month <-> week간 전환
isMonth = !isMonth;
notifyListeners();
}
}
위에서 썼던 isMonth 함수는 그냥 반대값 대입해주는 함수다. 대입하고 나서는 notiftListener로 알려준다. 그리고 여기에는 따로 안 적었는데, Cursor class에 plusMonth와 거의 똑같은 역할을 하는 plusWeek 함수를 추가했다(true이면 +7일, false이면 -7일).
배운점
-
Provider를 쓸 때 context의 계층?과 관련해서 그 중요성을 좀 배운 것 같다. 맨 위에 언급했던 내용처럼 Provider가 자신이 선언되어 있는 계층에서 호출해버리면 아무래도 하위 계층에서 호출 했던 것과는 다르게 자신의 context를 전달한 적이 없으니까 오류가 나는게 당연한 것 같다.
-
삼항연산자를 통핸 편리한 화면 전환을 구현할 수 있었다. MonthCal과 WeekCal 사이에서 어떻게 자유자재로 오갈지 좀 고민을 했었는데, isMonth 변수를 Provider로 선언을 하고 이를 listen 시키니까 언제든지 간에 내가 버튼을 누르기만 하면 자동으로 화면을 바꿔주는게 Navigator을 이용한 것보다 더 편했다. 사실 Navigator.push 형태로 하면 뒤에서 화면이 돌아가고 있는 와중에 위에 화면을 하나 더 쌓아올리는 형태라 뭔가 동시에 실행 해서 낭비하는 것 같은 느낌? 그래서 일부로 이런 방식을 택했다.