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.
Related
I have 2 data provider classes that extend ChangeNotifier. Within each, there's a function to fetch data and at the end of them, I use notifyListeners() to notify the screens/listeners that the data changed. However, it seems that the listeners start getting notified endlessly instead of once and that creates a loop of reloading, circle indicators that don't go away, and a frozen screen. I don't get it.
Data providers:
class UsersDataProvider extends ChangeNotifier {
UsersDataProvider() : super();
static Map<int, QueryDocumentSnapshot<Object?>> usersMap = {};
Future<void> fetchUsers() async {
final userRef = FirebaseFirestore.instance.collection('users');
final QuerySnapshot result = await userRef.get();
final docs = result.docs.asMap();
usersMap = docs;
print(usersMap.length);
notifyListeners();
}
}
class PostsDataProvider extends ChangeNotifier {
PostsDataProvider() : super();
static Map<int, QueryDocumentSnapshot<Object?>> postsMap = {};
Future<void> fetchPosts() async {
UsersDataProvider.usersMap.forEach((index, resultValue) async {
final postsRef = FirebaseFirestore.instance
.collection('users')
.doc(resultValue.id)
.collection('posts');
final QuerySnapshot postsResult = await postsRef.get();
final postDocs = postsResult.docs.asMap();
postsMap = postDocs;
print('Post map: ${postsMap.length}');
notifyListeners();
});
}
}
Add listeners and reload data:
Future<void> fetchUsersAndPosts(bool initial) async {
if (!initial) {
setState(() {
postsLoading = true;
});
usersDataProvider.fetchUsers();
postsDataProvider.fetchPosts();
}
if (initial) {
usersDataProvider.addListener(() {
print('changed');
setState(() {
fetchUsersAndPosts(false);
});
});
}
if (initial) {
postsDataProvider.addListener(() {
setState(() {
fetchUsersAndPosts(false);
});
});
}
UsersDataProvider.usersMap.forEach((index, value) async {
List<Post> posts = [];
PostsDataProvider.postsMap.forEach((index, value) {
final post = Post.fromJson(value.data() as Map<String, dynamic>);
posts.add(post);
setState(() {});
if (posts.length == PostsDataProvider.postsMap.length) {
setState(() {
postsList = posts;
postsList.sort((a, b) {
return b.date.compareTo(a.date);
});
postsLoading = false;
});
}
});
final profileInfo =
ProfileInfoObject.fromJson(value.data() as Map<String, dynamic>);
Profile profile = Profile(profileInfo, postsList.where((p) => p.uid == value.id).toList());
UserSearchResult user = (UserSearchResult(profile, value.id));
if (usersList.where((u) => u.uid == user.uid).toList().isEmpty) {
setState(() {
usersList.add(user);
});
}
});
setState(() {
postsList.sort((a, b) {
return b.date.compareTo(a.date);
});
});
}
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 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());
}
}
}
I using Flutter Riverpod package to handling http request. I have simple Http get request to show all user from server, and i using manage it using FutureProvider from Flutter Riverpod package.
API
class UserGoogleApi {
Future<List<UserGoogleModel>> getAllUser() async {
final result = await reusableRequestServer.requestServer(() async {
final response =
await http.get('${appConfig.baseApiUrl}/${appConfig.userGoogleController}/getAllUser');
final Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson['status'] == 'ok') {
final List list = responseJson['data'];
final listUser = list.map((e) => UserGoogleModel.fromJson(e)).toList();
return listUser;
} else {
throw responseJson['message'];
}
});
return result;
}
}
User Provider
class UserProvider extends StateNotifier<UserGoogleModel> {
UserProvider([UserGoogleModel state]) : super(UserGoogleModel());
Future<UserGoogleModel> searchUserByIdOrEmail({
String idUser,
String emailuser,
String idOrEmail = 'email_user',
}) async {
final result = await _userGoogleApi.getUserByIdOrEmail(
idUser: idUser,
emailUser: emailuser,
idOrEmail: idOrEmail,
);
UserGoogleModel temp;
for (var item in result) {
temp = item;
}
state = UserGoogleModel(
idUser: temp.idUser,
createdDate: temp.createdDate,
emailUser: temp.emailUser,
imageUser: temp.emailUser,
nameUser: temp.nameUser,
tokenFcm: temp.tokenFcm,
listUser: state.listUser,
);
return temp;
}
Future<List<UserGoogleModel>> showAllUser() async {
final result = await _userGoogleApi.getAllUser();
state.listUser = result;
return result;
}
}
final userProvider = StateNotifierProvider((ref) => UserProvider());
final showAllUser = FutureProvider.autoDispose((ref) async {
final usrProvider = ref.read(userProvider);
final result = await usrProvider.showAllUser();
return result;
});
After that setup, i simply can call showAllUser like this :
Consumer((ctx, read) {
final provider = read(showAllUser);
return provider.when(
data: (value) {
return ListView.builder(
itemCount: value.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
final result = value[index];
return Text(result.nameUser);
},
);
},
loading: () => const CircularProgressIndicator(),
error: (error, stackTrace) => Text('Error $error'),
);
}),
it's no problem if http request don't have required parameter, but i got problem if my http request required parameter. I don't know how to handle this.
Let's say , i have another http get to show specific user from id user or email user. Then API look like :
API
Future<List<UserGoogleModel>> getUserByIdOrEmail({
#required String idUser,
#required String emailUser,
#required String idOrEmail,
}) async {
final result = await reusableRequestServer.requestServer(() async {
final baseUrl =
'${appConfig.baseApiUrl}/${appConfig.userGoogleController}/getUserByIdOrEmail';
final chooseURL = idOrEmail == 'id_user'
? '$baseUrl?id_or_email=$idOrEmail&id_user=$idUser'
: '$baseUrl?id_or_email=$idOrEmail&email_user=$emailUser';
final response = await http.get(chooseURL);
final Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson['status'] == 'ok') {
final List list = responseJson['data'];
final listUser = list.map((e) => UserGoogleModel.fromJson(e)).toList();
return listUser;
} else {
throw responseJson['message'];
}
});
return result;
}
User Provider
final showSpecificUser = FutureProvider.autoDispose((ref) async {
final usrProvider = ref.read(userProvider);
final result = await usrProvider.searchUserByIdOrEmail(
idOrEmail: 'id_user',
idUser: usrProvider.state.idUser, // => warning on "state"
);
return result;
});
When i access idUser from userProvider using usrProvider.state.idUser , i got this warning.
The member 'state' can only be used within instance members of subclasses of 'package:state_notifier/state_notifier.dart'.
It's similiar problem with my question on this, but on that problem i already know to solved using read(userProvider.state) , but in FutureProvider i can't achieved same result using ref(userProvider).
I missed something ?
Warning: This is not a long-term solution
Assuming that your FutureProvider is being properly disposed after each use that should be a suitable workaround until the new changes to Riverpod are live. I did a quick test to see and it does work. Make sure you define a getter like this and don't override the default defined by StateNotifier.
class A extends StateNotifier<B> {
...
static final provider = StateNotifierProvider((ref) => A());
getState() => state;
...
}
final provider = FutureProvider.autoDispose((ref) async {
final a = ref.read(A.provider);
final t = a.getState();
print(t);
});
Not ideal but seems like a fine workaround. I believe the intention of state being inaccessible outside is to ensure state manipulations are handled by the StateNotifier itself, so using a getter in the meantime wouldn't be the end of the world.
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