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

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

Related

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 do i use StateNotifier riverpod to track the changes of enum value

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

Firebase documentation for flutter does not work for deleting user

The following documentation on deleting a user does not work:
try {
await FirebaseAuth.instance.currentUser.delete();
} catch on FirebaseAuthException (e) {
if (e.code == 'requires-recent-login') {
print('The user must reauthenticate before this operation can be executed.');
}
}
"delete()" is not a function recognized by Flutter. "FirebaseAuthException" is also not recognized by Flutter.
How do I delete a user? Where do I find this information?
Using flutter, if you want to delete firebase accounts together with the associated firestore user collection document, the following method works fine. (documents in user collection named by the firebase uid).
Database Class
class DatabaseService {
final String uid;
DatabaseService({this.uid});
final CollectionReference userCollection =
Firestore.instance.collection('users');
Future deleteuser() {
return userCollection.document(uid).delete();
}
}
Use Firebase version 0.15.0 or above otherwise, Firebase reauthenticateWithCredential() method throw an error like { noSuchMethod: was called on null }.
Authentication Class
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
Future deleteUser(String email, String password) async {
try {
FirebaseUser user = await _auth.currentUser();
AuthCredential credentials =
EmailAuthProvider.getCredential(email: email, password: password);
print(user);
AuthResult result = await user.reauthenticateWithCredential(credentials);
await DatabaseService(uid: result.user.uid)
.deleteuser(); // called from database class
await result.user.delete();
return true;
} catch (e) {
print(e.toString());
return null;
}
}
}
Then use the following code inside the clickable event of a flutter widget tree to achieve the goal:
onTap: () async {
await AuthService().deleteUser(email, password);
}

How to get CONTEXT for the provider to work? Flutter

In the Future fetchStudentInfo() function, i would like to use the userId from my Auth class to do filtering. The userId is embedded in the URL and it will retrieve data from database. But, the issue is that the context is lacking in the function itself. However, I couldn't figure out a way to pass in the context. It would be great if any legend could help me. The solution which retrieve data from internet is found on the flutter documentation. And i wouldn't like to hard code the userId.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:provider/provider.dart';
import '../model/student.dart';
import '../provider/auth.dart';
Future<Student> fetchStudentInfo() async {
final auth = Provider.of<Auth>(context);
final response = await http.post(
'https://intermediary-sharpe.000webhostapp.com/Student/read_one.php?userId=$auth.userId');
if (response.statusCode == 200) {
return Student.fromJson(json.decode(response.body));
} else {
throw Exception('Failed');
}
}
class ProfileScreen extends StatefulWidget {
#override
_ProfileScreenState createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
Future<Student> student;
#override
void initState() {
// TODO: implement initState
super.initState();
student = fetchStudentInfo();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<Student>(
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.studentId);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return CircularProgressIndicator();
},
future: student,
),
);
}
}
import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
import 'package:shared_preferences/shared_preferences.dart';
import '../model/http_exception.dart';
class Auth with ChangeNotifier {
String _token;
DateTime _expiryDate;
String userId;
Timer _authTimer;
bool get isAuthenticated {
return token != null;
}
String get token {
if (_expiryDate != null &&
_expiryDate.isAfter(DateTime.now()) &&
_token != null) {
return _token;
}
return null;
}
Future<void> _authenticate(
String email, String password, String urlSegment) async {
final url =
'https://identitytoolkit.googleapis.com/v1/accounts:$urlSegment?key=AIzaSyCkNZysDY4PGpScw2jUlBpd0mvpGjgSEag';
try {
final response = await http.post(
url,
body: json.encode(
{
'email': email,
'password': password,
'returnSecureToken': true,
},
),
);
final responseData = json.decode(response.body);
if (responseData['error'] != null) {
throw HttpException(responseData['error']['message']);
}
_token = responseData['idToken'];
userId = responseData['localId'];
_expiryDate = DateTime.now().add(
Duration(
seconds: int.parse(
responseData['expiresIn'],
),
),
);
_autoLogout();
notifyListeners();
final prefs = await SharedPreferences.getInstance();
final userData = json.encode({
'token': _token,
'userId': userId,
'expiryDate': _expiryDate.toIso8601String(),
});
prefs.setString('userData', userData);
} catch (error) {
throw error;
}
}
//Auto Login Function
Future<bool> tryAutoLogin() async {
final prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey('userData')) {
return false;
}
final extractedUserData =
json.decode(prefs.getString('userData')) as Map<String, Object>;
final expiryDate = DateTime.parse(extractedUserData['expiryDate']);
if (expiryDate.isBefore(DateTime.now())) {
return false;
}
_token = extractedUserData['token'];
userId = extractedUserData['userId'];
_expiryDate = expiryDate;
notifyListeners();
_autoLogout();
return true;
}
//SignUp function
Future<void> signUp(String email, String password) async {
return _authenticate(email, password, 'signUp');
}
//Login Function
Future<void> login(String email, String password) async {
return _authenticate(email, password, 'signInWithPassword');
}
//Logout Function
Future<void> logout() async {
_token = null;
userId = null;
_expiryDate = null;
if (_authTimer != null) {
_authTimer.cancel();
_authTimer = null;
}
notifyListeners();
final prefs = await SharedPreferences.getInstance();
prefs.clear();
}
//Auto Logout function
void _autoLogout() {
if (_authTimer != null) {
_authTimer.cancel();
}
final timeToExpiry = _expiryDate.difference(DateTime.now()).inSeconds;
_authTimer = Timer(Duration(seconds: timeToExpiry), logout);
}
//PHP related functions
}
Thank you in advance.
I agree with #lyio, you need to modify the function to pass the context, however after passing context, you cannot call it from initState as stated in documentation of initState
BuildContext.dependOnInheritedWidgetOfExactType from this method. However, didChangeDependencies will be called immediately following this method, and BuildContext.dependOnInheritedWidgetOfExactType can be used there.
Getting provider with Provider.of(context) under the hood is using the inherited widget, so cannot be called using context from initState
So implement instead of initState use didChangeDependencies to call your fetchStudentsInfo(context) method
Wouldn't the easiest solution be to pass the context into fetchStudentInfo?
You would change fetchStudentInfo() to fetchStudentInfo(BuildContext context). And then, when you call the method you pass in the required context. That way, you have the appropriate context available.
If you are not using the `fetchStudentInfo()` outside of the state class, then just move that method into the state class and the issue will be resolved.
Since Any state class has a context getter defined by default./
I just realized how improper this answer was.
Update:
According to the answer by #dlohani, didChangeDependencies should be used in stead of initState.
So what you can do is following:
Pass BuildContext as parameter in the fetchStudentInfo method
Override didChangeDependencies in state class & call fetchStudentInfo from here instead of initState