Reputation: 3499
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
Upvotes: 1
Views: 614
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