Where who wants to meet someone
[Flutter] Firebase 인증 상태에 따른 GoRouter redirect 구현하기 본문
go_router | Flutter package
A declarative router for Flutter based on Navigation 2 supporting deep linking, data-driven routes and more
pub.dev
로그인 기능을 구현하다보니, 로그인 / 로그아웃을 할 때 context.go 메서드를 통해 화면을 전환하는 것이 아니라 자동으로 상태 반영이 되면 어떨까 하는 생각이 들게 되었다.
Flutter에서 Firebase 인증 시작하기 | Firebase 문서
Google I/O 2022에서 Firebase의 새로운 기능을 확인하세요. 자세히 알아보기 의견 보내기 Flutter에서 Firebase 인증 시작하기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류
firebase.google.com
현재 인증 상태 확인을 위해서는 다음의 3가지 메서드를 사용할 수 있다.
1. authStateChanges()
- 리스너가 등록된 직후
- 사용자가 로그인한 경우
- 현재 사용자가 로그아웃한 경우
2. idTokenChanges()
- 리스너가 등록된 직후
- 사용자가 로그인한 경우
- 현재 사용자가 로그아웃한 경우
- 현재 사용자의 토큰이 변경된 경우
3. userChanges()
- 리스너가 등록된 직후
- 사용자가 로그인한 경우
- 현재 사용자가 로그아웃한 경우
- 현재 사용자의 토큰이 변경된 경우
- FirebaseAuth.instance.currentUser에서 제공하는 다음 메서드가 호출되는 경우:
- reload()
- unlink()
- updateEmail()
- updatePassword()
- updatePhoneNumber()
- updateProfile()
지금 프로젝트의 상황에서는 토큰을 사용하거나 currentUser의 메서드 감지 목적이 아닌 로그인/로그아웃에 의한 상태 변화만 원하기 때문에 authStateChanges를 사용하기로 했다.
처음에는 authStateChanges()가 Stream<User?>이기 때문에 RootPage를 만들고, StreamBuilder로 해당 메서드에 따라 Auth() 혹은 Home() 위젯을 반환하면 되지 않을까 했지만..
실제로 구현을 해보니 첫 순간에만 화면이 변하고(Auth -> Home || Home -> Auth) 두 번째 부터는 변화가 없었다.
중단점을 찍어본 결과, 로그인/로그아웃 시 해당 코드가 실행되긴 하지만 화면이 변하지 않아 Hot Reload를 하거나 Restart를 해야 변한 것을 확인할 수 있었다.
stateless -> state, WidgetsBinding.instance.addPostFrameCallback(이는 혹시 몰라 router로 이동해볼까 했던 것인데, 위젯이 그려지기 전 router를 호출하는 이슈가 있어 위젯이 그려진 이후 메서드를 사용하고자) 등 다양한 방법을 써보았으나 원하는 방식으로 동작하지 않았다.
그러던 중, 라우팅에 사용했던 GoRouter에 redirect 기능이 있다는 것을 알게 되었고 이를 사용해 보기로 했다.
결과는? 실패.
redirect에 똑같이 조건문과 분기를 넣어도, 로그인과 로그아웃 시 트리거가 되지 않았다.
go_router
Multiple redirections It's possible to redirect multiple times w/ a single navigation, e.g. / => /foo => /bar. This is handy because it allows you to build up a list of routes over time and not to worry so much about attempting to trim each of them to thei
docs.page
go_router의 redirection 문서를 보니, go_router는 앱의 상태 변화를 모르기 때문에 redirect만으로는 변화를 줄 수 없어 수동으로 라우팅을 해야 했다.
대신, 앱의 상태에 따라 go_router가 자동으로 redirect를 하기 위해서는 refreshListenable을 사용할 수 있다고 한다.
If you'd like to have the app's state cause go_router to automatically redirect, you can use the refreshListenable
argument of the GoRouter constructor:
해당 생성자에는 Listenable 타입이 들어가야 하는데, 이건 무엇인가 하니
Listenable class - foundation library - Dart API
An object that maintains a list of listeners. The listeners are typically used to notify clients that the object has been updated. There are two variants of this interface: Many classes in the Flutter API use or implement these interfaces. The following su
api.flutter.dev
공식문서에 따르면 객체가 업데이트되었음을 클라이언트에 알리는데 사용하는 리스너 목록을 관리하는 객체라고 한다.
ChangeNotifier와 ValueNotifer를 통해 이 인터페이스를 사용할 수 있는데 User 값을 사용할 것이 아니기 때문에 ChangeNotifier를 써보기로 했다.
Since the loginInfo is a ChangeNotifier, it will notify listeners when it changes. By passing it to the GoRouter constructor, go_router will automatically refresh the route when the login info changes. This allows you to simplify the login logic in your app:
final class _AuthChangeNotifier extends ChangeNotifier {
late final StreamSubscription<User?> _authSubscription;
_AuthChangeNotifier() {
_authSubscription = FirebaseAuth.instance.authStateChanges().listen((_) {
notifyListeners();
});
}
@override
void dispose() {
_authSubscription.cancel();
super.dispose();
}
}
StreamSubscription<User?> 에 FirebaseAuth.instance.authStateChanges().listen을 할당하고, 상태 변경이 일어날 때마다 notifiyListeners를 사용하여 변경이 일어났음을 알려주도록 했다.
또한 상태 변경에 사용한 _authSubscription이 StreamSubscription 타입이기 때문에 dispose시 cancel로 구독을 취소하도록 했다.
이제 트리거를 해줄 refreshListenable과 redirect가 구성되었으므로 실제로 테스트만 해보면 된다.
final GoRouter router = GoRouter(
redirect: (context, state) {
final user = FirebaseAuth.instance.currentUser;
if (user == null && state.fullPath == '/home') {
return '/auth';
} else if (user != null && state.fullPath == '/auth') {
return '/home';
}
return null;
},
refreshListenable: _authChangeNotifier,
routes: <RouteBase>[
GoRoute(
path: '/auth',
builder: (context, state) => const Auth(),
),
GoRoute(
path: '/home',
builder: (context, state) => const Home(),
),
]
)
아. refreshListenable에서 _AuthChangeNotifier()로 인스턴스화 해서 넣었었는데, router가 리빌드되는 경우를 생각하면 매번 새로운 인스턴스가 생성되고 불필요한 구독이 생길 수 있으므로 다른 방법이 필요했다.
Riverpod을 사용했기 때문에 ChangeNotiferProvider를 써야하나 했지만 router가 ConsumerWidget 내부에 있는 것이 아니라 전역적으로 생성 후 MaterialApp.router에 전달되는 구조기 때문에 ref를 사용할 수 없었다.
(이는 좀 더 생각해보고자 한다)
이에 _AuthChangeNotifier를 인스턴스화 한 것을 router에 전달하는 방식을 사용했다.
final _AuthChangeNotifier _authChangeNotifier = _AuthChangeNotifier();
final class _AuthChangeNotifier extends ChangeNotifier {
late final StreamSubscription<User?> _authSubscription;
_AuthChangeNotifier() {
_authSubscription = FirebaseAuth.instance.authStateChanges().listen((_) {
notifyListeners();
});
}
@override
void dispose() {
_authSubscription.cancel();
super.dispose();
}
}
이렇게 하면 로그인 버튼에서는 signIn 메서드만, 로그아웃 버튼에서는 signOut 메서드만 호출하면 바뀐 사용자 상태에 따라 router가 자동으로 트리거되어 라우팅해주게 된다.
'Flutter' 카테고리의 다른 글
[Flutter] Firebase에 사용할 SHA1, SHA-256 확인하기 (2) | 2024.09.26 |
---|