State managment in flutter with consumer and scaffoldState - flutter

I'm using the Provider dependencie to manage states on my screen. Currently I have created a Loading Screen that works with Lottie animation. In my Sign In page, whenever there is an error with the log in, a Snackbar is shown to the user. Althought now, when I use the splash screen, the screen doesn't return and the snackBar isn't shown.
This is a piece of the login screen:
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: RaisedButton(
onPressed: userManager.loading
? null
: () {
if (formKey.currentState!
.validate()) {
userManager.signIn(
user: User(
email:
emailController.text,
password:
passController.text),
onFail: (e) {
scaffoldKey.currentState!
.showSnackBar(SnackBar(
content: Text(
'Falha ao entrar: $e'),
backgroundColor:
Colors.red,
));
},
onSucess: () {
debugPrint(
'Sucesso ao Logar');
Navigator.of(context).pop();
});
}
},
On the onFail I get this error, whenever I have a wrong password or other datas wrong:
Ocorreu uma exceção.
_CastError (Null check operator used on a null value)
This is how I'm changing between pages:
class LoginScreen extends StatelessWidget {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
final TextEditingController emailController = TextEditingController();
final TextEditingController passController = TextEditingController();
#override
Widget build(BuildContext context) {
return Consumer<UserManager>(builder: (_, userManager, child) {
if (userManager.loading) {
return SplashScreen();
} else {
return Scaffold(
key: scaffoldKey,
appBar: AppBar(
UserManager:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:loja_virtual_nnananene/helpers/firebase_errors.dart';
import 'package:loja_virtual_nnananene/models/user.dart';
class UserManager extends ChangeNotifier {
UserManager() {
_loadCurrentUser();
}
final FirebaseAuth auth = FirebaseAuth.instance;
User? user;
bool _loading = false;
bool get loading => _loading;
bool get isLoggedIn => user != null;
Future<void> signIn(
{required User user,
required Function onFail,
required Function onSucess}) async {
loading = true;
try {
final AuthResult result = await auth.signInWithEmailAndPassword(
email: user.email!, password: user.password!);
await _loadCurrentUser(firebaseUser: result.user);
onSucess();
} on PlatformException catch (e) {
onFail(getErrorString(e.code));
}
loading = false;
notifyListeners();
}
Future<void> signUp(
{required User user,
required Function onFail,
required Function onSucess}) async {
loading = true;
try {
final AuthResult result = await auth.createUserWithEmailAndPassword(
email: user.email!, password: user.password!);
user.id = result.user.uid;
this.user = user;
await user.saveData();
onSucess();
} on PlatformException catch (e) {
onFail(getErrorString(e.code));
}
loading = false;
notifyListeners();
}
void signOut() {
auth.signOut();
user = null;
notifyListeners();
}
set loading(bool value) {
_loading = value;
notifyListeners();
}
Future<void> _loadCurrentUser({FirebaseUser? firebaseUser}) async {
final FirebaseUser currentUser = firebaseUser ?? await auth.currentUser();
if (currentUser != null) {
final DocumentSnapshot docUser = await Firestore.instance
.collection('users')
.document(currentUser.uid)
.get();
user = User.fromDocument(docUser);
final docAdmin = await Firestore.instance
.collection('admins')
.document(user!.id!)
.get();
if (docAdmin.exists) {
user!.admin = true;
}
notifyListeners();
}
}
bool get adminEnabled => user != null && user!.admin;
}
Is there another way to set the splash screen thats easier?

While I wait for you to add the UserManager class implementation, I think there's a missing notifyListeners() in the signIn method.

Related

Flutter getting value from provider show null

I have a simple controller like this
class UserController with ChangeNotifier {
UserData user = UserData();
UserData get userdata => user;
void setUser(UserData user) {
user = user;
print(user.sId);
notifyListeners();
}
login(data) async {
var response = await ApiService().login(data);
final databody = json.decode(response);
if (databody['success']) {
UserData authUser = UserData.fromJson(databody['data']);
setUser(authUser);
notifyListeners();
return true;
} else {
return false;
}
}
}
I am trying to just print it like this on both widget and in initstate function but values are showing null. I can see in set function value is not null.
print('id ${context.watch<UserController>().user.sId.toString()}');
print(
'id2 ${Provider.of<UserController>(context, listen: false).user.sId.toString()}');
I already have added
ChangeNotifierProvider(create: (_) => UserController()),
],
in main.dart in MultiProvider
Also on Tap of login button I am doing this
showLoader(context);
UserController auth = Provider.of<UserController>(
context,
listen: false);
var data = {
"userEmail":
emailController.text.trim().toLowerCase(),
"userPassword": passwordController.text.trim(),
};
auth.login(data).then((v) {
if (v) {
hideLoader(context);
context.go('/homeroot');
} else {
hideLoader(context);
Fluttertoast.showToast(
backgroundColor: green,
textColor: Colors.white,
msg:
'Please enter correct email and password');
}
});
Try to include this while naming is same,
void setUser(UserData user) {
this.user = user;
print(user.sId);
notifyListeners();
}
Follow this structure
class UserController with ChangeNotifier {
UserData user = UserData();
UserData get userdata => user;
void setUser(UserData user) {
this.user = user;
print(user.sId);
notifyListeners();
}
Future<bool> login(String data) async {
await Future.delayed(Duration(seconds: 1));
UserData authUser = UserData(sId: data);
setUser(authUser);
notifyListeners();
return true;
}
}
class HPTest extends StatelessWidget {
const HPTest({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer<UserController>(
builder: (context, value, child) {
return Text(value.user.sId);
},
),
floatingActionButton: FloatingActionButton(onPressed: () async {
final result = await Provider.of<UserController>(context, listen: false)
.login("new ID");
print("login $result");
;
}),
);
}
}

How to use sharedpreferences to save users roles and navigate to a specific page depending on role in Flutter

I'm working on app that have user logins (Admin login and user login). First i make a user part and it works, the account keep logged even when the app restart. and then when i have to separate the users (admin and user) i got some problem. I don't know how to code the shared preferences, this is the code when i make a user part
preference_helper.dart
import 'package:shared_preferences/shared_preferences.dart';
class PreferencesHelper {
final Future<SharedPreferences> sharedPreferences;
const PreferencesHelper({required this.sharedPreferences});
static const String login = 'LOGIN';
void setIsLogin(bool value) async {
final prefs = await sharedPreferences;
prefs.setBool(login, value);
}
Future<bool> get isLogin async {
final prefs = await sharedPreferences;
return prefs.getBool(login) ?? false;
}
}
i use the provider like this
preference_notifier.dart
class PreferencesNotifier extends ChangeNotifier {
PreferencesHelper preferencesHelper;
PreferencesNotifier({required this.preferencesHelper}) {
_getIsLogin();
}
bool _isLogin = false;
bool get isLogin => _isLogin;
void _getIsLogin() async {
_isLogin = await preferencesHelper.isLogin;
notifyListeners();
debugPrint(_isLogin ? 'isLogin true' : 'isLogin false');
}
void setIsLogin(bool value) async {
preferencesHelper.setIsLogin(value);
_getIsLogin();
}
}
i want to use shared preferences to save the user roles and navigate to specific page. So if the user's log in it will go to the UserHomePage and if the admin log in it will go to the AdminHomePage. My backend is firebase firestore.
this is part of sign page (when click register button)
MaterialButton(
color: primaryColor,
textTheme: ButtonTextTheme.primary,
height: 40,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
onPressed: () async {
setState(() {
_isLoading = true;
});
try {
final navigator = Navigator.of(context);
final email = _emailController.text;
final password = _passwordController.text;
const role = "user";
await _auth
.createUserWithEmailAndPassword(
email: email,
password: password,
)
.then((value) => {postDetailsToFirestore(email, role)});
navigator.pop();
} catch (err) {
final snackBar = SnackBar(content: Text(err.toString()));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} finally {
setState(() {
_isLoading = false;
});
}
},
child: const Text('Signup'),
),
postDetailsToFirestore(String email, String role) async {
FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;
var user = _auth.currentUser;
CollectionReference ref = firebaseFirestore.collection('users');
ref.doc(user!.uid).set({'email': _emailController.text, 'role': role});
}
this is the login page (when click the login button)
MaterialButton(
color: primaryColor,
textTheme: ButtonTextTheme.primary,
height: 40,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
onPressed: () async {
setState(() {
_isLoading = true;
});
try {
final navigator = Navigator.of(context);
final email = _emailController.text;
final password = _passwordController.text;
await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
route();
value.setIsLogin(true);
navigator.pushReplacementNamed(HomePage.routeName);
} catch (err) {
final snackBar = SnackBar(content: Text(err.toString()));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} finally {
setState(() {
_isLoading = false;
});
}
},
child: const Text('Login'),
),
i want to navigate the navigator to specific user role
this is the route() function
void route() {
User? user = FirebaseAuth.instance.currentUser;
FirebaseFirestore.instance.collection('users').doc(user!.uid).get().then(
(DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
if (documentSnapshot.get('role') == "user") {
Navigator.pushNamed(context, UserHomePage.routeName);
} else {
Navigator.pushNamed(context, AdminHomePage.routeName);
}
} else {
debugPrint('Document does not exist on the database');
}
},
);
}
and this is the main.dart at runApp()
runApp(
await preferencesHelper.isLogin
? const MyApp(
pageRouteName: HomePage.routeName,
)
: const MyApp(
pageRouteName: LoginPage.routeName,
),
);
I really need to know how am i supposed to do because this is for my exam. I'm sorry if my english is bad, i'm barely use English to talk. Thank you
that code that i share is what i tried to make sharedpreferences but it just for 1 user, i dont know how to separate user (admin and user)
First of all, you need to use architecture to separate the UI from logic and in your architect, you have to create a layer to handle basic requests of the local database and then create a class for implementing basic commands of the database, then you can create a separated storage layer for each of entities that you have.
the abstract basic commands class is like this :
abstract class LocalStorage {
Future<void> write(final String key, final dynamic json);
dynamic read<S>(final String key);
void remove(final String key);
void removeAll();
}
and for implementation :
class StorageService implements LocalStorage {
StorageService() {
_init();
}
late GetStorage storage;
void _init() {
storage = GetStorage();
}
#override
Future<void> write(final String key, final dynamic value) async {
await storage.write(key, convert.jsonEncode(value));
}
#override
dynamic read<S>(final String key) {
final value = storage.read(key);
if (value == null) return;
return convert.jsonDecode(value.toString());
}
#override
void remove(final String key) {
GetStorage().remove(key);
}
#override
void removeAll() {
GetStorage.Remove(key1);
GetStorage.Remove(key2);
...
}
}
and for Usage for each entity:
class UserStorage {
final LocalStorage _storage;
Future<void> SaveUser(User usr) async {
await _storage.write(userKey, usr);
}
}
I have used GetX to handle local storage for read and write but you can replace your preferred shared preference library.

Fetch data from firestore after Sign In before showing HomeScreen

When a User sign up for the first time, i want that he gets a own firestore document with some data. This data I want to show on the homescreen but I get an error that the data is not there yet. After hot reload the data is there so the problem is that the homescreen is shown before the data is fetched from firestore although I use a FutureBuilder. I only got this problem when a user signs in for the first time.
class GoogleSignInProvider extends ChangeNotifier {
final googleSignIn = GoogleSignIn();
GoogleSignInAccount _user;
GoogleSignInAccount get user => _user;
Future googleLogin() async {
try {
final googleUser = await googleSignIn.signIn();
if (googleUser == null) return;
_user = googleUser;
final googleAuth = await googleUser.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
await FirebaseAuth.instance.signInWithCredential(credential);
await getUserData();
} catch (e) {
print(e.toString());
}
notifyListeners();
}
Future getUserData() async {
final myUser = FirebaseAuth.instance.currentUser;
bool userExist = await checkIfUserExists(myUser.uid);
if (userExist == false) {
print('User dont exist');
await FirebaseFirestore.instance.collection('users').doc(myUser.uid).set({
"email": myUser.email,
"plans": [],
"userScore": "100",
});
} else {
print('User exist');
}
/// Save userData from firestore in a Helper class which is shown on the homescreen
var userData = FirebaseFirestore.instance.collection('users').doc(myUser.uid);
return FutureBuilder(
future: userData.get(),
builder: (context, userDataSnapshot) {
if (userDataSnapshot.data == ConnectionState.done) {
var value = userDataSnapshot.data;
UserManager.userdata = value.data(); //static class where userData is stored
return null;
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
});
}
Future<bool> checkIfUserExists(String uid) async {
try {
var collectionRef = FirebaseFirestore.instance.collection('users');
var doc = await collectionRef.doc(uid).get();
return doc.exists;
} catch (e) {
throw e;
}
}
EDIT
This is the Button where the user can sign In
ElevatedButton.icon(
onPressed: () {
final provider = Provider.of<GoogleSignInProvider>(context, listen: false);
provider.googleLogin();
},
icon: Icon(MdiIcons.google),
label: Text(
'Sign In with Google',
style: TextStyle(fontSize: 16),
),
),
The problem here is that FutureBuilder is a widget, it should not be used in a function to wait for a future to complete, but in another widget to have callbacks on the completion and change display based on that.
If not rendered, FutureBuilder will do nothing but be instantiated and occupy memory.
You should probably modify your code as such:
...
/// Save userData from firestore in a Helper class which is shown on the homescreen
var userData = await FirebaseFirestore.instance.collection('users').doc(myUser.uid).get();
UserManager.userdata = userData.data();
...
Should you want to add a CircularProgress on your main screen, this would be done by lisntening to your Provider in some way.

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;
}
}

UserProfile crashes after auth.getCurrentUser()

I am making an app in flutter an now when i try to show the user Name on the profile i got this error
════════ Exception caught by widgets library ═══════════════════════════════════
The following NoSuchMethodError was thrown building UserPage(dirty, dependencies: [MediaQuery], state: _UserProfile#ca57c):
The getter 'auth' was called on null.
Receiver: null
Tried calling: auth
The relevant error-causing widget was
UserPage
lib/HomePage.dart:92
When the exception was thrown, this was the stack
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
#1 _UserProfile.build
package:tariffo/UserPage.dart:131
#2 StatefulElement.build
package:flutter/…/widgets/framework.dart:4619
#3 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4502
#4 StatefulElement.performRebuild
package:flutter/…/widgets/framework.dart:4675
...
═══════════════════════════════════════════════════════════════════════════════
and i used this code
FutureBuilder(
future:
Provider.of(context).auth.getCurrentUser(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
return Text("${snapshot.data.displayName}",
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 22,
));
} else {
return CircularProgressIndicator();
}
},
),
This is the code that i used for my auth.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'User.dart';
String email, name, photoUrl;
class Authentication {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = GoogleSignIn();
Stream<String> get onAuthStateChanged => _firebaseAuth.onAuthStateChanged.map(
(FirebaseUser user) => user?.uid,
);
// GET UID
Future<String> getCurrentUID() async {
return (await _firebaseAuth.currentUser()).uid;
}
// GET CURRENT USER
Future getCurrentUser() async {
return await _firebaseAuth.currentUser();
}
// Email & Password Sign Up
Future<String> createUserWithEmailAndPassword(
String email, String password, String name) async {
final authResult = await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
// Update the username
await updateUserName(name, authResult.user);
return authResult.user.uid;
}
Future updateUserName(String name, FirebaseUser currentUser) async {
var userUpdateInfo = UserUpdateInfo();
userUpdateInfo.displayName = name;
await currentUser.updateProfile(userUpdateInfo);
await currentUser.reload();
}
// Email & Password Sign In
Future<String> signInWithEmailAndPassword(
String email, String password) async {
return (await _firebaseAuth.signInWithEmailAndPassword(
email: email, password: password))
.user
.uid;
}
// Sign Out
signOut() {
return _firebaseAuth.signOut();
}
// Reset Password
Future sendPasswordResetEmail(String email) async {
return _firebaseAuth.sendPasswordResetEmail(email: email);
}
// Create Anonymous User
Future singInAnonymously() {
return _firebaseAuth.signInAnonymously();
}
Future convertUserWithEmail(
String email, String password, String name) async {
final currentUser = await _firebaseAuth.currentUser();
final credential =
EmailAuthProvider.getCredential(email: email, password: password);
await currentUser.linkWithCredential(credential);
await updateUserName(name, currentUser);
}
Future convertWithGoogle() async {
final currentUser = await _firebaseAuth.currentUser();
final GoogleSignInAccount account = await _googleSignIn.signIn();
final GoogleSignInAuthentication _googleAuth = await account.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
idToken: _googleAuth.idToken,
accessToken: _googleAuth.accessToken,
);
await currentUser.linkWithCredential(credential);
await updateUserName(_googleSignIn.currentUser.displayName, currentUser);
}
// GOOGLE
Future<String> signInWithGoogle() async {
final GoogleSignInAccount account = await _googleSignIn.signIn();
final GoogleSignInAuthentication _googleAuth = await account.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
idToken: _googleAuth.idToken,
accessToken: _googleAuth.accessToken,
);
return (await _firebaseAuth.signInWithCredential(credential)).user.uid;
}
// APPLE
}
class NameValidator {
static String validate(String value) {
if (value.isEmpty) {
return "Name can't be empty";
}
if (value.length < 2) {
return "Name must be at least 2 characters long";
}
if (value.length > 50) {
return "Name must be less than 50 characters long";
}
return null;
}
}
class EmailValidator {
static String validate(String value) {
if (value.isEmpty) {
return "Email can't be empty";
}
return null;
}
}
class PasswordValidator {
static String validate(String value) {
if (value.isEmpty) {
return "Password can't be empty";
}
return null;
}
}
and this is the code for User.dart
import 'package:flutter/material.dart';
class User {
final String uid;
User({this.uid});
String adress;
bool business;
Map<String, dynamic> toJson() => {
'adress': adress,
'business': business,
};
}
Now it just appear a red background when i try to acces the profile page pressing the profile icon button
provider_widget.dart
import 'package:flutter/material.dart';
import 'auth.dart';
class Provider extends InheritedWidget {
final Authentication auth;
Provider({Key key, Widget child, this.auth}) : super(key: key, child: child);
#override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
static Provider of(BuildContext context) =>
(context.inheritFromWidgetOfExactType(Provider) as Provider);
}