Flutter/Dio Hangs when in an isolate - flutter

So I'm trying to sync my database in an isolate, since UI doesn't care about the sync.
Database _backgroundConnection(File dbFile) {
final database = VmDatabase(dbFile);
var connection = DatabaseConnection.fromExecutor(database);
return Database.connect(connection);
}
Dio _getDio(String urlBase, String authToken) {
var dio = Dio();
dio.options.baseUrl = urlBase;
dio.options.headers = {"Authorization": authToken};
dio.options.connectTimeout = 5000; //5s
dio.options.receiveTimeout = 3000;
return dio;
}
Future<void> _syncUser(UserDao dao, DateTime lastSynced, Dio dio) async {
var users = await dao.getUser();
if (users.isEmpty) {
return;
}
var user = users[0];
var userDto = UserDto.fromUser(user);
//if (user.lastUpdated.isAfter(lastSynced)) {
var result = await dio.put("/api/v1/user", data: userDto.toJson());
print(result);
//}
}
Future<void> _startSync(Map<String, dynamic> info) async {
String dbPath = info["dbPath"];
DateTime lastSynced = info["lastSynced"];
String urlBase = info["url"];
String token = info["token"];
var dio = _getDio(urlBase, token);
File dbFile = File(dbPath);
var database = _backgroundConnection(dbFile);
_syncUser(UserDao.fromDatabase(database), lastSynced, dio);
}
class DatabaseSync {
Future<void> syncDatabase() async {
final dbFolder = await getApplicationDocumentsDirectory();
var envVars = GetIt.instance.get<EnvVariables>();
var authRepo = GetIt.instance.get<FirebaseAuthRepository>();
compute(_startSync, {
"lastSynced": DateTime.now(),
"dbPath": p.join(dbFolder.path, 'db.sqlite'),
"url": envVars.url,
"token": await authRepo.getAuthToken(),
});
}
}
I've removed the compute portion, and just ran the code, and it works. It makes the request. However when I put a breakpoint at just before the request, it hits, but when I put it on the print statement, it never hits. Nothing is ever written to the console. This happens on both iOS and android. I tried changing the urlBase from my localhost server to https://google.com to make sure it would hit something https (since iOS gets picky) and still nothing.

So this is kinda strange but calling await _syncUser(...) fixed the problem.

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!

Flutter await does not await until return of function

I tried to find an answer but my problem is still there.
In my asynchronous upload function I return at the and the generated name of the image, which I want to use to make my database request.
This is my upload function:
Future<String> upload(File imageFile) async {
var stream =
new http.ByteStream(DelegatingStream.typed(imageFile.openRead()));
// get file length
var length = await imageFile.length();
var uri = Uri.parse("http://localhost:8080/upload");
var request = new http.MultipartRequest("POST", uri);
var multipartFile = new http.MultipartFile('file', stream, length,
filename: basename(imageFile.path));
request.files.add(multipartFile);
var response = await request.send();
print(response.statusCode);
var createdFileName = "";
response.stream.transform(utf8.decoder).listen((value) {
createdFileName = value;
print(createdFileName);
});
return createdFileName;
}
I call it like this:
List createdFileNames = [];
for (var e in imagefiles) {
createdFileNames.add(await upload(File(e)));
}
I don't know why, but the createdFileNames are ["",""], but the upload gives as result the right name. In debug mode I can see, that the loop does not wait until the upload has finished.
Do you have any suggestions?
Thank you very much!
response.stream.transform(utf8.decoder).listen((value) {
createdFileName = value;
print(createdFileName);
});
This part in your function is asynchronous, it uses a callback.
But you don't wait for it to finish in any form. You just continue to return the createdFileName, that by that time most likely has not been filled.
I don't know what your stream looks like, if you only need the first value, you could await that instead of listening:
createdFileName = await response.stream.transform(utf8.decoder).first;
Replace
response.stream.transform(utf8.decoder).listen((value) {
createdFileName = value;
print(createdFileName);
});
with
createdFileName=await response.stream.bytesToString();
change code
for (var e in imagefiles) {
upload(File(e)).then((value) => createdFileNames.add(value));
}

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;
}

TimeStamps for DateTime and Time siche Epoch Dart?

How to create timestamps in dart? I have found many solution but non of them works. I need to put in get-request the timestamps. Server takes timestamps in time siche epoch format. I was trying this:
_asyncMethod() async {
var url =
"https://link/cameras/snapshot/$formatDate"; // <-- 1
var response = await http.get(Uri.parse(url), headers: {
'cookie': cookies,
'login': userName,
'password': password,
'key': apiKey
});
print(response.body);
var documentDirectory = await getApplicationDocumentsDirectory();
var firstPath = documentDirectory.path + "/image";
var filePathAndName = documentDirectory.path + 'image/png';
await Directory(firstPath).create(recursive: true);
File file2 = File(filePathAndName);
file2.writeAsBytesSync(response.bodyBytes);
setState(() {
imageData = filePathAndName;
dataLoaded = true;
});
}
String imageData;
bool dataLoaded = false;
String formatDate(int milliseconds) {
final template = DateFormat('yyyy-MM-dd');
return template.format(DateTime.fromMillisecondsSinceEpoch(milliseconds));
}
but the answer from the server is
{"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"|3fc1bda-4c3086a677e6762a.","er
rors":{"timestamp":["The value 'Closure: (int) => String from Function 'formatDate':.' is not valid."]}}
I understand that the data is not correct but I can't understand how to solve it, because it seems like code for timestamps is correct

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.