anonymous-dev
anonymous-dev

Reputation: 3499

How can I have a stream provider which needs a user id?

I have a multiprovider that has an authentication provider and a user providerm and is wrapped around the main app. The authentication provider has an async method that returns a Future<AuthenticationCertificate>. The user provider has a stream that needs that authentication certificate. So how can I get the certificate into my user provider?

This is my multiprovider which is wrapped around the main app

return MultiProvider(
  providers: [
    ChangeNotifierProvider<PreferencesProvider>(
        create: (_) => PreferencesProvider()),
    Provider<AuthenticationProvider>(create: (_) => AuthenticationProvider(),),
    Provider<GroupProvider>(create: (_) => GroupProvider()),
    Provider<UserProvider>(
      create: (_) => UserProvider(),
    ),
    StreamProvider(create: (context) {
      return Provider.of<UserProvider>(context).authenticatedUserStream(Provider.of<AuthenticationProvider>(context).userAuthenticationCertificate());
    }),
  ],

This is my authentication provider

class AuthenticationProvider {
  final FirebaseAuth _authentication = FirebaseAuth.instance;
  String _smsCode;
  String _verificationID;

  Future<void> registerWithPhone(
      {String phoneNumber = '',
      BuildContext context,
      Function(AuthCredential, BuildContext) onLogin,
      Function(AuthException, BuildContext) onLoginFailed}) async {
    String localizedPhoneNumber = phoneNumber.toString();

    return await _authentication.verifyPhoneNumber(
        phoneNumber: localizedPhoneNumber,
        timeout: Duration(seconds: 30),
        verificationCompleted: (authCredential) =>
            _verificationComplete(authCredential, context, onLogin),
        verificationFailed: (authException) =>
            _verificationFailed(authException, context, onLoginFailed),
        codeAutoRetrievalTimeout: (verificationId) =>
            _codeAutoRetrievalTimeout(verificationId),
        codeSent: (verificationId, [code]) =>
            _smsCodeSent(verificationId, [code]));
  }

  _verificationComplete(AuthCredential authCredential, BuildContext context,
      Function(AuthCredential, BuildContext) onLogin) {
    FirebaseAuth.instance
        .signInWithCredential(authCredential)
        .then((authResult) {
      onLogin(authCredential, context);
    });
  }

  _verificationFailed(AuthException exception, BuildContext context,
      Function(AuthException, BuildContext) onLoginFailed) {
    onLoginFailed(exception, context);
  }

  _smsCodeSent(String verificationId, List<int> code) {
    _smsCode = verificationId;
  }

  _codeAutoRetrievalTimeout(String verificationId) {
    _smsCode = verificationId;
  }

  Future<bool> isSignedIn() async {
    return await _authentication.currentUser() != null;
  }

  Future<UserAuthenticationCertificate> userAuthenticationCertificate() async
  {
    FirebaseUser authenticatedUser = await _authentication.currentUser();
    if(authenticatedUser != null)
      return UserAuthenticationCertificate.fromFirebase(authenticatedUser);
    return null;
  }

  Future deleteAuthenticatedUser() async
  {
    FirebaseUser authenticatedUser = await _authentication.currentUser();
    if(authenticatedUser != null)
      return authenticatedUser.delete();
    return null;
  }
}

And this is my user provider

class UserProvider {
  static const userCollectionKey = 'users';
  final CollectionReference userCollection =
      Firestore.instance.collection(userCollectionKey);

  Stream<UserModel> authenticatedUserStream(UserAuthenticationCertificate certificate) {
    Stream<DocumentSnapshot> userData = userCollection.document(certificate.userID).snapshots();
    Stream<DocumentSnapshot> privateUserData = userCollection.document(certificate.userID).collection('private').document('data').snapshots();
    Stream<DocumentSnapshot> publicUserData = userCollection.document(certificate.userID).collection('public').document('data').snapshots();
    return CombineLatestStream([userData, privateUserData, publicUserData], (values) => values.toList()).asBroadcastStream().map((snapshot) => UserModel.fromFirebase(snapshot));
  }
}

And this is the authentication certificate

class UserAuthenticationCertificate
{
  String _userID;

  String get userID{
    return _userID;
  }

  UserAuthenticationCertificate._internal(this._userID);

  factory UserAuthenticationCertificate.fromFirebase(FirebaseUser firebaseUser)
  {
    return UserAuthenticationCertificate._internal(
      firebaseUser.uid
    );
  }
}

The problem is when I want to define my stream provider

StreamProvider(create: (context) {
  return Provider.of<UserProvider>(context).authenticatedUserStream(Provider.of<AuthenticationProvider>(context).userAuthenticationCertificate());
}),

There is an error because the userAuthenticationCertificate() is async. And also could return null if the user is not logged in. How can I deal with his problem?

Edit

This is what I currently have which does not throw a intelisense error

  Stream<UserModel> authenticatedUserStream(Future<UserAuthenticationCertificate> certificateFuture) async* {
    UserAuthenticationCertificate certificate = await certificateFuture;
    Stream<DocumentSnapshot> userData = userCollection.document(certificate.userID).snapshots();
    Stream<DocumentSnapshot> privateUserData = userCollection.document(certificate.userID).collection('private').document('data').snapshots();
    Stream<DocumentSnapshot> publicUserData = userCollection.document(certificate.userID).collection('public').document('data').snapshots();
    yield* CombineLatestStream([userData, privateUserData, publicUserData], (values) => values.toList()).asBroadcastStream().map((snapshot) => UserModel.fromFirebase(snapshot));
  }

But when I try to use the provider in a child widget

class _ProfileListState extends State<ProfileList> {
  @override
  Widget build(BuildContext context) {
    final userModel = Provider.of<UserModel>(context);
    return MediaQuery.removePadding(
        context: context,
        removeTop: true,
        removeLeft: true,
        removeRight: true,
        child: ListView.separated(
            itemCount: 3,
            physics: NeverScrollableScrollPhysics(),
            separatorBuilder: (BuildContext context, int index) =>
                Divider(height: 1.0),
            itemBuilder: (BuildContext context, int index) {
              return [
                ListTile(
                  title: Text(
                    'Name',
                    style: Theme.of(context).textTheme.body1,
                  ),
                  trailing: Text(
                    userModel.name,
                    style: Theme.of(context).textTheme.subtitle,
                  ),
                ),
                ListTile(
                  title: Text(
                    'Phone Number',
                    style: Theme.of(context).textTheme.body1,
                  ),
                  trailing: Text(
                    userModel.phoneNumber,
                    style: Theme.of(context).textTheme.subtitle,
                  ),
                ),
                ListTile(
                  title: Text(
                    'Delete',
                    style: Theme.of(context).textTheme.body1,
                  ),
                  trailing: Icon(
                    Icons.delete,
                    color: Theme.of(context).primaryColor,
                  ),
                  onTap: () => print('delete'),
                ),
              ][index];
            }));
  }
}

The line final userModel = Provider.of<UserModel>(context); throws the following error

enter image description here

Upvotes: 1

Views: 614

Answers (1)

Lo&#239;c Fonkam
Lo&#239;c Fonkam

Reputation: 2340

Try removing the StreamProvider from the multi-provider list and wrap it around the class that needs it at creation.

class ProfileList extends StatefulWidget {
  static Widget create(BuildContext context) {
    final cert = Provider.of<AuthenticationProvider>(context)
        .userAuthenticationCertificate();

    return StreamProvider(
      create: (context) {
        return Provider.of<UserProvider>(context).authenticatedUserStream(cert);
      },
      child: ProfileList(),
    );
  }

  @override
  _ProfileListState createState() => _ProfileListState();
}

class _ProfileListState extends State<ProfileList> {
  @override
  Widget build(BuildContext context) {
    ...
  }
}

Then you can call the ProfileList like ProfileList.create(context) in the place of ProfileList()

Upvotes: 1

Related Questions