Issue
In my app, I listen to changes from a User Document in Cloud Firestore.
I do this by getting the current user ID, and then getting the document associated with that ID.
class UserService {
...
//GET A USER'S INFORMATION AS A STREAM
// ? IF NO UID IS PASSED, IT GETS THE INFO OF THE CURRENT USER
Stream<User> getUserInfoAsStream({String uid}) async* {
if (uid == null) {
uid = await AuthService().getUID();
}
yield* Firestore.instance
.collection('users')
.document(uid)
.snapshots()
.map((doc) => User.fromFirestore(doc));
}
...
I then use a StreamProvider
to listen to the stream in my main.dart
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<User>.value(
value: UserService().getUserInfoAsStream(),
),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: SplashScreen(),
),
);
}
}
During the course of the app’s lifecycle, it works perfectly, but when the user signs out using FirebaseAuth.instance.signOut();
and then logs in with a different user, the stream remains constant (i.e it listens to the old uid stream), and the StreamProvider doesn’t listen to the new stream of data.
| Sign Out Code For Reference |
// ? SIGN OUT CODE: If user signed out, it returns true, else, false
Future<bool> signOut() async {
try {
await _firebaseAuth.signOut();
return true;
} catch (error) {
print(error);
return false;
}
}
| Where it is used |
FlatButton(
onPressed: () {
AuthService().signOut().then((value) =>
Navigator.of(context).pushAndRemoveUntil(
CupertinoPageRoute(
builder: (BuildContext context) {
return Onboarding();
}), (route) => false));
},
child: Text("Yes")),
To solve the problem, I would’ve passed the current uid to the StreamProvider instead, but I can only get the current uid asynchronously.
How can I listen to an asynchronous stream using the StreamProvider, and update it when the user changes?
EDIT: I managed to fix the problem to some extent by moving the provider up the widget tree to the screen immediately after the login page. But because providers are scoped, I had to create a completely new MaterialApp after my original MaterialApp which is messing up my some components in my app.
Is there any better workaround?
Solution
I managed to fix the problem by switching from the provider
package to get_it
.
get_it
allows you to register and unregister singletons, meaning that when a user logs in, I can register the singleton so it can be used across all screens that depend on it. Then, when I logout, I simply unregister it. That way, the User is always updated after signing in and out.
Here’s how to do it yourself.
Install the package get_it
in your pubspec.yaml.
get_it: ^4.0.2
Create a new file next to your main.dart
called locator.dart
. Inside it, add this code:
GetIt locator = GetIt.instance;
void setupLocator() {
// Replace this with the object you're trying to listen to.
User user;
Stream<User> userStream = UserService().getUserInfoAsStream();
userStream.listen((event) => user = event);
locator.registerLazySingleton(() => user); // Register your object
}
When you login, just call setupLocator();
and when you log out, use this code:
locator.unregister<User>();
That’s all I did to get it up and running!
Edit: I managed to make it even better and lighter by using a UserProvider
Singleton that listens to changes in Authentication and then gets the current user when a user logs in.
import 'package:planster/models/core/user.dart';
import 'package:planster/models/services/auth_service.dart';
import 'package:planster/models/services/user_service.dart';
class UserProvider {
// SINGLETON INITIALIZATION
static final UserProvider _singleton = UserProvider._internal();
factory UserProvider.instance() {
return _singleton;
}
UserProvider._internal() {
listenToUserAuthState();
}
// VARIABLES
User user;
void listenToUserAuthState() async {
AuthService().onAuthStateChanged.listen((event) {
uid = event.uid;
if (uid != null)
UserService().getUserInfoAsStream(uid: uid).listen((userEvent) {
user = userEvent;
});
});
}
}
Answered By – Wilson Wilson
Answer Checked By – Terry (FlutterFixes Volunteer)