StreamProvider listening to User doesn't update when User changes

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)

Leave a Reply

Your email address will not be published. Required fields are marked *