Pass the user id to an http parameter - flutter

Good morning, I have a login that returns me the UserID from the server, I store it in an instance of Shared Preferences, and I want to use it as a parameter so that in the main screen, it shows five recent records, also brought from the database.
I attach the code of the login, I emphasize that I was trying to pass some arguments by means of the routes, which are the UserID and the Role of the user, to show him a screen in special.
Future<void> login(email, password) async{
try{
var url = 'serverurl';
var response = await http.post(Uri.parse(url),
body:
{
'Email' : email,
'Password' : password
}).timeout(const Duration(seconds: 30));
var datos = jsonDecode(response.body);
print(datos);
if(response.body != '0'){
guardarDatos(datos['UserID'], datos['Role']);
if('Role' == 'admin'){
Navigator.pushNamed(context, '/AdminPage', arguments: {'UserID':UserId, 'Role': Role});
} else {
Navigator.pushNamed(context, '/UserPage', arguments: {'UserID': UserId, 'Role': Role});
}
} else{
//Cuadro de diálogo que indica que los datos son incorrectos.
showDialog(
context: context,
builder: (BuildContext context) {
return const AlertLogin();
});
print('Usuario Incorrecto');
}
} on TimeoutException catch(e){
print('Tiempo de proceso excedido.');
} on Error {
print('http error.');
}
}
The following, is the code for the main screen, where I plan to pass the user id as a parameter in the URL of the http.get, to get the user records, for example number 1.
//HTTP Request
Future<List<Record>> fetchRecord() async {
//final response = await http.get(Uri.parse('https://e5ac-45-65-15257.ngrok.io/get/fiverecords/1')); Este es estático.
final response = await http
.get(Uri.parse('https://e5ac-45-65-152-57.ngrok.io/get/fiverecords/'));
if (response.statusCode == 200) {
final parsed = json.decode(response.body).cast<Map<dynamic, dynamic>>();
return parsed.map<Record>((json) => Record.fromMap(json)).toList();
} else {
throw Exception('Failed to load records.');
}
}

So in your code for main screen, I am assuming you are asking how to retrieve your arguments passed through named routes. Here's what you need to do:
Define a variable:
final user = ModalRoute.of(context)!.settings.arguments;
You can use this user variable to access the UserID and Email just like this:
print('User Email: ${user.UserID}');
print('User Email: ${user.Role}');
Its like calling a map's value using its key.
So this is how your final code might look like:
Future<List<Record>> fetchRecord() async {
final user = ModalRoute.of(context)!.settings.arguments; // You can use this variable directly in your links
//final response = await http.get(Uri.parse('https://e5ac-45-65-15257.ngrok.io/get/fiverecords/1')); Este es estático.
final response = await http
.get(Uri.parse('https://e5ac-45-65-152-57.ngrok.io/get/fiverecords/'));
if (response.statusCode == 200) {
final parsed = json.decode(response.body).cast<Map<dynamic, dynamic>>();
return parsed.map<Record>((json) => Record.fromMap(json)).toList();
} else {
throw Exception('Failed to load records.');
}
}
Hope that solves your problem. Feel free to clear up any confusions.

Related

how to redirect the user to the login page if the token has expired

hello I have a case where when the user token expires the user does not switch to the loginPage page, even though I have set it here.
how do i solve this problem thanks.
i set it on splashscreen if token is not null then go to main page and if token is null then go to login page.
but when the token expires it still remains on the main page
Future<void> toLogin() async {
Timer(
const Duration(seconds: 3),
() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String? token = prefs.getString(Constant.token);
Navigator.pushReplacementNamed(
context,
token != null ? AppRoute.mainRoute : AppRoute.loginRoute,
arguments: token,
);
},
);
}
and function when user login
CustomButtonFilled(
title: 'Login',
onPressed: () async {
final prefs =
await SharedPreferences.getInstance();
prefs.setString(Constant.token, '');
if (nimController.text.isEmpty ||
passwordController.text.isEmpty) {
showError('NIM/Password harus diisi');
} else {
setState(() {
isLoading = true;
});
User? user = await userProvider.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,
);
}
}
},
),
and this call api
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'];
//Ini mulai nyimpen 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();
}
}
you can just make your own HTTP client using Dio and add Interceptor to automatically regenerate idToken if expired using the refreshToken given.
Http client gives an error if the refreshToken also gets expired.
In that case, just navigate to the login screen.
Full code for adding interceptor and making own HTTP client is given below
import 'package:dio/dio.dart';
import '../utils/shared_preference.dart';
class Api {
static Dio? _client;
static Dio clientInstance() {
if (_client == null) {
_client = Dio();
_client!.interceptors
.add(InterceptorsWrapper(onRequest: (options, handler) async {
if (!options.path.contains('http')) {
options.path = 'your-server' + options.path;
}
options.headers['Authorization'] =
'Bearer ${PreferenceUtils.getString('IdToken')}';
return handler.next(options);
}, onError: (DioError error, handler) async {
if ((error.response?.statusCode == 401 &&
error.response?.data['message'] == "Invalid JWT")) {
if (PreferenceUtils.exists('refreshToken')) {
await _refreshToken();
return handler.resolve(await _retry(error.requestOptions));
}
}
return handler.next(error);
}));
}
return _client!;
}
static Future<void> _refreshToken() async {
final refreshToken = PreferenceUtils.getString('refreshToken');
final response = await _client!
.post('/auth/refresh', data: {'refreshToken': refreshToken});
if (response.statusCode == 201) {
// successfully got the new access token
PreferenceUtils.setString('accessToken', response.data);
} else {
// refresh token is wrong so log out user.
PreferenceUtils.deleteAll();
}
}
static Future<Response<dynamic>> _retry(RequestOptions requestOptions) async {
final options = Options(
method: requestOptions.method,
headers: requestOptions.headers,
);
return _client!.request<dynamic>(requestOptions.path,
data: requestOptions.data,
queryParameters: requestOptions.queryParameters,
options: options);
}
}
Dio client = Api.clientInstance();
var resposne = (hit any request);
if(error in response is 401){
//it is sure that 401 is because of expired refresh token as we
//already handled idTokoen expiry case in 401 error while
//adding interceptor.
navigate to login screen for logging in again.
}
Please accept the solution if it solves your problem.
If your session expire feature has some predefine interval or logic than you have to implement it in splash screen and based on that you can navigate user further. Otherwise you want to handle it in API response only you have add condition for statusCode 401.
checkSessionExpire(BuildContext context)
if (response.statusCode == 200) {
//SuccessWork
} else if (response.statusCode == 401) {
//SessionExpire
} else {
return null
}
}

Getting response after executing entire code

I am trying to get some information from a database which I do get eventually, but my if conditions are checked first before getting the data and prints the data after completing the checking of the if conditions, even though I have used await to wait for the data to arrive and then continue.
Future reg() async {
getData().then((value) async {
print(value["serverIP"]);
print(value["port"]);
print(value["passwordMain"]);
Dio dio = Dio();
Response response = await dio.get(
'http://${value["serverIP"]}:${value["port"]}/${value["passwordMain"]}/reg/${controllerEmail.text}/${controllerPassword.text}/${controllerUsername.text}');
print(response.data);
return response;
});
ElevatedButton(
onPressed: () async {
if (!controllerEmail.text.endsWith("#gmail.com") &
!controllerEmail.text.endsWith("#gmail.com ") &
!controllerEmail.text.endsWith("#email.com") &
!controllerEmail.text.endsWith("#email.com ") &
!controllerEmail.text.endsWith("#hotmail.com") &
!controllerEmail.text.endsWith("#hotmail.com ")) {
if (controllerEmail.text.endsWith(" ")) {
controllerEmail.text =
controllerEmail.text.replaceAll(" ", "");
}
showErrorDialog(context, 'Unknown Email Address',
'Try Changing the Email to one of the Providers we Support.');
} else if ((controllerPassword.text !=
controllerRePassword.text) |
controllerPassword.text.isEmpty) {
showErrorDialog(context, 'Passwords Do not Match/Empty',
'Please Re-Type your Passwords as they do not Match, or are Empty');
} else {
var response = await reg();
if (response != null) {
if (response.data == "done") {
showErrorDialog(context, "Done",
"Your Account has been Created, please Log in");
} else if (response.data == "key") {
showErrorDialog(
context,
"Incorrect API Key/Main Server Password",
"The API Key (Main Server Password) is Incorrect. Kindly, Ensure the Key.");
} else if (response.data == "email") {
showErrorDialog(context, "Account Already Exists",
"An Account already exists with this Email");
} else if (response.data == "username") {
showErrorDialog(context, "Account Already Exists",
"An Account already exists with this Username");
}
}
}
},
child: const Text("Sign Up"),
),
You're missing a return in your reg() function. Add one before your getData() call like this:
Future reg() async {
try {
return getData().then((value) async {
Dio dio = Dio();
Response response = await dio.get(
'http://${value["serverIP"]}:${value["port"]}/${value["passwordMain"]}/reg/${controllerEmail.text}/${controllerPassword.text}/${controllerUsername.text}');
return response;
});
} catch (e) {}
}
Now the function should be properly awaited because it is now returning a promise instead of nothing.
Alternatively, you might prefer to rewrite it using more async/await for easier comprehension, like this:
Future reg() async {
try {
const value = await getData();
Dio dio = Dio();
Response response = await dio.get(
'http://${value["serverIP"]}:${value["port"]}/${value["passwordMain"]}/reg/${controllerEmail.text}/${controllerPassword.text}/${controllerUsername.text}');
return response;
} catch (e) {}
}
Credit: https://stackoverflow.com/a/74238420/13909069

Save local string from a function inside shared preferences and use it in other pages

I wish to save the userid string that am getting from a function that parses and decodes JWT token , and be able to use it in other pages in my Flutter app . I tried to save it inside shared preferences but doesn't seem to be working .This is my function and how I used shared preferences
String userName;
dynamic authenticator;
String _decodeBase64(String str) {
String output = str.replaceAll('-', '+').replaceAll('_', '/');
switch (output.length % 4) {
case 0:
break;
case 2:
output += '==';
break;
case 3:
output += '=';
break;
default:
throw Exception('Illegal base64url string!"');
}
return utf8.decode(base64Url.decode(output));
}
String _userid = '';
Map<String, dynamic> parseJwt(String token) {
final parts = token.split('.');
if (parts.length != 3) {
throw Exception('invalid token');
}
final payload = _decodeBase64(parts[1]);
final payloadMap = json.decode(payload);
if (payloadMap is! Map<String, dynamic>) {
throw Exception('invalid payload');
}
print(payload);
addStringToSF() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
_userid = payloadMap['user_id'];
prefs.setString('stringValue',_userid );
}
//print(payloadMap['user_id']);
return payloadMap;
}
getStringValuesSF() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
//Return String
String _userid = prefs.getString('userid');
print (_userid);
return _userid;
}
#override
void initState() {
super.initState();
getStringValuesSF();
}
authenticate() async {
// keyclock url : key-clock-url : example : http://localhost:8080
// my realm : name of your real.m
var uri = Uri.parse('http://169.254.105.22:8080/auth/realms/Clients');
// your client id
var clientId = 'helium';
var scopes = List<String>.of(['openid', 'profile']);
var port = 8080;
var issuer = await Issuer.discover(uri);
var client = new Client(issuer, clientId);
print(issuer.metadata);
urlLauncher(String url) async {
if (await canLaunch(url)) {
await launch(url, forceWebView: true);
} else {
throw 'Could not launch $url';
}
}
authenticator = new Authenticator(
client,
scopes: scopes,
port: port,
urlLancher: urlLauncher,
);
var c = await authenticator.authorize();
closeWebView();
var token = await c.getTokenResponse();
var userInformation = await c.getUserInfo();
setState(() {
userAccessToken = token.accessToken;
userName = userInformation.preferredUsername;
});
//print(token);
//return token;
parseJwt(userAccessToken);
}
I wish to use the userid variable here instead of the static string (id) am passing , in a way it dynamically reads the value from the function then use it inside the link to show the user's info :
final response = await http.get('http://169.254.105.22:8093/user/v1/users/d374169b-c61f-4a5a-b00a-2a2a8d9c4e19');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return User.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load user');
}
}
The second function is in another page (profile page), if anyone knows how I can save the userid from the function , then pass to another page (using sp or any other way) please don't hesitate to help thank you in advance
In my experience, when it comes to simple key/value storage, GetStorage is easier to use and less finicky than Shared Preferences. Try this:
Put this in your main before running your app.
await GetStorage.init();
Then your addStringToSF method would look like this:
addStringToSF() async {
final box = GetStorage();
_userid = payloadMap['user_id'];
box.write('stringValue', _userid);
}
Then from anywhere in your app access the stored value by
final box = GetStorage();
final newString = box.read('stringValue');
That should work for you.

How do I return to the user stream in flutter

I'm having an issue return a Stream to a StreamBuilder widget in a flutter. I'm trying to access a custom class that is stored token.
class User {
String token;
User({this.token});
}
===============================
class AuthService {
String url = 'https://reqres.in/api/login';
String token = '';
// {
// "email": "eve.holt#reqres.in",
// "password": "cityslicka"
// }
Map data;
Future signIn(String email, String password) async {
final response =
await post(url, body: {'email': email, 'password': password});
data = jsonDecode(response.body);
print(data['token']);
token = data['token'];
_userFromDatabaseUser(data);
return data;
}
//create user obj based on the database user
User _userFromDatabaseUser(Map user) {
return user != null ? User(token: user['token']) : null;
}
//user stream for provider
Stream<User> get user {
return .................. ;
}
You could use a stream controller:
class AuthService {
final String url = 'https://reqres.in/api/login';
final controller = StreamController<User>();
Future<User> signIn(String email, String password) async {
final response = await post(url, body: {'email': email, 'password': password});
final data = jsonDecode(response.body);
final user = _userFromDatabaseUser(data);
controller.add(user);
return user;
}
//create user obj based on the database user
User _userFromDatabaseUser(Map user) {
return user != null ? User(token: user['token']) : null;
}
//user stream for provider
Stream<User> get user {
return controller.stream;
}
Please note that this approach is a simplistic example that has some flaws, you should read up on it in the documentation.
If you use this for the purpose you describe, you may want to look into the bloc pattern and it's implementation as flutter-bloc. It might seem easier to do the user in this way by hand, but once you reach the point where you have multiple of those streams, you may want a more structured approach.
You can use
Stream<User> get user async*{
yield .................. ;
}
you can use yield keyword when you want to return stream object.
2nd way you can use a stream controller. You can add value in controller and
listen wherever you want to listen in your app there is no need to return stream

How to get content from json with SharedPreferences- Flutter/Dart

How can I get only content from this json:
{
id: 2,
profileImage: {
id: 1,
fileId: "e8ec429d-1e09-48c9-9ec8-6e61c1177324.jpg",
content: "http://localhost/file/e8ec429d-1e09-48c9-9ec8-6e61c1177324.jpg"
}
}
I want to get him from response after my login request using sharedpreferences:
Future<bool> makeLoginRequest(String email, password) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
Map data = {
'email': emailController.text,
'password': passwordController.text
};
var jsonResponse;
var url = 'http://10.0.2.2:80/user/login';
var response = await http.post(url, body: data);
if (response.statusCode == 200) {
_isLoading = false;
jsonResponse = json.decode(response.body);
setState((){
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (BuildContext context) => NavBar()),
(Route<dynamic> route) => false);
});
return true;
} else {
return false;
}
}
thanks for any help :)
If you just need the content String you can do something like:
String content = jsonResponse['profileImage']['content'];
However a couple of recommendations, I would separate the parsing logic from the networking logic and in general try to have Model objects to map the models you receive from your services (network/storage) instead of extracting directly its content. For a trivial use case like this it's fine but for more complex scenarios it will allow you to write cleaner and more testable code.