Flutter getting value from provider show null - flutter

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");
;
}),
);
}
}

Related

Hive not updating field values

I have a user box where I store some data about the user.
In the main.dart file, I am registering and opening the UserDBAdapter
void main() async {
await Hive.initFlutter();
Hive.registerAdapter(UserDBAdapter());
await Hive.openBox<UserDB>('users');
return runApp(
MyApp(),
);
}
In my Bloc, I have a function which logins the user and then saves true in the isLoggedIn field of the user
class UserBloc extends Bloc<UserEvent, UserState> {
UserDB user = UserDB();
final usersBox = Hive.box<UserDB>('users');
UserRepository userRepository;
UserBloc(this.userRepository) : super(UserInitial()) {
createUser();
verifyUser();
loginUser();
logoutUser();
usersBox.put('user', user);
}
Future<void> loginUser() async {
return on<LoginUser>((event, emit) async {
emit(UserLoading());
await Future.delayed(const Duration(milliseconds: 500));
try {
final result = await userRepository.login(
event.email,
event.password,
);
if (result['success'] == false) {
emit(UserError(result['message']));
} else {
emit(
UserLoggedIn(),
);
user.isLoggedIn = true; // I set it to true here
user.token = result['token'];
user.save(); // Then I save it here. which means the new changes is persisted in the local storage
}
} catch (error) {
emit(
UserError(
error.toString(),
),
);
}
});
}
But my point is that, when I try hot reloading the app, it sends me to the login screen instead of the HomeScreen()
#override
void initState() {
if (Hive.box<UserDB>('users').get('user') == null) {
print('empty'); // This does not print, that means I have a user in the local storage
Hive.box<UserDB>('users').put(
'user',
UserDB(isVerified: false, isLoggedIn: false),
);
}
super.initState();
}
#override
Widget build(BuildContext context) {
final userDB = Hive.box<UserDB>('users').get('user')!;
print(userDB.isLoggedIn); // Here prints false instead of true
if (userDB.isLoggedIn) {
return const NavigationScreen();
} else if (userDB.isVerified && !userDB.isLoggedIn) {
return AnimatedSplashScreen(
splash: const SplashScreen(),
duration: 4000,
nextScreen: const LoginSignUpSwitch(),
);
} else {
return AnimatedSplashScreen(
splash: const SplashScreen(),
duration: 4000,
nextScreen: const WelcomeScreen(),
);
}
}
Below is the UserDB class
import 'package:hive/hive.dart';
part 'user.g.dart';
#HiveType(typeId: 1)
class UserDB extends HiveObject {
UserDB({
this.name,
this.email,
this.isVerified = false,
this.isLoggedIn = false,
this.token,
});
// NAME (0)
#HiveField(0)
String? name;
//EMAIL(1)
#HiveField(1)
String? email;
//IS-VERIFIED(2)
#HiveField(2, defaultValue: false)
bool isVerified;
//IS-LOGGEDIN(3)
#HiveField(3, defaultValue: false)
bool isLoggedIn;
//TOKEN(4)
#HiveField(4)
String? token;
}
NB: I am printing the value of the isLoggedIn in the HomeScreen and it prints true
final userDB = Hive.box<UserDB>('users').get('user');
print(userDB!.isLoggedIn); // Prints true here

State managment in flutter with consumer and scaffoldState

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.

flutter context become null if list is not empty

I have a form to create/update activity which is a stateful widget like this:
class ActivityForm extends StatefulWidget {
final Activity activity;
final bool isEdit;
const ActivityForm({Key key, this.activity, this.isEdit}) : super(key: key);
#override
_ActivityFormState createState() => _ActivityFormState();
}
class _ActivityFormState extends State<ActivityForm> {
final _formKey = GlobalKey<FormState>();
List<int> activityDetailIdToBeDeleted;
...
void submitHandler() async {
setState(() {
_isLoading = true;
});
// Mapping input field to activity model
Activity activity = Activity(
id: widget.activity.id,
tanggal: _tanggalController.text,
uraianKegiatan: _uraianKegiatanController.text,
pic: _picController.text,
jumlahTim: int.parse(_jumlahTimController.text),
kendala: _kendalaController.text,
penyelesaian: _penyelesaianController.text,
approverUserId: selectedApprovalUser,
rincianPekerjaan: rincianPekerjaanList,
status: 'PENDING');
if (widget.isEdit) {
await Provider.of<ActivityProvider>(context, listen: false)
.updateActivity(activity, activityDetailIdToBeDeleted);
} else {
await Provider.of<ActivityProvider>(context, listen: false)
.createActivity(activity);
}
Navigator.pop(context);
}
in my form there are some detail list of activities. When i press delete it adds the id of
the activity detail to the list of "to be deleted" :
IconButton(
icon: Icon(Icons.delete),
onPressed: () => removeRincianPekerjaan(
activityDetail.description),),
here's the function:
void removeRincianPekerjaan(desc) {
if (widget.isEdit) {
int detailId = rincianPekerjaanList
.where((element) => element.description == desc)
.first
.id;
if (!activityDetailIdToBeDeleted.contains(detailId))
activityDetailIdToBeDeleted.add(detailId);
print(activityDetailIdToBeDeleted);
}
setState(() {
rincianPekerjaanList
.removeWhere((element) => element.description == desc);
});
}
that list of ids will be sent to provider which looks like this:
Future<bool> updateActivity(
Activity activity, List<int> activityDetailToBeDeleted) async {
final response = await ActivityService()
.updateActivity(activity.id.toString(), activity.toMap());
if (response != null) {
// Update Success.
if (response.statusCode == 200) {
var body = json.decode(response.body);
removeActivityToList(activity);
activity = Activity.fromMap(body['activity']);
addActivityToList(activity);
if (activityDetailToBeDeleted.isNotEmpty) {
print("to be deleted is not empty.");
print("ID to be deleted: ${activityDetailToBeDeleted.join(",")}");
final res = await ActivityService()
.deleteActivityDetail(activityDetailToBeDeleted.join(","));
if (res.statusCode == 204) {
print('ID DELETED.');
return true;
}
}
return true;
}
// Error unauthorized / token expired
else if (response.statusCode == 401) {
var reauth = await AuthProvider().refreshToken();
if (reauth) {
return await createActivity(activity);
} else {
return Future.error('unauthorized');
}
}
return Future.error('server error');
}
return Future.error('connection failed');
}
the problem is if the list is empty, or i didn't add the id to the list, builder context is not null but if i add an id to the list "to be deleted", the Navigator.pop(context) will throw an error because context is null. I don't understand why it becomes null.
full code in here
Your [context] must came from the build() method. Like this.
void submitHandler(BuildContext context) async {
setState(() {
_isLoading = true;
});
// Mapping input field to activity model
Activity activity = Activity(
id: widget.activity.id,
tanggal: _tanggalController.text,
uraianKegiatan: _uraianKegiatanController.text,
pic: _picController.text,
jumlahTim: int.parse(_jumlahTimController.text),
kendala: _kendalaController.text,
penyelesaian: _penyelesaianController.text,
approverUserId: selectedApprovalUser,
rincianPekerjaan: rincianPekerjaanList,
status: 'PENDING');
if (widget.isEdit) {
await Provider.of<ActivityProvider>(context, listen: false)
.updateActivity(activity, activityDetailIdToBeDeleted);
} else {
await Provider.of<ActivityProvider>(context, listen: false)
.createActivity(activity);
}
SchedulerBinding.instance.addPostFrameCallback((_) {
Navigator.pop(context);
});
}

NoSuchMethodError: Class 'FlutterError' has no instance getter 'code'. Receiver: Instance of 'FlutterError' Tried calling: code)

I've been trying a sample Flutter application code from GitHub to simply login and register the user on Firebase. Every time I login or register after clean building the application, it takes me to the main page but throws this exception Exception has occurred. NoSuchMethodError (NoSuchMethodError: Class 'FlutterError' has no instance getter 'code'. Receiver: Instance of 'FlutterError' Tried calling: code)
I've no idea what 'FlutterError' is referring to because I don't see any such class. and there are two occurrences of code in the file named 'login-register.dart'. I'm attaching the code below:
(Note: it runs okay after I hot reload the app and the user is already logged in, only throws exception the first time)
void _validateLoginInput() async {
final FormState form = _formKey.currentState;
if (_formKey.currentState.validate()) {
form.save();
_sheetController.setState(() {
_loading = true;
});
try {
final FirebaseUser user = (await FirebaseAuth.instance
.signInWithEmailAndPassword(email: _email, password: _password)).user;
// final uid = user.uid;
Navigator.of(context).pushReplacementNamed('/home');
} catch (error) {
switch (error.code) {
case "ERROR_USER_NOT_FOUND":
{
_sheetController.setState(() {
errorMsg =
"There is no user with such entries. Please try again.";
_loading = false;
});
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
child: Text(errorMsg),
),
);
});
}
break;
case "ERROR_WRONG_PASSWORD":
{
_sheetController.setState(() {
errorMsg = "Password doesn\'t match your email.";
_loading = false;
});
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
child: Text(errorMsg),
),
);
});
}
break;
default:
{
_sheetController.setState(() {
errorMsg = "";
});
}
}
}
} else {
setState(() {
_autoValidate = true;
});
}
}
void _validateRegisterInput() async {
final FormState form = _formKey.currentState;
if (_formKey.currentState.validate()) {
form.save();
_sheetController.setState(() {
_loading = true;
});
try {
final FirebaseUser user = (await FirebaseAuth.instance
.createUserWithEmailAndPassword(
email: _email, password: _password)).user;
UserUpdateInfo userUpdateInfo = new UserUpdateInfo();
userUpdateInfo.displayName = _displayName;
user.updateProfile(userUpdateInfo).then((onValue) {
Navigator.of(context).pushReplacementNamed('/home');
Firestore.instance.collection('users').document().setData(
{'email': _email, 'displayName': _displayName}).then((onValue) {
_sheetController.setState(() {
_loading = false;
});
});
});
} catch (error) {
switch (error.code) {
case "ERROR_EMAIL_ALREADY_IN_USE":
{
_sheetController.setState(() {
errorMsg = "This email is already in use.";
_loading = false;
});
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
child: Text(errorMsg),
),
);
});
}
break;
case "ERROR_WEAK_PASSWORD":
{
_sheetController.setState(() {
errorMsg = "The password must be 6 characters long or more.";
_loading = false;
});
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
child: Text(errorMsg),
),
);
});
}
break;
default:
{
_sheetController.setState(() {
errorMsg = "";
});
}
}
}
} else {
setState(() {
_autoValidate = true;
});
}
}
The exception you're catching doesn't have a code property. That only exists with the firebase exception implementation, not the general exception class.
If you expect a certain type of error, you should explicitly catch that error and handle it properly and have a separate catch block for all other errors.
This can be done with an on ... catch block:
try {
final FirebaseUser user = (await FirebaseAuth.instance
.signInWithEmailAndPassword(email: _email, password: _password)).user;
// final uid = user.uid;
Navigator.of(context).pushReplacementNamed('/home');
} on FirebaseAuthException catch (error) {
...
} catch(e) {
...
}
The methods you're calling in the code you shared will throw FirebaseAuthExceptions as shown in the code above.
You are getting an error that is not a FirebaseError but a FlutterError. This means, it does not implement a code field.
You can simply put
if(!(error is FirebaseError)){
print(error.message); // this is the actual error that you are getting
}
right below catch(error) { (in both files) to handle this.
However, it seems like you get another Flutter Error that you might want to handle. It should be printed to the console now.

Show dialog using Scoped model

I have a basic login form, with my LoginModel.
But I do not understand how I can call to the function notifyListeners to display a dialog in my view.
The login widget:
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new ScopedModel<LoginModel>(
model: _loginModel,
child: Center(child: ScopedModelDescendant<LoginModel>(
builder: (context, child, model) {
if (model.status == Status.LOADING) {
return Loading();
}
else return showForm(context);
}))));
}
And the login model:
class LoginModel extends Model {
Status _status = Status.READY;
Status get status => _status;
void onLogin(String username, String password) async {
_status = Status.LOADING;
notifyListeners();
try {
await api.login();
_status = Status.SUCCESS;
notifyListeners();
} catch (response) {
_status = Status.ERROR;
notifyListeners();
}
}
I need to display a dialog when the status is Error
Finally I got this, just returning a Future in the method onLogin
Future<bool> onLogin(String username, String password) async {
_status = Status.LOADING;
notifyListeners();
try {
await api.login();
_status = Status.SUCCESS;
notifyListeners();
return true;
} catch (response) {
_status = Status.ERROR;
notifyListeners();
return false;
}
}
And in the widget:
onPressed: () async {
bool success = await _loginModel.onLogin(_usernameController.text, _passwordController.text);
if(success) {
Navigator.pop(context, true);
}
else{
_showDialogError();
}
}