Flutter Multiprovider with dependent streams - flutter

I'm want to make the User document from Firestore available to different widgets in my application. I'm listening to the authstate changes and getting the user ID, however, I'm not sure how to use that within a subsequent stream in my multiprovider :
return MultiProvider(
providers: [
Provider<FirebaseAuthService>(
create: (_) => FirebaseAuthService(),
),
//I'm looking to get another provider based on the provider above^^^ i.e the user id,
],
child: getScreen(),
);
class FirebaseAuthService {
final _firebaseAuth = FirebaseAuth.instance;
User _userFromFirebase(FirebaseUser user) {
return user == null ? null : User(uid: user.uid);
}
Stream<User> get onAuthStateChanged {
return _firebaseAuth.onAuthStateChanged.map(_userFromFirebase);
}
Future<User> signInAnonymously() async {
final authResult = await _firebaseAuth.signInAnonymously();
return _userFromFirebase(authResult.user);
}
Future<void> signOut() async {
return await _firebaseAuth.signOut();
}
}

Related

Page not disposed when signed up: Flutter

I am trying to implement provider package to signUp/signIn/signOut using Firebase Auth.
My ChangeNotifier class is-
import 'package:e_shoppie/db/authentication.dart';
import 'package:e_shoppie/db/user_services.dart';
import 'package:e_shoppie/structure/constants.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
//User can only be one of these states
/**
* uninitialized: User just opened the app (just opening)
* unauthenticated: Show the login screen to the user
* authenticating: Show a circular indicator the user
* authenticated: User is looged into the app
*/
enum Status { uninitialized, authenticated, authenticating, unauthenticated }
class UserProvider with ChangeNotifier {
FirebaseAuth _auth;
Auth _userAuth = Auth();
UserServices userServices = UserServices();
User? _user;
GoogleSignIn _googleSignIn = GoogleSignIn();
Status _status =
Status.uninitialized; //when the instance of the class is created
UserProvider.initialize() : _auth = FirebaseAuth.instance {
//subscribing to stream to listen to changes in user status
_auth.authStateChanges().listen(
(user) {
_onStatusChanged(user);
},
);
}
Status get status => _status;
User? get user => _user;
Future<bool> signUp(String username, String email, String password) async {
try {
//change the status of the user
_status = Status.authenticating;
//notify the listeners
notifyListeners();
// UserCredential credential =
await _auth.createUserWithEmailAndPassword(
email: email, password: password);
Map<String, dynamic> values = {
'name': username,
'email': email,
'id': user!.uid,
};
userServices.createUser(values);
_status = Status.authenticated;
notifyListeners();
return true;
} catch (e) {
_status = Status.unauthenticated;
notifyListeners();
print(e.toString());
return false;
}
}
Future signOut() async {
print('entered signOut');
await _auth.signOut();
_status = Status.unauthenticated;
notifyListeners();
print('Exiting signOut');
return Future.delayed(Duration
.zero); //duration to return is set to zero (can work without it)
}
Future<void> _onStatusChanged(User? user) async {
if (user == null) {
_status = Status.unauthenticated;
} else {
_user = user;
_status = Status.authenticated;
}
notifyListeners();
}
}
The way I am navigating on state change is-
class ScreenController extends StatelessWidget {
const ScreenController({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final user = Provider.of<UserProvider>(context);
switch (user.status) {
case Status.uninitialized:
return SplashScreen();
case Status.unauthenticated:
return LoginScreen();
case Status.authenticating:
return LoadingScreen();
case Status.authenticated:
return HomePage();
default:
return LoginScreen();
}
}
}
Problem: in my SignUp page, I call the signUp method of the UserProvider class to signUp the user.
I expect the signup page gets destroyed and home page appears when user is created and sign up procedure is complete.
What I get: Home Page is built but the sign up page is not destroyed and remains on the screen unless I press the back button.
Sign Up button -
// minWidth: MediaQuery.of(context).size.width.,
child: Text(
'Sign Up and Register',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
onPressed: () async {
if (!await provider.signUp(
_nameTextController.text,
_emailTextController.text,
_passwordTextController.text)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Sign In Failed')));
}
// Navigator.pop(context);
// Navigator.pushReplacement(
// context,
// MaterialPageRoute(
// builder: (context) => HomePage()));
},
),
Also- my Sign Up class is wrapped with Consumer.
I am using the provider package for navigation. The problem I am facing is: that Debug mode shows that the login page is disposed of when the user logs in and Home Page appears. But when I sign in from the Sign Up page, the page is not disposed and Home Page is constructed below it.
Please help!!
You have to use Navigator.popAndPushNamed(context) or Navigator.pushReplacementNamed(context). This is work perfect in your scenario both have the same output difference is only animations. For signUp/signIn/signOut this is ideal way.

Receiving data as null in provider

This is My Repository
class DB {
final db = FirebaseFirestore.instance;
Stream<QuerySnapshot> init(UserModel user) {
return db
.collection('CollectionName')
.doc(user.email) //this is a unique value which i want to retrieve the value from main after successful login
.collection('New Collection')
.snapshots();
}
void readData(String id, UserModel user) async {
DocumentSnapshot snapshot = await db
.collection('Collection Name')
.doc(user.email)
.collection('New Collection')
.doc(id)
.get();
// ignore: avoid_print
print(snapshot['name']);
}
}
DB db = DB();
This is My BlocFile
class IncidentBloc implements BlocBase {
IncidentBloc(UserModel user) {
db.init(user).listen((data) => _inFirestore.add(data));
}
final _idController = BehaviorSubject<String>();
Stream<String> get outId => _idController.stream;
Sink<String> get _inId => _idController.sink;
final _firestoreController = BehaviorSubject<QuerySnapshot>();
Stream<QuerySnapshot> get outFirestore => _firestoreController.stream;
Sink<QuerySnapshot> get _inFirestore => _firestoreController.sink;
void readData(UserModel user) async {
db.readData(id, user);
}
#override
void dispose() {
_firestoreController.close();
_idController.close();
}
}
And This is my main
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
BlocOverrides.runZoned(
() => runApp(
BlocProviderr(bloc: IncidentBloc(UserModel()), child: const App())
),
blocObserver: AppBlocObserver(),
);
}
It seems that the UserModel is null or empty how do i pass value to my IncidentBloc? And this is after a successful login/authentication. If I do it like this in main: "IncidentBloc(UserModel(email: 'abcde.t#gmail.com'))" It is working, but i want it to dynamically retrieve data based on the user's email not the hardcoded 'abcde.t#gmail.com'
Based on your code, you will need to get the user's email from Firebase and pass it into Incident Bloc. This StackOverflow answer explains how to do that; so does this one.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
<FirebaseUser> user = await _auth.currentUser();
final mailID = user.email;
BlocOverrides.runZoned(
() => runApp(
BlocProviderr(bloc: IncidentBloc(UserModel(email: mailID)), child: const App())
),
blocObserver: AppBlocObserver(),
);
}

dart - How can I resolve 'The argument type 'Null' can't be assigned to the parameter type 'MyUser'.'

I am trying to implement a simple signup page. The following is my main.dart code:
main.dart
void main() async {
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamProvider<MyUser>.value(
value: AuthService().user,
initialData: null,
child: MaterialApp(
home: Wrapper(),
debugShowCheckedModeBanner: false,
)
);
}
}
I am seeing this error on the initialData: null, line:
The argument type 'Null' can't be assigned to the parameter type 'MyUser'.
This is my MyUser class:
user.dart
class MyUser {
final String uid;
MyUser({this.uid});
}
It is showing me the following error on uid in line 3:
The parameter 'uid' can't have a value of 'null' because of its type, but the implicit default value is 'null'.
Try adding either an explicit non-'null' default value or the 'required' modifier.
I am fairly new to flutter development so I'm not sure what this means and how to resolve it. I wasn't able to find any relevant help online. Any help will be appreciated.
EDIT 1:
The entire auth.dart file:
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// create user obj based on firebase user
MyUser? _userFromFirebaseUser(User user) {
// ignore: unnecessary_null_comparison
return user != null ? MyUser(uid: user.uid) : null;
}
// auth change user stream
Stream<MyUser> get user {
return _auth.authStateChanges()
//.map((FirebaseUser user) => _userFromFirebaseUser(user));
.map(_userFromFirebaseUser);
}
// sign in with email and password
Future signInWithEmailAndPassword(String email, String password) async {
try {
UserCredential result = await _auth.signInWithEmailAndPassword(email: email, password: password);
User? user = result.user;
return _userFromFirebaseUser(user!);
} catch (error) {
print(error.toString());
return null;
}
}
// register with email and password
Future registerWithEmailAndPassword(String email, String password) async {
try {
UserCredential result = await _auth.createUserWithEmailAndPassword(email: email, password: password);
User? user = result.user;
return _userFromFirebaseUser(user!);
} catch (error) {
print(error.toString()+"oollala");
return null;
}
}
// sign out
Future signOut() async {
try {
return await _auth.signOut();
} catch (error) {
print(error.toString());
return null;
}
}
}
Error in:
Stream<MyUser> get user {
return _auth.authStateChanges()
.map(_userFromFirebaseUser);
}
It is showing me an error in _userFromFirebaseUser
The argument type 'MyUser? Function(User)' can't be assigned to the parameter type 'MyUser Function(User?)'.
You defined your property uid as a non-nullable String but it is declared as an optional value in your constructor because of the {} which means that it would have the default value null if not assigned.
To fix this error you either need to make uid non optional in your constructor:
MyUser(this.uid);
Or to make it a required parameter:
MyUser({required this.uid});
If it is intended that your uid can be null then you need to declare your variable like this:
final String? uid; // this is a nullable String variable
make sure you have provider 5.0.0
and then change your main.dart to
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return StreamProvider<MyUser?>.value(
initialData: null,
value: AuthService().user,
child: MaterialApp(
home: Wrapper(
),
),
);
}
}

Flutter: Snapshot is not updating its data

So I'm trying to save data into firestore, but I have a class to signup which has this code:
FirebaseAuth auth = FirebaseAuth.instance;
await auth.createUserWithEmailAndPassword(
email: emailController.text, password: passwordController.text)
.then((value) => {
Navigator.pushNamed(context, 'DialogFlow'),
user=auth.currentUser,
user.sendEmailVerification(),
DatabaseService(uid:user.uid).UpdateUserData("", emailController.text, ChatScreenState().mess)
this will navigate me to the dialogflow, which has this code:
#override
Widget build(BuildContext context) {
final firebaseUser = context.watch<User>();
FirebaseAuth auth = FirebaseAuth.instance;
user=auth.currentUser;
DatabaseService db = DatabaseService(uid: user.uid);
return StreamBuilder(
stream: db.userData,
builder: (context , snapshot){
Userdata userdata=snapshot.data;
print("====================================");
print(snapshot.data);
print("====================================");
if (userdata != null) {
this.userTestMessage = "";
shhh = pressed ? true : false;
flag = true;
if (!Retrieved_messages) {
this.messsages = userdata.messsages; //Retrieve user data from firebase only once.
// Retrieve user data from firebase only once.
Retrieved_messages = true;
}//load only 20 messages at once . When we scroll up load more.
db.UpdateUserData(
firebaseUser.displayName, firebaseUser.email, this.messsages);
print(userdata.messsages);
print(mess);
print(userdata.messsages);
print("==============================");
print(snapshot.data);
print("==============================");
}
if (db.getUserMessages() == null) {
if (firebaseUser != null) {
db.UpdateUserData(
firebaseUser.displayName, firebaseUser.email, this.messsages);
}
}
and the database class is
DatabaseService({this.uid, this.messsages});
final CollectionReference userCollection = FirebaseFirestore.instance
.collection('users');
UpdateUserData(String Username, String Email,
List messsages) async
{ try {
return await FirebaseFirestore.instance.collection("users").doc(uid).set({
'Username': Username,
'Email': Email,
'messsages': messsages,
}
);
}catch(e){
print(e+" this is the error");
}
}
Future getUserMessages() async
{
FirebaseFirestore.instance.collection(uid).snapshots();
}
Userdata _userDataFromSnapshot(DocumentSnapshot snapshot) {
return Userdata(uid: uid,
name: snapshot.data()['Username'],
email: snapshot.data()['Email'],
messsages: snapshot.data()['messsages']
);
}
Stream<Userdata> get userData {
return userCollection.doc(uid).snapshots().map(_userDataFromSnapshot);
}
}
The problem I'm facing is that whenever I create a new user, and try to save new messages, the firestore keeps on saving and removing the messages, so its in an infinite loop, so I hope that someone can tell me how I can fix it that it saves the messages without removing them.
Note: the snapshot data isn't updating.
I believe the reason you have your build function rebuilt is usage of context.watch in the beginning of your build(). As in bloc documentation:
Using context.watch at the root of the build method will result in the entire widget being rebuilt when the bloc state changes. If the entire widget does not need to be rebuilt, either use BlocBuilder to wrap the parts that should rebuild, use a Builder with context.watch to scope the rebuilds, or decompose the widget into smaller widgets.

Creating StreamBuilder to keep track whenever there is a change on Sensor value with Realtime Firebase

I'm working on a project to keep track of temperature and humidity sensor whenever they change. I will be working with a ESP32 to send the data to the Firebase, and my App to keep monitoring the values. So if the value goes from 23 to 24 I would like to immediately show the user on my app that change.
I will use a StreamBuilder to keep track of theses changes, But I'm having problems using the Stream.
This is how I the code I'm using to gather the specific user sensor info. This code is at a separate dart file, called auth.dart
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
final databaseReference = FirebaseDatabase.instance.reference();
//Cria um objeto baseado no Usuario da FirebaseUser
User _userFromFirebaseUser (FirebaseUser user){
return user != null ? User(uid: user.uid) : null;
}
// Devolve o UID da pessoa
Future<String> personuid() async{
final FirebaseUser user = await _auth.currentUser();
return user.uid;
}
// Função para ler o valor da temperatura
Future<int> getSensorTemperature() async {
final FirebaseUser user = await _auth.currentUser();
int result = (await databaseReference.child(user.uid+"/temperature").once()).value;
print(result);
return result;
}
// Função para ler o valor da humidade
Future<int> getSensorHumidity() async {
final FirebaseUser user = await _auth.currentUser();
int result = (await databaseReference.child(user.uid+"/humidity").once()).value;
print(result);
return result;
}
In my home page I attempted to use the StreamBuilder like this:
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
final AuthService _auth = AuthService();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main page'),
actions: <Widget>[
FlatButton.icon(onPressed: () async {
await _auth.signOut();
},
icon: Icon(Icons.logout),
label: Text('Logout'))
],
),
body: StreamBuilder(
stream: _auth.getSensorTemperature(), <-- I get an error here
builder: (context, snapshot) {
if (snapshot.hasError){
return Container(color: Colors.red,);
}
if (!snapshot.hasData){
return Center(child: CircularProgressIndicator(),);
}
if (snapshot.hasData){
return Container(
color: Colors.blue,
);
}
},
),
);
}
}
The error I get from the StreamBuilder is:
The argument type 'Futureint' can't be assigned to the parameter type 'Streamdynamic'
You're not actually using streams. You were only taking a single event previously. Use the streams that the package make available and then use an await for to handle it.
Stream<int> getSensorTemperature() async* {
final FirebaseUser user = await _auth.currentUser();
await for(var event in databaseReference.child(user.uid+"/temperature").onValue) {
yield event.snapshot.value;
}
}
With error handling:
Stream<int> getSensorTemperature() async* {
final FirebaseUser user = await _auth.currentUser();
Stream stream = databaseReference.child(user.uid+"/temperature").onValue.handleError((error) {
print("Error: $error");
});
await for(var event in stream) {
yield event.snapshot.value;
}
}