How do i use StateNotifier riverpod to track the changes of enum value - flutter

I'm trying to use Riverpod stateNotifier to track the changes of an enum during user authentication to determine the appropriate screen to be displayed. Eg SignUp, SignIn, Homepage or the Authenticating screen but I get this error back in my named constructor:
The superclass 'StateNotifier' doesn't have a zero argument constructor.
Try declaring a zero argument constructor in 'StateNotifier', or explicitly invoking a different constructor in 'StateNotifier'.
I know that there something i don't understand here but i can't figure it out.
Here is my code:
enum Status {
unInitialized,
unauthenticated,
authenticating,
authenticated,
processing
}
class AuthWithEmailPassword extends StateNotifier<Status> {
AuthWithEmailPassword() : super(Status.authenticated);
Status _status = Status.authenticated;
// AuthWithEmailPassword();
UserServices _userServices = UserServices();
FirebaseAuth _auth;
UserModel _userModel;
User _user;
Status get status => _status;
User get user => _user;
UserModel get userModel => _userModel;
//Name consturctor of this class
#override
AuthWithEmailPassword.initialize()
: _auth = FirebaseAuth.instance{
_status = Status.unInitialized;
_auth.authStateChanges().listen((User value) async {
_status = Status.unInitialized;
if (value == null) {
_status = Status.unauthenticated;
print('user is signed out');
} else {
_userModel = await _userServices.getUserByUid(id: value.uid);
_status = Status.authenticated;
_user = value;
print('user signed in');
}
});
}}

Instead of using a named constructor, you could create an initialize function and call it in your StateNotifierProvider.
For example:
class AuthWithEmailPassword extends StateNotifier<Status> {
AuthWithEmailPassword() : super(Status.authenticated);
Status _status = Status.authenticated;
Status get status => _status;
final FirebaseAuth _auth = FirebaseAuth.instance;
User _user;
User get user => _user;
UserModel _userModel;
UserModel get userModel => _userModel;
bool _init = false;
late StreamSubscription _sub;
void initialize() {
if (_init) return;
_status = Status.unInitialized;
_sub = _auth.authStateChanges().listen(_listener);
_init = true;
}
#override
void dispose() {
_sub.cancel();
super.dispose();
}
Future<void> _listener(User? value) async {
_status = Status.unInitialized;
if (value == null) {
_status = Status.unauthenticated;
print('user is signed out');
} else {
_userModel = await _userServices.getUserByUid(id: value.uid);
_status = Status.authenticated;
_user = value;
print('user signed in');
}
}
}
final authWithEmailPasswordProvider = StateNotifierProvider.autoDispose<AuthWithEmailPassword, Status>((_) {
return AuthWithEmailPassword()..initialize();
});

Thanks to Alex Hartford who helped me with a solution when i was desperate on this issue. I have finally been able to figure out another solution also and maybe someone might like it.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_ecom/controle/userServices.dart';
import 'package:flutter_ecom/models/userModel.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
enum Status {
unInitialized,
unauthenticated,
authenticating,
authenticated,
}
final authStatus = StateNotifierProvider<AuthWithEmailPassword, Status>(
(ref) => AuthWithEmailPassword.initialize());
class AuthWithEmailPassword extends StateNotifier<Status> {
FirebaseFirestore firestore = FirebaseFirestore.instance;
UserServices _userServices = UserServices();
// Status state = Status.unInitialized;
FirebaseAuth _auth;
UserModel _userModel;
User _user;
// Status get status => _status;
User get user => _user;
UserModel get userModel => _userModel;
String _error;
String get error => _error;
//Name consturctor of this class
AuthWithEmailPassword.initialize()
: _auth = FirebaseAuth.instance,
super(Status.unInitialized) {
_auth.authStateChanges().listen((User value) async {
await Future.delayed(
const Duration(milliseconds: 4000),
);
if (value == null) {
state = Status.unauthenticated;
print('user is signed out');
} else {
_userModel = await _userServices.getUserByUid(id: value.uid);
state = Status.authenticated;
_user = value;
print('user signed in');
}
});
}

Related

I need your help for an error I encounter in flutter dart

I have an application and I created a legin and logout page... and when I click on my application's logout button, I get an error like this " Null check operator used on a null value"*and when I point to the error, it tells me [1] :
https://i.stack.imgur.com/n0uJ8.pngentrez
import 'dart:async';
import 'dart:convert';
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:logger/logger.dart';
import '../db/db_auth_shared_preference.dart';
import '../network/app_urls.dart';
import '../models/auth.dart';
enum Status {
notLoggedIn,
loggedIn,
authenticating,
loggedOut,
notReet,
reseted,
resetting
}
//Help display the logs
var logger = Logger();
class AuthProvider with ChangeNotifier {
Auth? _auth;
Auth get auth => _auth!;
void setAuth(Auth auth) {
_auth = auth;
notifyListeners();
}
bool isAuth() {
if (_auth == null || auth.token == '') {
return false;
}
return true;
}
// Time before the token expires
Timer? _authTimer;
DateTime? _expiryDate;
String? username;
String? password;
// Set the status of the user to Not Logged In at the start of the app
Status _status = Status.notLoggedIn;
Status get status => _status;
// Change the status of the user
set status(Status value) {
_status = value;
notifyListeners();
}
// Log In the user
Future<Map<String, dynamic>> login(String email, String password) async {
Map<String, Object> results;
final Map<String, dynamic> loginData = {
'email': email,
'password': password
};
status = Status.authenticating;
logger.d("--- Authentication ---");
try {
Response response = await post(
Uri.parse(
"${AppUrl.login}? username=${loginData['email']}&password=${loginData['password']}"
),
);
logger.d('Login response : ${response.statusCode}');
// The Request Succeded
if (response.statusCode == 200) {
final Map<String, dynamic> responseData =
json.decode(utf8.decode(response.bodyBytes));
var requestStatus = responseData["status"];
if (requestStatus != 0) {
status = Status.notLoggedIn;
results = {'status': false, 'message': "La Connexion a échoué"};
} else {
// Get the status code of the request
Map<String, dynamic> authData = responseData["utilisateurJson"];
logger.d(authData);
_expiryDate = DateTime.now().add(const Duration(seconds: 3500));
//store user shared pref
Auth authUser = Auth.fromMap(authData,
timeToExpire: _expiryDate,
username: loginData['email'],
password: loginData['password']);
_expiryDate = authUser.expiryDate;
logger.wtf(_expiryDate);
//clear session data
AuthPreferences().removeAuth();
//store User session
AuthPreferences().saveAuth(authUser);
setAuth(authUser);
status = Status.loggedIn;
username = loginData["email"];
password = loginData["password"];
results = {
'status': true,
'message': 'Successful login',
'auth': authUser,
};
autoLogOut();
}
} else {
status = Status.notLoggedIn;
results = {'status': false, 'message': 'La Connexion a échoué'};
}
return results;
} catch (e) {
logger.e(e);
status = Status.notLoggedIn;
results = {
'status': false,
'message': "La Connexion avec le serveur a échoué"
};
return results;
} }
void autoLogOut() {
if (_authTimer != null) {
_authTimer!.cancel();
}
final timeToExpiry = _expiryDate!.difference(DateTime.now()).inSeconds;
_authTimer = Timer(Duration(seconds: timeToExpiry),
() async => await login(username!, password!));
}
// Log Out the User
void logOut() {
logger.d("--- User Logging Out ---");
AuthPreferences().removeAuth();
status = Status.loggedOut;
_expiryDate = null;
_auth = null;
logger.d("--- User Logged Out ---");
}
Future<Auth?> tryAutoLogin() async {
final authSession = await AuthPreferences().getAuth();
if (authSession == null) {
return null;
}
logger.d("The expiry time is : ${authSession.expiryDate}");
if (authSession.expiryDate.isBefore(DateTime.now())) {
login(authSession.username, authSession.password);
return authSession;
}
_expiryDate = authSession.expiryDate;
setAuth(authSession);
logger.d("SETTING THE USER");
autoLogOut();
return authSession;
}
}
Error Explanation: Bang operator(!) means that in flutter, when you use this operator, you are completely sure that variable is not going to be null in any case.
There are two ways to resolve it -
Use if conditional to confirm that variable is not null
Use null-aware or if-null operator ?? like
Auth get auth => _auth ?? Auth();
Since you didn't provide any error logs; based on attached image and as your cursor on line no 29, _auth variable is null. So before using ! make sure your variable is not null.

displaying only the current user data

I protected data_service with current user to only display the current user's habits.
data_service.dart:
class DataService {...
late final Database db;
Users? _user;
late final StreamData<Map<int, Habit>> habits;
Future<void> init() async {
db = await HabitsDb.connectToDb();
habits = StreamData(initialValue: await _getAllHabits(), broadcast: true);
}
String get userEmail => AuthService.firebase().currentUser!.email;
Future<Map<int, Habit>> _getAllHabits() async {
getOrCreateUser(email: userEmail); //issue
final habits = await _getAllHabitsFromDb();
final map = Map<int, Habit>();
final currentUser = _user;
print(currentUser);
for (final habit in habits) {
if (currentUser != null) {
print(currentUser.id);
print(habit.userId);
if (habit.userId == currentUser.id) {
map[habit.id] = habit;
}
}
//map[habit.userId] = currentUser?.id;
}
return map;
}
Future<List<Habit>> _getAllHabitsFromDb() async {
final habitsMap = await HabitsDb.getAllHabits(db);
final habitsList = habitsMap.map((e) => Habit.fromDb(e)).toList();
return habitsList;
}
Future<Users> getOrCreateUser({
required String email,
bool setAsCurrentUser = true,
}) async {
try {
//we found the user
final user = await getUser(email: email);
if (setAsCurrentUser) {
_user = user;
}
print(_user?.email);
return user;
} on CouldNotFindUser {
//we didn't find the user
final createdUser = await createUser(email: email);
if (setAsCurrentUser) {
_user = createdUser;
}
return createdUser;
} catch (e) {
rethrow;
}
}
...}
in main class:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final dataService = DataService();
await dataService.init();
GetIt.I.registerSingleton(dataService);
... }
StreamData class:
class StreamData<T> {
List<Habit> _notes = [];
User? _user;
late final StreamController<T> _controller;
Stream<T> get stream => _controller.stream;
late T _value;
T get value => _value;
StreamData({required T initialValue, bool broadcast = true}) {
if (broadcast) {
_controller = StreamController<T>.broadcast();
} else {
_controller = StreamController<T>();
}
_value = initialValue;
}
the problem is that the line getOrCreateUser(email: userEmail); is only called once and it does not work when I switch user and I need to Hot Restart to fix it. I think using Futurebuilder will fix it. but if yes, how do I use it when there is a need to call dataService.init at the beginning of the main?
Since your getOrCreateUser function is declared as async, you'll want to use await when you call it in _getAllHabits:
await getOrCreateUser(email: userEmail)
This ensures the getOrCreateUser code has completed before the rest of the code in _getAllHabits (that depends on the result of getOrCreateUser) executes.

Flutter: LateError (LateInitializationError: Field 'user' has not been initialized.)

I am nit sure about this error because user should be inithialized in Auth Provider and then I will be able to use it in User Provider but flutter continue giving this error.
Here is my code. Can someone help to solve or tell me a better form to organize it?
AuthProvider
class AuthProvider extends ChangeNotifier {
late final FirebaseAuth _auth;
late final NavigationService _navigationService;
late final DatabaseService _databaseService;
late UserData user;
AuthProvider() {
_auth = FirebaseAuth.instance;
_navigationService = GetIt.instance.get<NavigationService>();
_databaseService = GetIt.instance<DatabaseService>();
_auth.authStateChanges().listen((_user) {
if (_user != null) {
//_databaseService.updateUserLastSeenTime(_user.uid);
_databaseService.getUser(_user.uid).then(
(_snapshot) {
if (_snapshot.exists) {
if (_snapshot.data() != null) {
user =
UserData.fromJson(jsonDecode(jsonEncode(_snapshot.data())));
notifyListeners();
}
}
_navigationService.removeAndNavigateToRoute('/home');
},
);
} else {
_navigationService.removeAndNavigateToRoute('/login');
}
});
}
User Provider
class UserProvider with ChangeNotifier {
final DatabaseService _databaseService = DatabaseService();
UserData _user = AuthProvider().user;
UserData get getUser => _user;
Future<void> refreshUser() async {
UserData user = await _databaseService.getUserDetails();
_user = user;
notifyListeners();
}
// update user name
Future<void> editName(String name) async {
try {
await _databaseService.getUserDoc(_user.uid).update({'name': name});
} catch (err) {
print(err.toString());
}
}
// update user last name
Future<void> editLastName(String lastName) async {
try {
await _databaseService
.getUserDoc(_user.uid)
.update({'lastName': lastName});
} catch (err) {
print(err.toString());
}
}
}

Why it is showing -- "The argument type 'User' can't be assigned to the parameter type 'User1' in flutter"

I get the below error when I run the code, Pls help me
error: The argument type 'User' can't be assigned to the parameter type 'User1'. (argument_type_not_assignable at [time_tracker_app] lib\services\auth.dart:34)
here is my code :
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
class User1 {
User1({#required this.uid, });
final String uid;
}
abstract class AuthBase {
Future<User1> currentUser();
Future<User1> signInAnonymously();
Future<void> signOut();
}
class Auth implements AuthBase {
final _firebaseAuth = FirebaseAuth.instance;
User1 _userFromFirebase(User1 user) {
if (user == null) {
return null;
}
return User1(uid: user.uid);
}
#override
Future<User1> currentUser() async {
final user = _firebaseAuth.currentUser;
return _userFromFirebase(User1(uid: user.uid));
}
#override
Future<User1> signInAnonymously() async {
final authResult = await _firebaseAuth.signInAnonymously();
return _userFromFirebase(authResult.user);
}
Future<void> signOut() async {
await _firebaseAuth.signOut();
}
}
The issue is in the _userFromFirebase function parameter. From the signInAnonymously function you are calling the _userFromFirebase function with Firebase User object.
#override
Future<User1> signInAnonymously() async {
final authResult = await _firebaseAuth.signInAnonymously();
return _userFromFirebase(authResult.user); // Passing User object
}
To fix the issue, you need to change the parameter type:
User1 _userFromFirebase(User user) {
if (user == null) {
return null;
}
return User1(uid: user.uid);
}

How can a method which accepts parameters be called without passing it's needed values?

I found this code snippet below on GitHub:
import 'package:flutter/widgets.dart';
import 'package:firebase_auth/firebase_auth.dart';
enum Status { Uninitialized, Authenticated, Authenticating, Unauthenticated }
class UserRepository with ChangeNotifier {
final FirebaseAuth auth;
FirebaseUser _user;
Status _status = Status.Uninitialized;
UserRepository.instance({this.auth}) {
auth.onAuthStateChanged.listen(onAuthStateChanged);
}
Status get status => _status;
FirebaseUser get user => _user;
Future<bool> signIn(String email, String password) async {
try {
_status = Status.Authenticating;
notifyListeners();
await auth.signInWithEmailAndPassword(email: email, password: password);
return true;
} catch (e) {
_status = Status.Unauthenticated;
notifyListeners();
return false;
}
}
Future signOut() async {
auth.signOut();
_status = Status.Unauthenticated;
notifyListeners();
return Future.delayed(Duration.zero);
}
Future<void> onAuthStateChanged(FirebaseUser firebaseUser) async {
if (firebaseUser == null) {
_status = Status.Unauthenticated;
} else {
_user = firebaseUser;
_status = Status.Authenticated;
}
notifyListeners();
}
}
At the top where UserRepository was instantiated,
UserRepository.instance({this.auth}) {
auth.onAuthStateChanged.listen(onAuthStateChanged);
}
on auth.onAuthStateChanged.listen he passes the onAuthStatechanged method. This method as you can see below the code snippet takes in parameter FirebaseUser firebaseUser but this is never passed when called.
My question is, how can this work then if it receives no value when called?
Full disclosure: This code isn't mine, it was/is available on GitHub. I only posted it here for whoever has an answer to my question to fully understand.
"auth.onAuthStateChanged.listen" itself is a function which takes
Future<void> Function(FirebaseUser) as an argument. Function eating function thats all.
OG author could've passed a unnamed function right there like this
auth.onAuthStateChanged.listen((FirebaseUser firebaseUser){});
but that would be less readable