Search code examples
firebaseflutterdartstreamprovider

StreamProvider and Firebase


void main() async {
  ...
  final AuthenticationProvider authenticationProvider = AuthenticationProvider();
  await authenticationProvider.initialize();
  runApp(
    MultiProvider(
      providers: [
        Provider<AuthenticationProvider>(create: (_) => authenticationProvider),
        StreamProvider<UserModel>(
          create: (_) => authenticationProvider.currentUser,
          initialData: UserModel(
            id: '',
            email: '',
            displayName: '',
            photo: null,
            premium: false,
            travels: [],
          ),
        )
      ],
      child: MiRoulotte(),
    ),
  );
}

I create a provider that generate a Stream with current user data that change when a user is sign out or sign in, but the data don't change. When I sign out, the stream should be null, and then when I sign in the stream should be the user data.

class AuthenticationProvider {
  Stream<UserModel>? _currentUser;
  Stream<UserModel>? get currentUser => this._currentUser;

  initialize() async {
    ...
    this._currentUser = FirebaseFirestore.instance
      .collection('users')
      .doc(FirebaseAuth.instance.currentUser?.uid)
      .snapshots()
      .map((user) => UserModel.fromJson(user.data() as Map<String, dynamic>));
    ...
  }

  Future signIn({required String email, required String password}) async {
    ...
    this._currentUser = FirebaseFirestore.instance
      .collection('users')
      .doc(userCredential.user?.uid)
      .snapshots()
      .map((user) => UserModel.fromJson(user.data() as Map<String, dynamic>));
    ...
  }


  Future signOut() async {
    ...
    this._currentUser = null;
    ...
  }
}

Solution

  • You don't actually want your Stream to be null but rather have it emit a null value. The AuthenticationProvider could have a StreamController with which you add new values of the current user object onto the streams.

    class AuthenticationProvider {
      final StreamController _controller = SteamController<UserModel?>();
      Stream<UserModel?> get userStream => _controller.stream;
    
      Future<void> initialize() async {
        final user = // get usermodel from firebase
        _controller.add(user);
      }
    
      Future<void> signIn({required String email, required String password}) async {
        ...
        final user = // get usermodel from firebase
        _controller.add(user);
      }
    
      void signOut() {
        _controller.add(null);
      }
    }
    

    Don't forget to close the StreamController on dispose:

    // in main
    Provider<AuthenticationProvider>(
      create: (_) => authenticationProvider,
      dispose: (provider) => provider.dispose(),
    ),
    
    // in AuthenticationProvider
    void dispose() {
      _controller.close();
    }