티스토리 뷰
화면의 설정 정보를 담는 Route는 여러 Callback methods를 가지고 있다.
이 메서드들은 자신이 Navigator에 push/pop 될 때 호출되고, nextRoute가 push/pop될때도 호출된다.
앱을 개발할 때 현재 화면이 Natigator에서 가장 위에 표시되는 화면인지 알고 싶을 때가 있다. 특히 다음 화면으로 넘어갔다가 다시 이전화면으로 돌아올 때 화면의 데이터를 갱신하거나 기타 필요한 동작을 해야 할 때가 있기 때문이다.
물론 await Nativiator.push()를 처럼 await구분을 이용해서 다음 화면이 pop될때 까지 기다리는 방법도 있지만 화면이 복잡해지고 연결된 다음화면이 여러 개 있을 경우 일괄적인 처리가 필요할 때도 있다.
Android에서는 Activity의 Lifecycle 이벤트를 이용해서 Resume/Pause 되는 이벤트를 수신할 수 있지만 Flutter에서는 그와 같은 이벤트를 발생키시지 않는다.
Navigator는 내부적으로 _RouteEntry이라는 Route의 랩핑 클래스를 따로 만들어 Route를 관리하는데 _RouteEntry는 별도의 상태정보인 _RouteLifecycle를 사용한다. 이 값들은 Navigator에서 내부적으로 사용하기 위한 Inner class로 외부에서 직접 사용하기에는 문제가 있다.
이를 해결하기 위해서 우리가 할 수 있는 몇가지 방법이 있는데 그중 한 가지 방법을 소개하고자 한다.
Route Callback Methods
아래 그림은 Navigator/_RouteEntiry /Route/Widget(화면)의 관계를 나타 내고 있다.
우리가 직접 다루는 Widget에서 _RouteLifecycle과 유사한 화면의 Lifecycle정보가 필요할때가 있다.
개발자가 활용 할수 있는 방법은 Route의 Callback methods를 이용해서 Route를 Lifecycle정보를 직접 관리하고 이 값이 변경될 때 Widget에게 전달할 수 있도록 하면 된다.
그렇다면 우선 Route에서 활용 가능한 Callback Method들에 대해서 알아보자.
아래코드는 Route의 전체 코드 중 Callback Method만 따로 정리한 것이다.
TickerFuture didPush() { }
void didAdd() { }
void didReplace(Route<dynamic>? oldRoute) { }
bool didPop(T? result) {}
void didComplete(T? result) {}
void didPopNext(Route<dynamic> nextRoute) { }
void didChangeNext(Route<dynamic>? nextRoute) { }
void didChangePrevious(Route<dynamic>? previousRoute) { }
void dispose() { }
함수 명에서 어느 정도 의미를 파악할 수 있겠지만 Route가 Navigator에 push / pop 되는 상태에 따라 호출되는 didPush/didPop 같은 함수도 있고 다음 Route(nextRoute)가 Navigator에 push/pop 되었을 때 알려주는 didChangeNext / didPopNext과 같은 함수도 있다.
Navigator가 Route의 Callback 함수를 호출하는 것을 이용해서 Route의 자체적인 상태를 정의해 볼 수 있다.
우선 안드로이드의 Activity의 상태를 참고해서 created/ resumed / paused의 상태가 있다고 가정해 보자
Enum RouteLifecycle { created, resumed, paused, destroryed }
created는 처음 Route의 상태이며, didPush가 발생하면 resumed로 didPop이 발생하면 destroryed로 변경된다.
그리고 nextRoute가 push 되는 상태를 알려주는 didChangeNext가 발생하면 paused로 didPopNext가 발생하면 resumed로 변경된다.
위 LifeCycle의 상태정의는 Flutter에서 제공하는 것이 아닌 개발자가 임의로 정해서 만든 것으로 Route의 Callback함수를 활용해서 정의한 것임에 유의하기 바란다. (필요에 따라 상태를 정의해서 사용하세요)
그런 이제 위 이벤트에 따라 Route의 상태를 변경하는 CustomPageRoute를 만들어 보자.
아래 Route는 몇 가지 Callback함수를 재정위 한 후는 ValueNotifier를 통해서 RouteLifecycle의 변경된 값이 외부로 전달될 수 있도록 구성하였다.
class CustomPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixin<T> {
final ValueNotifier<RouteLifecycleState> lifecycleState = ValueNotifier(RouteLifecycleState.created);
Route? _nextRoute;
CustomPageRoute({required super.builder});
@override
void didAdd() {
lifecycleState.value = RouteLifecycleState.created;
super.didAdd();
}
@override
TickerFuture didPush() {
lifecycleState.value = RouteLifecycleState.resumed;
return super.didPush();
}
@override
void didChangeNext(Route? nextRoute) {
if (_nextRoute == null && nextRoute != null) {
stateListener?.onRouteStateChanged(route: this, state: RouteState.paused);
}
_nextRoute = nextRoute;
super.didChangeNext(nextRoute);
}
@override
void didPopNext(Route nextRoute) {
lifecycleState.value = RouteLifecycleState.resumed;
_nextRoute = null
super.didPopNext(nextRoute);
}
@override
void dispose() {
lifecycleState.dispose();
_nextRoute = null
super.dispose();
}
}
위 코드는 lifecycleState의 값을 callback함수를 override 해 수정하는 코드만 추가된 상태이다.
Route 상태 전달하기
이제 이렇게 변경된 lifecycleState를 Widget에게 전달하는 방법을 찾아야 한다.
Route에서 생성하는 Widget의 build를 위임할 CustomPageBuilder 함수를 정의하자.
typedef CustomPageBuilder = Widget Function(
BuildContext context,
ValueNotifier<RouteLifecycleState> lifecycleState,
);
이제 CustomPageRoute를 통해 화면정보를 설정할 때에는 context와 lifecycleState를 모두 argument로 받아서 사용할 수 있다.
CustomPageRoute를 생성할 때는 CustomPageBuilder를 생성자 argument로 전달하도록 CustomPageRoute의 생성자를 추가해야 한다. 그리고 buildContent함수를 override 해서 builder에서 생성한 Widget을 전달하도록 변경하자.
class CustomPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixin<T> {
final ValueNotifier<RouteLifecycleState> lifecycleState = ValueNotifier(RouteLifecycleState.created);
final CustomPageBuilder builder;
CustomPageRoute(this.builder);
……..
@override
Widget buildContent(BuildContext context) => builder(context, lifecycleState);
}
이제 필요한 기본 준비는 완료되었다.
Navigator에 새로운 화면을 push 하기 위해 CustomPageRoute를 사용하게 되면 builder함수의 argument로 들어온 lifecycleState를 화면을 표시할 Widget으로 전달해서 route의 lifecycle상태를 구독할 수 있도록 하자.
final route = CustomPageRoute((context, routeLifecycleState) {
return LoginPageWidget(routeLifecycleState: routeLifecycleState);
});
Navigator.push(context, route);
class LoginPageWidget extends StatefulWidget {
final ValueNotifier<RouteLifecycleState>? routeLifecycleState;
const LoginPageWidget({super.key, this.routeLifecycleState});
@override
State<LoginPageWidget> createState() => _LoginPageWidgetState();
}
class _LoginPageWidgetState extends State<LoginPageWidget> {
ValueNotifier<RouteLifecycleState>? routeLifecycleState;
@override
void initState() {
super.initState();
routeLifecycleState = widget.routeLifecycleState;
routeLifecycleState?.addListener(() {
switch (routeLifecycleState?.value) {
case RouteLifecycleState.resumed:
// do something
break;
case RouteLifecycleState.paused:
// do something
break;
default:
break;
}
});
}
@override
Widget build(BuildContext context) {
throw UnimplementedError();
}
}
Widget은 위에서 처럼 StatefulWidget으로 만들어 사용할 수도 있고 별도의 상태관리 라이브러리를 사용할 수도 있다.
하지만 중요한 것은 위젯을 생성자를 통해 ValueNotifier인 RouteLifecycleState를 수신할 수 있다는 것이며 이것을 활용해 원하는 작업을 해줄 수 있다.
Route의 상태 변경을 Widget로 전달할 수 있는 방법은 위에서 제시한 방법되에도 여러 가지 생각해 볼 수 있는 것 있다.
예를 들면 상태정보를 InheritedWidget으로 만든 후 Route의 상태가 변경될 때 하위 위젯에 변경된 정보를 전달하도록 하는 방법도 사용할 수 있고, 그 외 Provider와 같은 라이브러리를 사용해서 InheritedWidget처럼 하위로 위젯으로 상태변경 이벤트를 전파할 수도 있을 것이다.
마치며
Route의 상태변경 이벤트를 Widget에서 알 필요가 없다고 생각하실 수도 있습니다. 하지만 Route에 정의된 Callback 함수들을 활용하면 상태정보를 알 수 있다는 것 정도는 알고 있으면 좋지 않을까요?
Flutter는 개발자의 성향에 따라 아주 많이 유연하게 프로그램을 구성할 수 있습니다. 그래서 다른 것들에 비해서 해결책이 다양하게 나올 수 있습니다. 어쩌면 위에서 소개한 방법 외에도 Route의 상태 이벤트를 Widget으로 전파시키는 다양한 방법들이 있을 수 있으니 자신만의 방법을 찾아보시는 것도 좋을 것 같습니다.
"3부 Navigator 1.0 vs Navigator 2.0" 에서 계속
'Flutter' 카테고리의 다른 글
[Flutter] Navigator와 Route - 1부 화면의 전환 (0) | 2023.02.17 |
---|---|
[Flutter] TDD로 가는 길 - 2부 TDD 가능한 구조로 설계하기 (1) | 2022.11.01 |
[Flutter] TDD로 가는 길 - 1부 앱 개발에 TDD를 적용해야 하는 이유 (0) | 2022.10.18 |
[Flutter] Widget과 Element - 4부 결론 (0) | 2022.09.29 |
[Flutter] Widget과 Element - 3부 GlobalKey와 LocalKey (0) | 2022.09.20 |
- Total
- Today
- Yesterday
- MVVM
- FlutterEngine
- Route
- Android
- Widget Tree
- Element LifeCycle
- Flutter LifeCycle
- StatefulWidget LifeCycle
- flutter element
- flutter_secure_storage
- flutter i18n
- dart enum
- enum member
- Flutter3.0
- DART
- freezed
- dart 2.17
- flutter 다국어처리
- Mutiple Flutter
- flutter l10n
- json_serializable
- python3
- flutter mvvm
- flutter2.0
- widget element
- Flutter TDD
- navigator
- RenderObject
- LocalKey
- Flutter
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 | 30 | 31 |