I am using provider package for state management system...I am trying to update value using proxy provider but my value is updating after calling api but I wanted to to update value before api call following is my main.dart where I am using proxy provider and trying to get value from change notifier
void main() {
Provider.debugCheckInvalidValueType = null;
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
// final auth = Auth();
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => Auth(),
),
ProxyProvider<Auth, ApiCalls>(
//create: (_) => ApiCalls(),
update: (_, auth, __) => ApiCalls(auth.token)),
//ApiCalls(auth.token)),
ChangeNotifierProvider(
create: (_) => FlutterFunctions(),
),
],
child: Consumer<Auth>(
builder: (context, value, _) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.red,
),
home: value.isAuth
? const AdminScreen()
: FutureBuilder(
future: value.tryAutoLogin(),
builder: (context, snapshot) =>
snapshot.connectionState == ConnectionState.waiting
? CircularProgressIndicator()
: const AdminLoginScreen(),
following is my Auth class with change notifier where I am updating value
class Auth extends ChangeNotifier {
String? accessToken;
DateTime? accessTokenExpiryDate;
String? get token {
if (accessTokenExpiryDate != null &&
accessTokenExpiryDate!.isAfter(DateTime.now()) &&
accessToken != null) {
return accessToken;
} else if (accessTokenExpiryDate!.isBefore(DateTime.now())) {
return accessToken;
}
return null;
}
Future<void> restoreAccessToken() async {
print('restoreAccessToken started');
//print(token);
final url = '${Ninecabsapi().urlHost}${Ninecabsapi().login}/$sessionId';
var response = await http.patch(
Uri.parse(url),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': accessToken!
},
body: json.encode(
{"refresh_token": refreshtoken},
),
);
var userDetails = json.decode(response.body);
if (response.statusCode == 401) {
print(userDetails['messages']);
}
sessionId = userDetails['data']['session_id'];
accessToken = userDetails['data']['access_token'];
accessTokenExpiryDate = DateTime.now().add(
Duration(seconds: userDetails['data']['access_token_expiry']),
);
refreshToken = userDetails['data']['refresh_token'];
refreshTokenExpiryDate = DateTime.now().add(
Duration(seconds: userDetails['data']['refresh_token_expiry']),
);
final userData = json.encode({
'sessionId': sessionId,
'refreshToken': refreshToken,
'refreshExpiry': refreshTokenExpiryDate!.toIso8601String(),
'accessToken': accessToken,
'accessTokenExpiry': accessTokenExpiryDate!.toIso8601String()
});
//print(userDetails);
notifyListeners();
final prefs = await SharedPreferences.getInstance();
prefs.setString('userData', userData);
print("this is from restoreAcessToken :$userDetails");
final extractData =
json.decode(prefs.getString('userData')!) as Map<String, dynamic>;
print('restore access token: ${extractData['accessToken']}');
reset();
}
following is my ApiCalls class where I want to use updated token when retry
class ApiCalls extends ChangeNotifier {
final String? token;
ApiCalls(this.token);
Future<void> postVehicles(BuildContext context, String vehicleType) async {
print('postVehicles:$token');
final prefs = await SharedPreferences.getInstance();
final extractData =
json.decode(prefs.getString('userData')!) as Map<String, dynamic>;
var data = {"vehicletype": vehicleType, "filename": vehicleType};
Map<String, String> obj = {"attributes": json.encode(data).toString()};
var flutterFunctions =
Provider.of<FlutterFunctions>(context, listen: false);
final url = Ninecabsapi().urlHost + Ninecabsapi().getvehicle;
try {
loading();
var response = http.MultipartRequest("POST", Uri.parse(url))
..files.add(await http.MultipartFile.fromPath(
"imagefile", flutterFunctions.imageFile!.path,
contentType: MediaType("image", "jpg")))
//..headers['Authorization'] = token!
..fields.addAll(obj);
final client = RetryClient(
http.Client(),
retries: 2,
when: (response) {
return response.statusCode == 401 ? true : false;
},
onRetry: (req, res, retryCount) async {
//print('retry started $token');
if (retryCount == 0 && res?.statusCode == 401) {
// Only this block can run (once) until done
await Provider.of<Auth>(context, listen: false)
.restoreAccessToken();
req.headers['Authorization'] = token!;
// extractData['accessToken']
//token = authProvider.token;
print('retry started ${extractData['accessToken']}');
//req.headers.clear();
}
},
);
final send = await client.send(response);
final res = await http.Response.fromStream(send);
switch (res.statusCode) {
case 201:
Future.delayed(Duration.zero)
.then((value) => showsnackbar(context, "Vehicle type inserted"));
}
var messages = json.decode(res.body);
print("this is from sample $messages");
loading();
in following image you can see my token is updated in ui here my token is updated following is the response from the restoreaccesswoken method and you can see I am not Abel to use updated token in headers and I am also printing the token in print statement it is not printing updated token you can see my response here
I had found the solution after 20 days but I don't know the following method is right way to do or not as I understood actually the problem is that notifyListeners schedules the notification for the next frame. Internally, what notifyListeners simply does is to mark the widget as dirty (markNeedsBuild) so the listeners get rebuilt on the next frame only. We can say that notifyListeners is asynchronous.
So, even though you await Auth.restoreAccessToken in the ApiCalls.sample, only in the next frame does the call to ApiCalls.updates occurs. But this await is longer done and the token is still the same as before for the retry.
To fix it I just return the token from the Auth.restoreAccessToken in a Future. like the below code snippet:
Future restoreAccessToken() async {
print('restoreAccessToken started');
//print(token);
final url = '${Ninecabsapi().urlHost}${Ninecabsapi().login}/$sessionId';
var response = await http.patch(
Uri.parse(url),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': accessToken!
},
body: json.encode(
{"refresh_token": refreshtoken},
),
);
var userDetails = json.decode(response.body);
if (response.statusCode == 401) {
print(userDetails['messages']);
}
sessionId = userDetails['data']['session_id'];
accessToken = userDetails['data']['access_token'];
accessTokenExpiryDate = DateTime.now().add(
Duration(seconds: userDetails['data']['access_token_expiry']),
);
refreshToken = userDetails['data']['refresh_token'];
refreshTokenExpiryDate = DateTime.now().add(
Duration(seconds: userDetails['data']['refresh_token_expiry']),
);
final userData = json.encode({
'sessionId': sessionId,
'refreshToken': refreshToken,
'refreshExpiry': refreshTokenExpiryDate!.toIso8601String(),
'accessToken': accessToken,
'accessTokenExpiry': accessTokenExpiryDate!.toIso8601String()
});
//print(userDetails);
notifyListeners();
final prefs = await SharedPreferences.getInstance();
prefs.setString('userData', userData);
print("this is from restoreAcessToken :$userDetails");
final extractData =
json.decode(prefs.getString('userData')!) as Map<String, dynamic>;
print('restore access token: ${extractData['accessToken']}');
reset();
return accessToken;
}
and I had saved this in a variable and used it like below
if (retryCount == 0 && res?.statusCode == 401) {
var acccess = await Provider.of<Auth>(context, listen: false)
.restoreAccessToken();
req.headers['Authorization'] = acccess.toString();