Search code examples
flutterflutter-change-notifier

how to update variable using changeNotifierProxyProvider?


I am trying to update my header when I retry my API upon 401 response I am able to retry but my header is taking the previous value but not updated value

following is my changeNotifierProxyProvider

 ChangeNotifierProxyProvider<Auth, ApiCalls>(
            create: (_) => ApiCalls(),
            update: (context, auth, previous) => previous!..updates(auth)),

following is my auth class where I am updating my token

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 {
    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()
    });


    notifyListeners();
    final prefs = await SharedPreferences.getInstance();

    prefs.setString('userData', userData);
    reset();
  }

}

following is my class where I wanted to call an api with updated token

class ApiCalls extends ChangeNotifier {
String? token;
void updates(Auth token) {
    this.token = token.token;
  }
Future<void> sample(BuildContext context, String s) async {
    print('sample:$token');

    var data = {"s": s, "filename":s};
    Map<String, String> obj = {"attributes": json.encode(data).toString()};

    var flutterFunctions =
        Provider.of<FlutterFunctions>(context, listen: false);
    final url =su().urlHost + su().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: 1,
        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']

            print('retry started ${req.headers['Authorization']}');
            //req.headers.clear();

          }
        },
      );
      final send = await client.send(response);
      final res = await http.Response.fromStream(send);
      
      var messages = json.decode(res.body);

      notifyListeners();
    } catch (e) {
      print(e);
    }
  }
}

I had tried different methods but no one worked I had tried to call token directly from shared preference but it dint work I am attaching response please check you can see invalid token in my response


Solution

  • 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 just return the token from the Auth.restoreAccessToken in a Future. Something like the below code snippet:

    token = await Provider.of<Auth>(context, listen: false)
        .restoreAccessToken();
    

    And there is no need to listen to Auth updates anymore.