Shared Preferences set null after setting a new value to the key - flutter

Am working on the task of changing the user name, however, when I click the button of changing the firstName and lastName, the shared preference is not set. This is my API Provider class am hitting :
class ChangeNameApiProvider {
Future<SharedPreferences> _pref = SharedPreferences.getInstance();
static const String USER = "USER";
// ignore: missing_return
Future<int> changeName(
String firstName, String lastName, String userId) async {
SharedPreferences prefs = await _pref;
String user = prefs.getString(USER);
var _user = jsonDecode(user);
final response = await http.put(
'${BaseUrl.BASE_URL}/v1/Users/$userId',
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer ${_user['token']}'
},
body: jsonEncode(<String, String>{
'first_name': firstName,
'last_name': lastName,
}),
);
if (response.statusCode == 200) {
var data = jsonDecode(response.body);
_user['user'] = data;
_pref.then((SharedPreferences sharedPref) {
prefs = sharedPref;
print('after output $_user');
prefs.setString(USER, _user.toString());
print('after 2 output $_user');
});
return response.statusCode;
} else {
return response.statusCode;
}
}
}
Then, this is the Button on the form I trigger to call the above changeName() method :
FlatButton(
// ignore: sdk_version_ui_as_code
onPressed: () async => {
if (_formKey.currentState.validate())
{
changeNameRepository
.editName(_firstNameController.text,
_lastNameController.text, '${widget.userId}')
.then((value) async {
if (value == 200) {
_showToastMessage("Name changed successfully.");
await Navigator.pop(context);
}
})
}
},
child: Text(
'SAVE CHANGES',
textAlign: TextAlign.right,
style: TextStyle(
color: Color(0xFFD2232A),
letterSpacing: 1.25,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
)
Then, this is the repository :
class ChangeNameRepository {
final changeNameApiProvider = ChangeNameApiProvider();
Future editName(firstName, lastName, userId) =>
changeNameApiProvider.changeName(firstName, lastName, userId);
}
So, when all the above code is executed, it's supposed to update the shared preferences, then pop back to the previous screen with updated Shared Preference vale USER.
But it seems, the value is set to null, it's not updating.
Finally, let me share how I retrieve the shared preferences on the first page after popping from the changing nanme page:
_localStorage() async {
try {
await SharedPreferences.getInstance().then((SharedPreferences prefs) {
preferences = prefs;
_user = preferences.getString(USER).toString();
});
setState(() {
user = jsonDecode(_user);
});
} catch (e) {
log('data: $e');
}
}
#override
void initState() {
// TODO: implement initState
super.initState();
_localStorage();
}
What am I missing?

Related

How to add json to an autocomplete widget in flutter

Im trying to pass the data from an API to a list so that I can view it in the Autocomplete widget but I keep getting errors. I tried below code.
This is the code I have tried which passes data to the autocomplete as instance of 'Bus'
GetBuilder<BusesListController>(
init: BusesListController(),
builder: (_) {
_.viewPartners();
return DottedBorder(
child: Padding(
padding:
const EdgeInsets.only(left: 8.0),
child: Autocomplete<Bus>(
optionsBuilder: (TextEditingValue
textEditingValue) {
List<Bus> partnercos = [];
partnercos = _.partners.value as List<Bus>;
// (_.partners).map((value) => Bus.fromJson(value as Map<String, dynamic>)).toList();
print(partnercos);
return partnercos
.where((bus) => bus.company!
.toLowerCase()
.contains(textEditingValue
.text
.toLowerCase()))
.toList();
},
)),
);
}),
I also tried passing _.partners directly but it doesn't work either
Other fix I tried is passing _.partners instead of _.partners. Value above which invokes errors in arena.dart in void _tryToResolveArena which shows that state. Members.length == 1 hence scheduleMicrotask(() => _resolveByDefault(pointer, state));
Contoller code
class BusesListController extends GetxController {
var partners = [].obs;
var isLoaded = false.obs;
final loginController = Get.put(LoginController());
Future<void> viewPartners() async {
final token = loginController.rxToken.value;
var headers = {
'Authorization': 'Bearer $token'
};
try {
var url =
Uri.parse(ApiEndPoints.baseUrl + ApiEndPoints.endpoints.listBusAdmin);
http.Response response = await http.get(url, headers: headers);
if (response.statusCode == 200) {
final json = jsonDecode(response.body);
partners. Value =
(json as List).map((json) => Bus.fromJson(json)).toList();
isLoaded.value = true;
} else {
throw jsonDecode(response.body)["Message"] ?? "Unknown Error Occured";
}
} catch (error) {
// Get.snackbar('Error', error.toString());
}
}
#override
void onInit() {
super.onInit();
viewPartners();
}
}
I am able to print the response so I know the api works but I'm having problems with passing partners list into the autocomplete

how to create redirect to login if not authorized in flutter

how to make if the user's token is expired or not authorized it will be redirected to the login page.
I have a problem when I login, if the user token is expired, it should be redirected to the login page, but in this case it doesn't return to the login page, instead it gives an 'exception' error message, is there a code I missed.
Thank you.
Future<User?> login(String nim, String password) async {
String url = Constant.baseURL;
try {
var body = {
'username': nim,
'password': password,
};
var response = await http.post(
Uri.parse(
'$url/login_mhs',
),
body: body,
);
if (response.statusCode == 200) {
final token = jsonDecode(response.body)['data']['access_token'];
await UtilSharedPreferences.setToken(token);
print(token);
print(await UtilSharedPreferences.getToken());
return User.fromJson(jsonDecode(response.body));
} else {
return null;
}
} catch (e) {
print(e);
throw Exception();
}
}
and this when doing get data
Future<UserBiodata> getDataMahasiswa() async {
String url = Constant.baseURL;
String token = await UtilSharedPreferences.getToken();
final response = await http.get(
Uri.parse(
'$url/auth/mhs_siakad/biodata',
),
headers: {
'Authorization': 'Bearer $token',
},
);
if (response.statusCode == 200) {
return UserBiodata.fromJson(jsonDecode(response.body));
} else {
throw Exception();
}
}
this when calling it in the widget
TextButton(
onPressed: () async {
final prefs =
await SharedPreferences.getInstance();
prefs.setString(Constant.token, '');
if (nimController.text.isEmpty ||
passwordController.text.isEmpty) {
showError('NIM tidak sesuai');
} else {
setState(() {
isLoading = true;
});
User? user = await Provider.of<Services>(
context,
listen: false)
.login(nimController.text,
passwordController.text);
setState(() {
isLoading = false;
});
if (user == null) {
showError('NIM/Password tidak sesuai');
} else {
userProvider.user = user;
Navigator.pushNamedAndRemoveUntil(
context,
'/main',
(route) => false,
);
}
}
},
style: TextButton.styleFrom(
backgroundColor: primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(66),
),
),
child: Text(
"Login",
style: boldButton,
),
),
this is the result when I have a user whose token is expired or not authorized the result is like this
Use another if else condition (nested into your else of the event) like below:
if (user == null) {
showError('NIM/Password tidak sesuai');
} else {
if (token_is_not_found_equals_true){
Navigator.pushNamedAndRemoveUntil(
context,
'/login',
(route) => false,
);
}
else {
userProvider.user = user;
Navigator.pushNamedAndRemoveUntil(
context,
'/main',
(route) => false,
);
}
}
The way I handle is using the package flutter_modular, there you have a feature call Route Guard. You check details in it's documentation. It's very easy to understand and implement.
I think it's the cleanest way to handle users unauthorized users.

Map got edited in a function without any return statement

So strange behavior for me, I am so confused, I have been with these 3 hours and still don't get it.
Problem :
I am calling an instance from a provider withe name “idea”
and later using that instance attribute in my widget.
This is my instance :
Widget build(BuildContext context) {
var idea = Provider.of<Idea>(context, listen: false).allideas.firstWhere(
(element) => element.id == widget.idea_id,
);
then I am calling another provider to call two functions
HTTP request
final connectionData =
await Provider.of<IdeaConnection>(context, listen: false)
.createIdeaConnection(
idea.userId,
idea.id,
);
final ideaProvider =
Provider.of<IdeaConnection>(context, listen: false);
await ideaProvider.uppdeaIdeaConnectionsprov(
idea.connections,
connectionData,
);
Future<dynamic> createIdeaConnection(int resiver_user_id, int idea_id) async {
final url = Uri.parse('http://10.0.2.2:3000/ideaconnetion');
var jwt = await storage.read(key: "token");
var userid = await storage.read(key: "id");
var response;
if (jwt != null) {
final Map<String, String> tokenData = {
"Content-Type": "application/json",
"token": jwt
};
try {
response = await http.post(url,
headers: tokenData,
body: json.encode({
"resiver_user_id": resiver_user_id,
"idea_id": idea_id,
}));
response = jsonDecode(response.body);
} catch (error) {
print(error);
rethrow;
}
}
return response;
}
calling another function to update data locally
Future<void> uppdeaIdeaConnectionsprov(
Map<int, IdeaConnectionsModel> connections, data) async {
var id = await storage.read(key: "id") as String;
var userData = await getLogedinUserData();
connections.putIfAbsent(
data['user_id'],
() => IdeaConnectionsModel(
id: data['id'],
uppdatedAtt: DateTime.parse(data['updatedAt']),
fName: userData['fName'] as String,
imge: stringToByteList(userData['image']!),
lNmae: userData['lName'] as String,
userId: int.parse(id),
userName: userData['userName'] as String,
accepted: false,
idea_Id: data['idea_id'],
));
}
What is strange is that the uppdeaIdeaConnectionsprov() do not return any map, but the map I am sending to the function gets updated and i see changes in my Text widget how is this possible......
> Text(
> "${idea.connections.length} Connections",
> style: const TextStyle(
> color: Colors.white,
> fontWeight: FontWeight.bold,
> fontSize: 12),
> ),
As stated in the documentation, putIfAbsent() modifies the current map. If you want the current map to remain unchanged and get a new map with new values, you can clone the existing map and call putIfAbsent() on the new map.
Future<Map<int, IdeaConnectionsModel>> uppdeaIdeaConnectionsprov(
Map<int, IdeaConnectionsModel> connections, data) async {
var id = await storage.read(key: "id") as String;
var userData = await getLogedinUserData();
return Map<int, IdeaConnectionsModel>.from(connections)..putIfAbsent(
data['user_id'],
() => IdeaConnectionsModel(
id: data['id'],
uppdatedAtt: DateTime.parse(data['updatedAt']),
fName: userData['fName'] as String,
imge: stringToByteList(userData['image']!),
lNmae: userData['lName'] as String,
userId: int.parse(id),
userName: userData['userName'] as String,
accepted: false,
idea_Id: data['idea_id'],
));
}

Refresh page if data isn't shown on screen

I have a Future in my initState function that gets jwt from cache and uses it to get the logged in user's details. The initState function is:
#override
void initState() {
super.initState();
Future.delayed(Duration.zero, () async {
final token = await CacheService().readCache(key: "jwt");
if (token != null) {
await Provider.of<ProfileNotifier>(context, listen: false)
.decodeUserData(
context: context,
token: token,
option: 'home',
);
}
});
}
Now, it does work and I do get the data, but not on the first run. I have to either hot reload the emulator or navigate to another page and come back for the page to rebuild itself and show the data on screen. I don't understand why it doesn't show the data on the first run itself.
ProfileNotifier class:
class ProfileNotifier extends ChangeNotifier {
final ProfileAPI _profileAPI = ProfileAPI();
final CacheService _cacheService = CacheService();
ProfileModel _profile = ProfileModel(
profileImage: "",
profileName: "",
profileBio: "",
);
AccountModel _account = AccountModel(
userId: "",
userEmail: "",
userPassword: "",
);
ProfileModel get profile => _profile;
AccountModel get account => _account;
Future decodeUserData({
required BuildContext context,
required String token,
required String option,
}) async {
try {
_profileAPI.decodeUserData(token: token).then((value) async {
final Map<String, dynamic> parsedData = await jsonDecode(value);
var userData = parsedData['data'];
if (userData != null) {
List<String>? userProfileData = await _cacheService.readProfileCache(
key: userData['userData']['id'],
);
if (userProfileData == null) {
final isProfileAvailable =
await Provider.of<ProfileNotifier>(context, listen: false)
.getProfile(
context: context,
userEmail: userData['userData']['userEmail'],
);
if (isProfileAvailable is ProfileModel) {
_profile = isProfileAvailable;
} else {
_account = AccountModel(
userId: userData['userData']['id'],
userEmail: userData['userData']['userEmail'],
userPassword: userData['userData']['userPassword'],
);
_profile = ProfileModel(
profileImage: '',
profileName: '',
);
}
if (option != 'profileCreation' && isProfileAvailable == false) {
Navigator.of(context).pushReplacementNamed(ProfileCreationRoute);
}
} else {
_account = AccountModel(
userId: userData['userData']['id'],
userEmail: userData['userData']['userEmail'],
userPassword: userData['userData']['userPassword'],
);
_profile = ProfileModel(
profileName: userProfileData[3],
profileImage: userProfileData[4],
profileBio: userProfileData[5],
);
}
} else {
Navigator.of(context).pushReplacementNamed(AuthRoute);
}
notifyListeners();
});
} catch (e) {
debugPrint('account/profileNotifier decode error: ' + e.toString());
}
}
Future getProfile({
required BuildContext context,
required String userEmail,
}) async {
try {
var getProfileData = await _profileAPI.getProfile(
userEmail: userEmail,
);
final Map<String, dynamic> parsedProfileData =
await jsonDecode(getProfileData);
bool isReceived = parsedProfileData["received"];
dynamic profileData = parsedProfileData["data"];
if (isReceived && profileData != 'Fill some info') {
Map<String, dynamic> data = {
'id': (profileData['account']['id']).toString(),
'userEmail': profileData['account']['userEmail'],
'userPassword': profileData['account']['userPassword'],
'profile': {
'profileName': profileData['profileName'],
'profileImage': profileData['profileImage'],
'profileBio': profileData['profileBio'],
}
};
AccountModel accountModel = AccountModel.fromJson(
map: data,
);
return accountModel;
} else {
return false;
}
} catch (e) {
debugPrint('profileNotifier getProfile error: ' + e.toString());
}
}
Future setProfile({
required String profileName,
required String profileImage,
required String profileBio,
}) async {
_profile.profileName = profileName;
_profile.profileImage = profileImage;
_profile.profileBio = profileBio;
await _cacheService.writeProfileCache(
key: _account.userId,
value: [
_account.userId,
_account.userEmail,
_account.userPassword as String,
profileName,
profileImage,
profileBio,
],
);
notifyListeners();
}
}
CacheService class:
class CacheService {
Future<String?> readCache({
required String key,
}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
String? cache = await sharedPreferences.getString(key);
return cache;
}
Future<List<String>?> readProfileCache({
required String key,
}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
List<String>? cachedData = await sharedPreferences.getStringList(key);
return cachedData;
}
Future writeCache({required String key, required String value}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
await sharedPreferences.setString(key, value);
}
Future writeProfileCache(
{required String key, required List<String> value}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
await sharedPreferences.setStringList(key, value);
}
Future deleteCache({
required BuildContext context,
required String key,
}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
await sharedPreferences.remove(key).whenComplete(() {
Navigator.of(context).pushReplacementNamed(AuthRoute);
});
}
}
I can't seem to figure out the problem here. Please help.
EDIT: The data is used to show profileImage of user in CircleAvatar like this:
#override
Widget build(BuildContext context) {
ProfileModel profile =
Provider.of<ProfileNotifier>(context, listen: false).profile;
return GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
child: Scaffold(
drawer: const ProfileDrawer(),
appBar: AppBar(
backgroundColor: Colors.white,
leading: Row(children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 9),
child: Builder(builder: (BuildContext context) {
return InkWell(
onTap: () => Scaffold.of(context).openDrawer(),
child: CircleAvatar(
maxRadius: 20.0,
backgroundImage: profile.profileImage.isNotEmpty
? NetworkImage(profile.profileImage)
: null,
child: profile.profileImage.isEmpty
? SvgPicture.asset(
'assets/images/profile-default.svg')
: null),
);
}),
), ....
This CircleAvatar in the appBar shows the image only after the page is rebuilt. There's nothing else on the page except the appbar for now.
When we use ChangeNotifier, it provides two options to access the data. These are:
Read the data - You read the data, it doesn't act as a Stream or State and only one time. This is what you're doing in your case.
Advantage - Whenever the data is needed only one time, for example - Mathematical calculation, you use this.
Disadvantage - It doesn't listen to the changes and the data returned is static.
Watch the data - What you need. It provides the data in a state manner, wherever you access the data using Watch, it (or the widget in the data is used) will be updated whenever the underlying data is updated, even from other Screens/Widgets.
Advantage - The data result is dynamic and the widget is updated whenever the data is updated.
Disadvantage - In case where static data works, it is unnecessary plus it may affect any operations dependent on the data.
There are two ways to use Read and Watch.
The normal functions provided by the Author of the package
//For reading the data
var yourData = Provider.of<YourNotifier>(context, listen: false);
//For watching the data
var yourData = Provider.of<ProfileNotifier>(context, listen: true);
The extension functions on BuildContext provided by the Author:
//For reading the data
var yourData = context.read<YourNotifier>();
//For watching the data
var yourData = context.watch<YourNotifier>();
So, what you need to do is:
Change
ProfileModel profile =
Provider.of<ProfileNotifier>(context, listen: false).profile;
to
ProfileModel profile =
Provider.of<ProfileNotifier>(context, listen: true).profile;
//Or
ProfileModel profile = context.watch<ProfileNotifier>().profile;
Edit: Also, considering good UX, you can use a bool flag to update the UI whenever the data is loaded and if it's loading, show a CircularProgressIndicator.

Flutter type 'String' is not subtype of type 'FutureOr<Model>' , Response Json Without Key

I'm trying to call the service like this, I think something wrong over here because the "result" return Future and cant assign to String, or how to parse Future to String?
and MyApi return Json without Key Something like this
body
ReturnStatus
so I'm trying to store the response directly into the user var, but still not working
UI (Button Login)
login.loginProcess
? Center(child: CircularProgressIndicator())
: RaisedButton(
color: myPrimaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Login",
style: TextStyle(
color: Colors.black,
fontFamily: "NunitoSansBold")),
],
),
padding:
EdgeInsets.only(top: 16.0, bottom: 16.0),
onPressed: () {
print("clicked Button Login");
**login.authenticationUser(context);**
},
),
Service
class AuthService {
Future<User> authenticateUser(String id, String password) async {
var user;
try {
final resAuth = await http.post(
"${BaseUrl.auth}api/AuthenticateUser",
body: {"login": id, "password": password},
);
if (resAuth.statusCode == 200) {
user = resAuth.body;
}
} catch (e) {
return user;
}
return user;
}
}
Provider with ChangeNotifier (For handling the business logic and stuf)
authenticationUser(BuildContext context) async {
if (_controllerEmail.text != "" && _controllerPassword.text != "") {
loginProcess = true;
final ioc = new HttpClient();
ioc.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
//TODO StoredSharedPreference
SharedPreferences _preferences = await SharedPreferences.getInstance();
try {
AuthService authService = new AuthService();
var result = authService.authenticateUser(
_controllerEmail.text, _controllerPassword.text);
showSnackBar(result);
_preferences.setString("status", "seen");
} catch (e) {
print(e);
showSnackBar(result.toString());
}
loginProcess = false;
} else {
autoValidate = true;
}
}
Model
class User {
String status;
String id;
String userName;
User({
this.status,
this.id,
this.userName,
});
factory User.fromJson(Map<String, dynamic> json) =>
User(status: json["status"], id: json["id"], userName: json["userName"]);
Map<String, dynamic> toJson() =>
{"status": status, "id": id, "userName": userName};
}
=======UPDATE , Change the method authenticationUser (add await)=======
Service
class AuthService {
Future<User> authenticateUser(String id, String password) async {
var user;
try {
final resAuth = await http.post(
"${BaseUrl.auth}api/AuthenticateUser",
body: {"login": id, "password": password},
);
if (resAuth.statusCode == 200) {
user = resAuth.body;
}
} catch (e) {
return user;
// throw Exception(e.toString());
}
return user;
}
}
Provider
authenticationUser(BuildContext context) async {
if (_controllerEmail.text != "" && _controllerPassword.text != "") {
loginProcess = true;
final ioc = new HttpClient();
ioc.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
//TODO StoredSharedPreference
SharedPreferences _preferences = await SharedPreferences.getInstance();
try {
AuthService authService = new AuthService();
var result = await authService.authenticateUser(
_controllerEmail.text, _controllerPassword.text);
_preferences.setString("status", "seen");
showSnackBar(result.toString());
} catch (e) {
print(e);
/* showSnackBar(e.toString());*/
}
loginProcess = false;
} else {
autoValidate = true;
}
}
in catch (e) {
print(e);
value of e is
String' is not a subtype of type 'FutureOr<User>'
==Update Add Image Value from resAuth==
resAuthValue
You probably need to add await.
var result = await authService.authenticateUser(
_controllerEmail.text, _controllerPassword.text);
Edit
Change your authenticateUser method to this
Future authenticateUser(String id, String password) async {
var user;
try {
final resAuth = await http.post(
"${BaseUrl.auth}api/AuthenticateUser",
body: {"login": id, "password": password},
);
if (resAuth.statusCode == 200) {
return resAuth.body;
}
} catch (e) {
return false;
}
return false;
}
Change provider code as below
try {
AuthService authService = new AuthService();
var result = await authService.authenticateUser(
_controllerEmail.text, _controllerPassword.text);
_preferences.setString("status", "seen");
if(result!=false)
showSnackBar(result.toString());
} catch (e) {
print(e);
/* showSnackBar(e.toString());*/
}