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());
}
}
}
Related
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.
Hello I’m new to flutter
I’m trying to retrieve the user data from his email but i got this error [Null is not a subtype of type String]
The data I’m trying to retrieve is not null
This is my code
class _ProfilePageState extends State<ProfilePage> {
late User user;
final _auth = FirebaseAuth.instance;
late User signedInUser;
var id;
var email;
var name;
var age;
var sex;
#override
void initState() {
super.initState();
onRefresh(FirebaseAuth.instance.currentUser);
getCurrentUser();
}
onRefresh(userCare)
{
setState(()
{
user = userCare;
});
}
void getCurrentUser()
{
try {
final user = _auth.currentUser;
if (user != null) {
signedInUser = user;
email = signedInUser.email;
id = signedInUser.uid;
}
} catch (e) {
print(e);
}
}
void getData() {
FirebaseFirestore.instance
.collection('users')
.get()
.then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
if (doc["email"] == signedInUser.email) {
name = doc['name'];
age = doc['age'];
sex = doc['sex'];
print(doc['name']);
}
});
});
}
This is my data
I want to retrieve then but i can’t because it says null how to fix the error?
this is the data I’m trying to retrieve
the error image
Please try this Code:
void getData() async {
await FirebaseFirestore.instance
.collection('users')
.get()
.then((value) {
for(var doc in value.docs) {
if (doc["email"] == signedInUser.email) {
name = doc.data()['name'];
age = doc.data()['age'];
sex = doc.data()['sex'];
print(doc.data()['name']);
}
}
});
}
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);
}
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');
}
});
}
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