Flutter - null safety and models - flutter

I have returned to Flutter after while (in the meantime null safety was introduced). I ended up with a need to update a project. Let's say in that project I have a User model. I managed to update most of the project but the part that gives me a headache is logout action. There is a need to clear the user (after logout) which I need to set up as null or empty it otherwise, but of course I am getting an error:
Unhandled Exception: type 'Null' is not a subtype of type 'User'
So my question here is what is the best strategy to clear not only user but any other models I have for a redux state without hitting this problem with models not being able to be null?
User model:
class User {
String id;
String username;
String email;
String jwt;
User({ required this.id, required this.username, required this.email, required this.jwt });
factory User.fromJson(Map<String, dynamic> json) {
return User (
id: json['id'],
username: json['username'],
email: json['email'],
jwt: json['jwt']
);
}
}
User actions:
/* User actions */
ThunkAction<AppState> getUserAction = (Store<AppState> store) async {
final prefs = await SharedPreferences.getInstance();
final String? storedUser = prefs.getString('user');
final user = storedUser != null ? User.fromJson(json.decode(storedUser)) : null;
if(user != null) {
store.dispatch(GetUserAction(user));
}
};
ThunkAction<AppState> logoutUserAction = (Store<AppState> store) async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('user');
var user;
store.dispatch(LogoutUserAction(user));
};
class GetUserAction {
final User _user;
User get user => this._user;
GetUserAction(this._user);
}
class LogoutUserAction {
final User _user;
User get user => this._user;
LogoutUserAction(this._user);
}
NOTE: see how I managed to go about the null in the getUserAction (login) part. I just don't dispatch the action if it is null, however I can not do this in the logout as I need explicitly to set the user to null (clear it) and that way log it out from the app.
Can I make a model accept null values? How would I go about this? Or is there any other way I should go about this? Thank you for your kind answer.

Change your model to:
class User {
String? id;
String? username;
String? email;
String? jwt;
User({ required this.id, required this.username, required this.email, required this.jwt });
factory User.fromJson(Map<String, dynamic> json) {
return User (
id: json['id'] ?? "",
username: json['username'] ?? "",
email: json['email'] ?? "",
jwt: json['jwt'] ?? ""
);
}
}
And you need check null_safety for all variable with operation ??
After logout you can check user null with user.id == "" or user == User()
ThunkAction<AppState> logoutUserAction = (Store<AppState> store) async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('user');
var user = User();
store.dispatch(LogoutUserAction(user));
};

Related

Tried to fetch document data in firestore subcollection then show this error "Null check operator used on a null value "on flutter?

I tried to fetch document data in firestore subcollection then show this error "Null check operator used on a null value " .
I want to fetch one article in user collection for each users.
database screenshot
user table
article subcollection
all articles UI
how to fetch a article when click view button
View button code in All articles UI
ElevatedButton(child: Text('View'),onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => ViewOneUserArticleScreen(id: data[index].id,)));
view one article code
Articles? oneArticle;
bool loading = false;
#override
initState() {
super.initState();
loading = true;
getArticle();
}
User? user = FirebaseAuth.instance.currentUser;
UserModel loggedInUser = UserModel();
Future<void> getArticle() async {
final id = widget.id;
final reference = FirebaseFirestore.instance.doc('users/${user?.uid}/articles/$id');
final snapshot = reference.get();
final result = await snapshot.then(
(snap) => snap.data() == null ? null : Articles.fromJson(snap.data()!));
setState(() {
oneArticle = result;
loading = false;
});
}
model
class Articles {
final String id;
final String topic;
final String description;
final String url;
Articles({
required this.id,
required this.topic,
required this.description,
required this.url
});
Articles.fromJson(Map<String, dynamic> json)
: this(
id: json['id'],
topic: json['topic']! as String,
url: json['url']! as String,
description: json['description']! as String,
);
Map<String, Object?> toJson() {
return {
'id': id,
'topic': topic,
'url': url,
'description': description,
};
}
}
new error
Your Issue is in your parsing method, change your Articles.fromJson to this:
Articles.fromJson(Map<String, dynamic> json)
: this(
id: json['id'] ?? '', // <--- change this
topic: json['topic'] as String ?? '', // <--- change this
url: json['url'] as String ?? '', // <--- change this
description: json['description'] as String ?? '', // <--- change this
);
in your json, topic, description and url may be null but you used ! on them and that means you are sure that they aren't null but they are. Also your id may be null to but in your object model you set it as required, so you need to provide default value to it or just remove the required before it.

How to create a FirebaseAuth User from a query from his UID?

I am a bit puzzled about how the Users are managed.
My app successfully creates user accounts via FirebaseAuth whom are creating classified ads.
I need to fetch the owner of the ad from the User UID and so far my code is as follows:
Future<Map<String, dynamic>?> getUser(String uid) async {
final d = await FirebaseFirestore.instance
.collection("User")
.where("id", isEqualTo: uid)
.get();
if (d.docs.isEmpty) return null;
return d.docs.first.data();
}
This code is expected to fetch the given user as a map.
And now, I'd like to convert the Map<String,dynamic> as an actual User instance. But how I should do that?
And is it the right way to go? Because I am wondering whether the User should only be dedicated to the 'authenticated self'.
If I'm right, do you mean the User of the firebase_auth package? You cannot convert Map<String, dynamic> to User. My opinion is to convert it to a model. Take a look at the example below:
class UserModel {
UserModel(
this.id,
this.name,
this.email,
…
);
final String id;
final String name;
final String email;
…
factory UserModel.fromMap(Map<String, dynamic> data, String documentId) {
final String? name = data["name"];
final String? email = data["email"];
…
return UserModel(
documentId,
name ?? "",
email ?? "",
…
);
}
Map<String, dynamic> toMap() => {
"name": name,
"email": email,
…
};
}
UserModel.fromMap(d.docs.first.data(), d.docs.first.id),
UserModel(
"Name",
"Email",
…
).toMap(),

A non-null String must be provided to a Text widget. 'data != null' and Error logging in

I am trying to perform user login with my flutter app but then I keep getting data != null error and that a Text Widget must have a non-null string.
Upon further debugging, I realized the response['message'] is printing a null value so I implement a condition to check if it's not null before proceeding but yet still it keeps giving me the same error.
When I use response['message'].toString(), it still gives the same error.
this is the full error being thrown 'data != null': A non-null String must be provided to a Text widget.
the issue seems to be from the response['message'] but I just can't seem to find ways to solve it
This is Auth controller class
class AuthController extends GetxController {
AuthService authService = AuthService();
ProjectApis projectApis = ProjectApis();
String name = '';
String email = '';
String password = '';
String confirmPassword = '';
var isPasswordHidden = true.obs;
Future loginUser(BuildContext context) async {
buildLoader(context, message: 'Loading...');
http.Response response = await authService.signInUser(
email,
password,
);
if (response.statusCode == 200) {
Map<String, dynamic> responseData = json.decode(response.body);
debugPrint(responseData.toString());
debugPrint(responseData['message']);
if (responseData["status"] == true) {
User user = User.fromJson(responseData);
UserPreferences().setUser(user);
Navigator.pop(context);
Get.offAll(() => BottomNavigation());
return;
} else {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(responseData['message']),
));
return;
}
} else {
Navigator.pop(context);
showErrorDialog(context, message: "Server Error");
return;
}
}
}
This is the sign in function
Future<http.Response> signInUser(
String email,
String password,
) async {
Map data = {
'email': email,
'password': password,
};
var body = json.encode(data);
var url = Uri.parse(projectApis.loginUrl);
var response = await client.post(
url,
body: body,
headers: projectApis.headers,
);
return response;
}
This is the User model class
User userFromJson(String str) => User.fromJson(json.decode(str));
String userToJson(User data) => json.encode(data.toJson());
class User {
User({
this.id,
this.name,
this.email,
this.password,
this.passwordConfirm,
this.token,
});
int? id;
String? name;
String? email;
String? password;
String? passwordConfirm;
String? token;
String applicationDirPath = "";
factory User.fromJson(Map<String, dynamic> json) => User(
id: json["id"],
name: json["name"],
email: json["email"],
password: json["password"],
passwordConfirm: json["passwordConfirm"],
token: json["token"],
);
Map<String, dynamic> toJson() => {
"id": id,
"name": name,
"email": email,
"password": password,
"passwordConfirm": passwordConfirm,
"token": token,
};
}
Use null-operator like here
response['message'] ?? ''
If left side was null the right side will assing
But you can use this just if you are sure this happen because of this line
read in medium
Text widget doesn't accept nullable String, and reading map can provide null value. You can provide default value on null case like
Text(myMap["key"]??"defaultValue")
And for perser you can do
if (responseData["status"] != null && responseData["status"]==true ) {
I think problem is with the Text(responseData['message']), line.
Dart can't be sure that me message key exist on responseData Map. So Text(responseData['message']), can be null which is bad for null safety.
Just do:
String message = responseData['message'] ?? '';
The ?? operator will return an empty string in case ResponseData['message'] is null.
Then replace in your Text widget:
Text(message),

Single user api fetch, flutter

I only want to fetch/get single api data, i tried to print the response from service i got all the api data but when i try to pass it to the controller the variable for model didn't correct,
i already look for the anwser for fetching single api with Service and Controller architecture with GetX but i didn't find any suitable answer, i hope you can help me it's very important. thanks
im using http and GetX
the problem is here
var user = <UserModel>{}.obs;
user.value = _user;
it tells
UserModel _user A value of type 'UserModel' can't be assigned to a
variable of type 'Set'. Try changing the type of the
variable, or casting the right-hand type to 'Set
here is the code
Api Service
class ApiService {
Future<UserModel> fetchApi(id) async {
var url = 'https://reqres.in/api/users/$id';
var response = await http.get(Uri.parse(url));
if(response.statusCode == 200){
var dataResponse = jsonDecode(response.body)['data'];
UserModel user = UserModel.fromJson(dataResponse);
return user;
} else {
throw Exception('Failed Get API');
}
}
}
Controller
class Controller extends GetxController {
var user = <UserModel>{}.obs;
Future fetchApi(id) async {
try {
var _user = await ApiService().fetchApi(id);
user.value = _user;
print(user);
} catch (e) {
print(e);
}
}
}
Model
class UserModel {
int id;
String email;
String name;
String avatar;
UserModel({
required this.id,
required this.email,
required this.name,
required this.avatar,
});
factory UserModel.fromJson(Map<String, dynamic> json) => UserModel(
id: json['id'],
email: json['email'],
name: json['first_name'] + ' ' + json['last_name'],
avatar: json['avatar'],
);
}
The main reason is that you are defining user as {} type and you need UserModel Type.
You need change this:
var user = <UserModel>{}.obs;
user.value = _user;
for this:
var user = UserModel().obs;
user.value = _user;

The method '[]' was called on null: firebase flutter

I need to retrieve User Data from Firebase and use a builder to pass on the data to UI. When I run the apps, I method is called in on Null.
I tried many ways to call firebase data but I keep receive error message on provider or on calling the data NULL.
The error is most likely coming from the method _getProfileData() below.
_getProfileData(AuthNotifier authNotifier) async {
final uid = await Provider.of(context, listen: false).authNotifier.getCurrentUID();
await Provider.of(context, listen: false)
.collection('Users')
.document(uid)
.get().then((result) {
user.isAdmin = result.data['isAdmin'];
});
}
When I made the changes below by using Provider, another error appears with Provider not working.
final uid = await Provider.of<authNotifier>(context, listen: false).getCurrentUID();
I placed the getter in the API.
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
// GET UID
Future<String> getCurrentUID(User user, AuthNotifier authNotifier) async {
return (await _firebaseAuth.currentUser()).uid;
}
// GET CURRENT USER
Future getCurrentUser(User user, AuthNotifier authNotifier) async {
return await _firebaseAuth.currentUser();
}
Stream<String> get onAuthStateChanged => auth.onAuthStateChanged.map(
(FirebaseUser user) => user?.uid,
);
I structured User Data as below.
class User {
List favorites = [];
String documentID;
String displayName;
String email;
String password;
bool isAdmin;
User({
this.favorites,
this.documentID,
this.displayName,
this.email,
this.password,
this.isAdmin,
});
factory User.fromFirestore(DocumentSnapshot document) {
Map data = document.data;
return User(
favorites: data['favorite'] ?? [],
documentID: document.documentID,
displayName: data['displayName'] ?? '',
email: data['email'] ?? '',
isAdmin: data['isAdmin'] ?? false,
);
}
// get admin => null;
Map<String, dynamic> toMap() {
return {
'displayName': displayName,
'email': email,
'isAdmin': isAdmin,
};
}
}