如何在 Flutter 中将 Stream 转换为 Listenable?
How to convert a Stream to a Listenable in Flutter?
我正在尝试弄清楚如何利用 Firebase 的 onAuthStateChanges()
流在 go_router 包的 refreshListenable
参数中用作 Listenable,以便在 authState 更改时重定向.
此外,我正在使用 flutter_riverpod 进行状态管理。
到目前为止,我的代码如下所示:
我创建了一个简单的 AuthService class(缩小到最重要的部分):
abstract class BaseAuthService {
Stream<bool> get isLoggedIn;
Future<bool> signInWithEmailAndPassword({ required String email, required String password });
}
class AuthService implements BaseAuthService {
final Reader _read;
const AuthService(this._read);
FirebaseAuth get auth => _read(firebaseAuthProvider);
@override
Stream<bool> get isLoggedIn => auth.authStateChanges().map((User? user) => user != null);
@override
Future<bool> signInWithEmailAndPassword({ required String email, required String password }) async {
try {
await auth.signInWithEmailAndPassword(email: email, password: password);
return true;
} on FirebaseAuthException catch (e) {
...
} catch (e) {
...
}
return false;
}
接下来我创建了这些提供程序:
final firebaseAuthProvider = Provider.autoDispose<FirebaseAuth>((ref) => FirebaseAuth.instance);
final authServiceProvider = Provider.autoDispose<AuthService>((ref) => AuthService(ref.read));
如前所述,我想以某种方式监听这些 authChanges 并将它们传递给路由器:
final router = GoRouter(
refreshListenable: ???
redirect: (GoRouterState state) {
bool isLoggedIn = ???
if (!isLoggedIn && !onAuthRoute) redirect to /signin;
}
)
我真的不知道如何使用 riverpod 执行此操作,但我认为使用 riverpod 不需要上下文。使用 Provider 我会做这样的事情:
// Somewhere in main.dart I register my dependencies with Provider:
Provider(
create: (context) => AuthService(//pass whatever),
// ...
// Somewhere in my *stateful* App Widget' State:
// ...
late ValueListenable<bool> isLoggedInListenable;
@override
void initState(){
// locate my authService Instance
final authService = context.read<AuthService>();
// as with anything that uses a stream, you need some kind of initial value
// "convert" the stream to a value listenable
isLoggedInListenable = authService.isLoggedIn.toValueListenable(false);
super.initState();
}
@override
Widget build(BuildContext context){
final router = GoRouter(
refreshListenable: isLoggedInListenable,
redirect: (GoRouterState state) {
bool isLoggedIn = isLoggedInListenable.value;
if (!isLoggedIn && !onAuthRoute) //redirect to /signin;
// ...
}
);
return MaterialApp.router(
// ...
);
}
这是将流“转换”为 ValueListenable 的扩展
extension StreamExtensions<T> on Stream<T> {
ValueListenable<T> toValueNotifier(
T initialValue, {
bool Function(T previous, T current)? notifyWhen,
}) {
final notifier = ValueNotifier<T>(initialValue);
listen((value) {
if (notifyWhen == null || notifyWhen(notifier.value, value)) {
notifier.value = value;
}
});
return notifier;
}
// Edit: added nullable version
ValueListenable<T?> toNullableValueNotifier{
bool Function(T? previous, T? current)? notifyWhen,
}) {
final notifier = ValueNotifier<T?>(null);
listen((value) {
if (notifyWhen == null || notifyWhen(notifier.value, value)) {
notifier.value = value;
}
});
return notifier;
}
Listenable toListenable() {
final notifier = ChangeNotifier();
listen((_) {
// ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
notifier.notifyListeners();
});
return notifier;
}
}
之所以有效,是因为 ValueListenable 是一个 Listenable!与 ChangeNotifier 相同(它也只是保存数据)。
在您的情况下,如果您可以在声明路由器之前获取您的 authService 实例,则可以将流转换为可侦听的,然后使用它。确保它是小部件状态的一部分,否则您可能会收集通知程序垃圾。另外,我添加了一个 notifyWhen
方法,以防您想按条件过滤通知。在这种情况下不需要,ValueNotifier 只会在值实际更改时通知。
再补充一点,对于使用 flutter_bloc
的人来说,此扩展也适用:
extension BlocExtensions<T> on BlocBase<T> {
Listenable asListenable() {
final notifier = ChangeNotifier();
stream.listen((_) {
// ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
notifier.notifyListeners();
});
return notifier;
}
ValueListenable<T> asValueListenable({
BlocBuilderCondition? notifyWhen,
}) {
final notifier = ValueNotifier<T>(state);
stream.listen((value) {
if (notifyWhen == null || notifyWhen(notifier.value, value)) {
notifier.value = value;
}
});
return notifier;
}
}
根据go_router文档,您可以简单地使用以下方法:
https://gorouter.dev/redirection#refreshing-with-a-stream
GoRouterRefreshStream(_fooBloc.stream)
我正在尝试弄清楚如何利用 Firebase 的 onAuthStateChanges()
流在 go_router 包的 refreshListenable
参数中用作 Listenable,以便在 authState 更改时重定向.
此外,我正在使用 flutter_riverpod 进行状态管理。
到目前为止,我的代码如下所示:
我创建了一个简单的 AuthService class(缩小到最重要的部分):
abstract class BaseAuthService {
Stream<bool> get isLoggedIn;
Future<bool> signInWithEmailAndPassword({ required String email, required String password });
}
class AuthService implements BaseAuthService {
final Reader _read;
const AuthService(this._read);
FirebaseAuth get auth => _read(firebaseAuthProvider);
@override
Stream<bool> get isLoggedIn => auth.authStateChanges().map((User? user) => user != null);
@override
Future<bool> signInWithEmailAndPassword({ required String email, required String password }) async {
try {
await auth.signInWithEmailAndPassword(email: email, password: password);
return true;
} on FirebaseAuthException catch (e) {
...
} catch (e) {
...
}
return false;
}
接下来我创建了这些提供程序:
final firebaseAuthProvider = Provider.autoDispose<FirebaseAuth>((ref) => FirebaseAuth.instance);
final authServiceProvider = Provider.autoDispose<AuthService>((ref) => AuthService(ref.read));
如前所述,我想以某种方式监听这些 authChanges 并将它们传递给路由器:
final router = GoRouter(
refreshListenable: ???
redirect: (GoRouterState state) {
bool isLoggedIn = ???
if (!isLoggedIn && !onAuthRoute) redirect to /signin;
}
)
我真的不知道如何使用 riverpod 执行此操作,但我认为使用 riverpod 不需要上下文。使用 Provider 我会做这样的事情:
// Somewhere in main.dart I register my dependencies with Provider:
Provider(
create: (context) => AuthService(//pass whatever),
// ...
// Somewhere in my *stateful* App Widget' State:
// ...
late ValueListenable<bool> isLoggedInListenable;
@override
void initState(){
// locate my authService Instance
final authService = context.read<AuthService>();
// as with anything that uses a stream, you need some kind of initial value
// "convert" the stream to a value listenable
isLoggedInListenable = authService.isLoggedIn.toValueListenable(false);
super.initState();
}
@override
Widget build(BuildContext context){
final router = GoRouter(
refreshListenable: isLoggedInListenable,
redirect: (GoRouterState state) {
bool isLoggedIn = isLoggedInListenable.value;
if (!isLoggedIn && !onAuthRoute) //redirect to /signin;
// ...
}
);
return MaterialApp.router(
// ...
);
}
这是将流“转换”为 ValueListenable 的扩展
extension StreamExtensions<T> on Stream<T> {
ValueListenable<T> toValueNotifier(
T initialValue, {
bool Function(T previous, T current)? notifyWhen,
}) {
final notifier = ValueNotifier<T>(initialValue);
listen((value) {
if (notifyWhen == null || notifyWhen(notifier.value, value)) {
notifier.value = value;
}
});
return notifier;
}
// Edit: added nullable version
ValueListenable<T?> toNullableValueNotifier{
bool Function(T? previous, T? current)? notifyWhen,
}) {
final notifier = ValueNotifier<T?>(null);
listen((value) {
if (notifyWhen == null || notifyWhen(notifier.value, value)) {
notifier.value = value;
}
});
return notifier;
}
Listenable toListenable() {
final notifier = ChangeNotifier();
listen((_) {
// ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
notifier.notifyListeners();
});
return notifier;
}
}
之所以有效,是因为 ValueListenable 是一个 Listenable!与 ChangeNotifier 相同(它也只是保存数据)。
在您的情况下,如果您可以在声明路由器之前获取您的 authService 实例,则可以将流转换为可侦听的,然后使用它。确保它是小部件状态的一部分,否则您可能会收集通知程序垃圾。另外,我添加了一个 notifyWhen
方法,以防您想按条件过滤通知。在这种情况下不需要,ValueNotifier 只会在值实际更改时通知。
再补充一点,对于使用 flutter_bloc
的人来说,此扩展也适用:
extension BlocExtensions<T> on BlocBase<T> {
Listenable asListenable() {
final notifier = ChangeNotifier();
stream.listen((_) {
// ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
notifier.notifyListeners();
});
return notifier;
}
ValueListenable<T> asValueListenable({
BlocBuilderCondition? notifyWhen,
}) {
final notifier = ValueNotifier<T>(state);
stream.listen((value) {
if (notifyWhen == null || notifyWhen(notifier.value, value)) {
notifier.value = value;
}
});
return notifier;
}
}
根据go_router文档,您可以简单地使用以下方法: https://gorouter.dev/redirection#refreshing-with-a-stream
GoRouterRefreshStream(_fooBloc.stream)