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

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.

Related

How to request refresh token called once on multiple api calls

I have a function that refreshes a token if the previous API response returns an error code of 1000. However, when multiple API calls are made at the same time, it results in multiple refresh token requests. I want to ensure that the refresh token is only called once.
Here is my code
requestGet(String endPoint, Map<String, dynamic> params, [bool isUsingToken = false]) async {
String sign = getSign(timestamp + jsonEncode(params));
String deviceId = await SharedPrefsService().getDeviceId();
String token = await SharedPrefsService().getToken();;
final response = await httpGet(endPoint, params, sign, token, deviceId, isUsingToken);
dynamic result = response;
var isRenewed = await renewTokenIfNeeded(deviceId, result, endPoint);
if (isRenewed) {
token = await SharedPrefsService().getToken();
final renewedResponse = await httpGet(endPoint, params, sign, token, deviceId, isUsingToken);
result = renewedResponse;
}
return result;
}
Future<bool> renewTokenIfNeeded(String deviceId, result) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool renewingToken = prefs.getBool('renewingToken') ?? false;
if (result['error_code'] == '1000') {
prefs.setBool('renewingToken', true);
try {
if (renewingToken) {
return true;
}
var isRenewed = await requestRenewToken(deviceId);
if (isRenewed) {
prefs.setBool('renewingToken', false);
return true;
}
} finally {
prefs.setBool('renewingToken', false);
}
}
return false;
}
requestRenewToken(String deviceId) async {
var refresh = await AuthenticationService().refreshToken();
if (refresh.errorCode == '9999') {
SharedPrefsService().clearAllData();
return false; // then back to sign in
}
if (refresh.errorCode == '0000') {
SharedPrefsService().saveTokenData(refresh.token!, refresh.userName!, deviceId);
return true;
}
return false;
}
I have tried using synchronized and mutex packages, but they do not seem to work and I prefer to minimize the use of external packages. Can you please suggest a solution? Thank you!

how can i send data to server interval times

it's my code
class TrackingManager {
Location locate = new Location();
Future<Map<String, double>> trackOn() async {
LocationData getlocationdata = await locate.getLocation();
Map<String, double> locationdata = {};
locationdata['latitude'] = getlocationdata.latitude ?? 0;
locationdata['longitude'] = getlocationdata.longitude ?? 0;
return locationdata;
}
}
if controller(user) call TrackingManager and initialize object,
and use trackOn so Stores the location information of the current user.
class LocateApiRequest {
final _networkManager = NetworkManager();
final _trackingManager = TrackingManager();
Future transmit(String accessToken) async {
final locationdata = await _trackingManager.trackOn();
final response = await _networkManager
.send(ApiRequestFactory.transmit(locationdata, accessToken));
final statusCode = response.statusCode;
if (statusCode == 200) {
final responseBody = jsonDecode(response.body);
return responseBody;
}
throw statusCode;
}
}
and Send the data to the server by requesting a post
But this action is carried out only once.
Starting with when a user clicks a specific button, I want to do this on the server with a regular time (background, foreground status is not important)
how can i solve it?I've thought about using a repeat statement, but this doesn't seem reasonable
using location callback
https://pub.dev/packages/location
location.onLocationChanged.listen((LocationData currentLocation) {
// call network api
// Use current location
});

Flutter await for another method complete

I want to check if new update is available for my application or not. if update is available redirect user to UpdateScreen and if update is not available get the user info and redirect to HomeScreen
_check() async {
await _checkForUpdate();
await _getUserData(token);
}
_checkForUpdate() async {
print('check for update');
var url = Uri.parse(Endpoints.mainData);
var response = await http.get(url);
var jsonResponse = convert.jsonDecode(response.body);
var data = jsonResponse['data'];
int lastVersionCode = data['lastVersionCode'];
if(lastVersionCode > Data.versionCode){
redirectToScreen(context, UpdateScreen());
}
}
_getUserData(String token) async {
print('get user data');
var url = Uri.parse(Endpoints.accountInfo + '/?token=' + token);
var response = await http.get(url);
var jsonResponse = convert.jsonDecode(response.body);
var data = jsonResponse['data'];
//setup user data in my app
redirectToScreen(context, HomeScreen());
When I run my application two methods( _checkForUpdate, _getUserData) get fired and in output I the get following message that i printed:
check for update
get user data
and i see Update screen for 1 second and then user is redirect to HomeScreen.
i want to skip running the other codes after _checkForUpdate redirect user to UpdateScreen
return a bool whether there is an update available and use it to skip other methods:
_check() async {
bool needsUpdate = await _checkForUpdate();
if (!needsUpdate)
await _getUserData(token);
}
Future<bool> _checkForUpdate() async {
print('check for update');
var url = Uri.parse(Endpoints.mainData);
var response = await http.get(url);
var jsonResponse = convert.jsonDecode(response.body);
var data = jsonResponse['data'];
int lastVersionCode = data['lastVersionCode'];
if (lastVersionCode > Data.versionCode) {
redirectToScreen(context, UpdateScreen());
return true;
}
return false;
}

Flutter mockito post always return null

I'm trying to test my code that makes a post to login from an API using a mocked http client, but instead of returning what I asked for, it returns null, I did the same test but changing the endpoint and method to GET and it worked perfectly. I'm currently using flutter's http to make the requests, but I've already tested it with Dio and the result was the same, below is my code
Future<String> signIn(String email, String password) async {
final Map<String, dynamic> body = {"email": email, "password": password};
final String url = url_base + Urls.auth_login;
final Map<String, String> customHeader = {
"Content-type": "application/json",
};
String returnCode;
try {
var x = jsonEncode(body);
http.Response response = await client.post(Uri.parse(url), body: x, headers: customHeader);
var parsedJson = json.decode(response.data);
if (parsedJson.containsKey("token")) {
returnCode = parsedJson["token"];
} else {
returnCode = parsedJson["non_field_errors"][0];
}
}catch (e) {
throw ServerException();
}
if (returnCode == null) {
throw ServerException();
} else {
return returnCode;
}
}
and the test case:
class ClientMock extends Mock implements http.Client {}
void main() {
RemoteData remoteData;
group('Test signIn', () {
test('Login with email and wrong password', () async {
final clientMock = ClientMock();
remoteData = RemoteData(client: clientMock);
String jsonMockResponse =
'{non_field_errors: [Unable to log in with provided credentials.]}';
when(clientMock.post(any))
.thenAnswer((_) async => http.Response(jsonMockResponse, 400));
String loginReturn =
await remoteData.signIn('test#email.com', 'password123');
expect(loginReturn,throwsA(const TypeMatcher<ServerException>()));
});
}
I've already tested some things like changing 'any' for exactly the same thing the real function gets and it didn't work either.
The actual test return 'Instance of 'ServerException'', an in debug mode i could see that the return is null, and the last if is the one who throws this exception.

Question mark converted to %3F in URI

I'm working on a project and I'm trying to get information from an API. When I write the link it doesn't detect the character "?" and it substitutes this char for "%3F" so I can't access to the API.
final String _charactersUrl = '/api/character/?page=2';
I get status code 500 from the API:
https://rickandmortyapi.com/api/character/%3Fpage=3
The class that gets information from the API
class Api {
final String _baseUrl = 'rickandmortyapi.com';
final String _charactersUrl = '/api/character/?page=2';
final String _charactersJsonKey = 'results';
final HttpClient _httpClient = HttpClient();
Future<List<Character>> getCharacters() async {
final uri = Uri.https(_baseUrl, _charactersUrl);
final response = await _getJson(uri);
if (response == null || response[_charactersJsonKey] == null) {
print('Api.getCharacters(): Error while retrieving characters');
return null;
}
return _convert(response[_charactersJsonKey]);
}
Future<Map<String, dynamic>> _getJson(Uri uri) async {
try {
final request = await _httpClient.getUrl(uri);
final response = await request.close();
if (response.statusCode != HttpStatus.OK) {
print('Api._getJson($uri) status code is ${response.statusCode}');
return null;
}
final responseBody = await response.transform(utf8.decoder).join();
return json.decode(responseBody);
} on Exception catch (e) {
print('Api._getJson($uri) exception thrown: $e');
return null;
}
}
List<Character> _convert(List charactersJson) {
List<Character> characters = <Character>[];
charactersJson.forEach((character) {
characters.add(Character.fromJson(character));
});
return characters;
}
}
I would be very grateful if someone could help me. Thanks!
The Uri class expects you to use the Uri.https constructor differently.
The third positional parameter is queryParameters, which you should use instead of passing your query parameters to the unencodedPath:
final String _baseUrl = 'rickandmortyapi.com';
final String _charactersPath = '/api/character/';
final Map<String, String> _queryParameters = <String, String>{
'page': '2',
};
Future<List<Character>> getCharacters() async {
final uri = Uri.https(_baseUrl, _charactersPath, _queryParameters);
...