0

I am trying to persist the user authentication state of an already working auth system using flutter_bloc and Nodejs, by storing my successfully retrieved and generated JWTs. I have chosen flutter_secure_storage (FSS), for brevity as my key-value cache system.

Whenever I try to read or write data with FSS, I keep running into these kind of errors:

Unhandled Exception: NoSuchMethodError: The method 'write/read/delete' was called on null.

In the case of writing my JWTs to FSS, here's an example snippet:

@override
Future<UserModel> loginUser(Map<String, dynamic> body) async {
final userModel = await dataSource.loginUser(body);
var userId = userModel.id;
Future.delayed(Duration(seconds: 2), () async {
  await storage.write(key: APIConstants.USER_ID_KEY, value: userId);
});
return userModel;

}

Have even tried delaying the write operation to see if the value will save, it only logs me in cos of proper credentials and then throws an error, not crashing though, that's great.

Here's the data source class snippet for login user:

abstract class UserRemoteDataSource {
  Future<UserModel> loginUser(Map<String, dynamic> body);
  Future<UserModel> registerUser(Map<String, dynamic> body);
  // Future<User> sendOTP();
  // Future<User> verifyOTP();
  Future<UserModel> getCurrentUser(String userId);
  Future<void> logOut();
}

class UserRemoteDataSourceImpl implements UserRemoteDataSource {

  final APIClient client;
  var storage = FlutterSecureStorage();
  UserRepository repository;
  UserRemoteDataSourceImpl({@required this.client});

  @override
  Future<UserModel> loginUser(Map<String, dynamic> body) async {
    final response = await client.postAuthData('login', body);
    final userResponseModel = UserResponseModel.fromJSON(response);

  final accessToken = userResponseModel.accessToken;
  final refreshToken = userResponseModel.refreshToken;

  storage = FlutterSecureStorage();
  await storage.write(key: APIConstants.ACCESS_TOKEN_KEY, value: accessToken)
        .then((value) => print('AccessToken Written to FSS')); // this callback from future logs, so I know that the tokens have been saved
  await storage.write(key: APIConstants.REFRESH_TOKEN_KEY, value: refreshToken)
        .then((value) => print('RefreshToken Written to FSS'));

  return userResponseModel.user;
}

 @override
 Future<UserModel> registerUser(Map<String, dynamic> body) async {
   final response = await client.postAuthData('register', body);
   final userResponseModel = UserResponseModel.fromJSON(response);

   final accessToken = userResponseModel.accessToken;
   final refreshToken = userResponseModel.refreshToken;

   storage = FlutterSecureStorage();
   await storage.write(key: APIConstants.ACCESS_TOKEN_KEY, value: accessToken)
      .then((value) => print('AccessToken Written to FSS'));
   await storage.write(key: APIConstants.REFRESH_TOKEN_KEY, value: refreshToken)
      .then((value) => print('AccessToken Written to FSS'));

   return userResponseModel.user;
 }

 @override
 Future<UserModel> getCurrentUser(String id) {
   return null; // cos of write and read operation for userid, had to just return null for now
 }

 @override
 Future<void> logOut() async {
  await client.postAuthData('logout', null);
 } 

}

Now, when I compile my code, cos of the Delayed Future in Repository Snippet (First One Above), I get this error (showing full logs):

I/flutter ( 5048): Login state is LoginStateInitial
I/flutter ( 5048): LoginState Listened is:  LoginStateLoading
I/flutter ( 5048): LoginState Building is:  LoginStateLoading
I/flutter ( 5048): {success: true, message: Logged in Successfully, user: {_id: 60435f3a0b5da10015bb87b3, username: abc123, email: abc123@gmail.com, password: $2b$10$wgdBFyvWUI4e0bB3KNRBl.WLLVwu2FVZQN9BtSSdxbSzfcwdQyH2K, phone: 89066060484, __v: 0}, accessToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MTgxMjU2NTEsImV4cCI6MTYyMDcxNzY1MSwiYXVkIjoiNjA0MzVmM2EwYjVkYTEwMDE1YmI4N2IzIiwiaXNzIjoicGlja3VycGFnZS5jb20ifQ.uH3D_v6mYwIOnBgRfoQQYzrN8asPt_Rhfei9wxmAM6A, refreshToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MTgxMjU2NTEsImV4cCI6MTY0OTY4MzI1MSwiYXVkIjoiNjA0MzVmM2EwYjVkYTEwMDE1YmI4N2IzIiwiaXNzIjoicGlja3VycGFnZS5jb20ifQ.dIgOHlsXdBXUtrLNqiF3mKhmKZU0HJe-IwMDyg23OO0}
D/FlutterSecureStoragePl( 5048): Initializing StorageCipher
I/fluttersecurestorage( 5048): Creating keys!
I/fluttersecurestorage( 5048): Initializing
I/fluttersecurestorage( 5048): Generating key pair
E/KeyStore( 5048): generateKeyInternal failed on request -68
D/FlutterSecureStoragePl( 5048): StorageCipher initialization complete
D/FlutterSecureStoragePl( 5048): StorageCipher already initialized
I/flutter ( 5048): LoginState Listened is:  LoginStateSuccess
W/utter_bloc_aut( 5048): Accessing hidden method Lsun/misc/Unsafe;- 
>compareAndSwapObject(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z (greylist, linking, allowed)
E/flutter ( 5048): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: 
   // starts from here // NoSuchMethodError: The method 'write' was called on null.
E/flutter ( 5048): Receiver: null
E/flutter ( 5048): Tried calling: write(key: "userId", value: "60435f3a0b5da10015bb87b3") // in repository file
E/flutter ( 5048): #0      Object.noSuchMethod (dart:core-patch/object_patch.dart:54:5)
E/flutter ( 5048): #1      UserRepositoryImpl.loginUser.<anonymous closure> (package:flutter_bloc_auth/data/repositories/user_repository_impl.dart:22:21)
E/flutter ( 5048): #2      UserRepositoryImpl.loginUser.<anonymous closure> (package:flutter_bloc_auth/data/repositories/user_repository_impl.dart:21:42)
E/flutter ( 5048): #3      new Future.delayed.<anonymous closure> (dart:async/future.dart:315:39)

Is this a prime example of using null safety and how? I had previously tried migrating the entire project to null safety but there were cyclic dependencies on the packages, so I continued without null safety.

Is this a case of using FutureBuilder to wait for the value of the strings (tokens and userid). Had tried calling .then but my response body (JSON String) is not a Future, obviously.

What's the cause of this error as we can see it later held the value of userId in E/flutter ( 5048): Tried calling: write(key: "userId", value: "60435f3a0b5da10015bb87b3") but initially it didn't, as per my understanding.

I've run into this error severally and still haven't found a way to deal with it. Any help will be appreciated.

                   --------EDIT (SIMILAR ISSUE)------

I've gotten the tokens and userid to save when login method is called. However, when I close the app and re-open it, it throws an error for reading the access token key (which was saved previously), like below log:

I/flutter (27570): AuthenticationStateInitial
I/flutter (27570): LoginState Building is:  LoginStateInitial
I/flutter (27570): NoSuchMethodError: The method 'read' was called on null.
I/flutter (27570): Receiver: null
I/flutter (27570): Tried calling: read(key: "access_token")
I/flutter (27570): AuthenticationStateFailure

I want the AuthenticationBloc to read the state once it finds the token as Authenticated not Failure, each time a previously logged in or just signed up user opens the app. What's the best strategy for that?

Here's my AuthenticationBloc snippet. Login & Register Blocs Snippets Skipped Due to Brevity and Similarity of Functionality.

class AuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {

  final UserRepository _repository;
  AuthenticationBloc(UserRepository repository)
  : assert(repository != null), _repository = repository,
    super(AuthenticationStateInitial());

  @override
  Stream<AuthenticationState> mapEventToState(
    AuthenticationEvent event) async* {

    if (event is AppStarted) {
      yield* _mapAppStartedToState(event);
    }

    if(event is UserSignedUp){
      yield* _mapUserSignedUpToState(event);
    }

    if(event is UserLoggedIn){
      yield* _mapUserLoggedInToState(event);
    }

    if(event is UserLoggedOut){
      await _repository.deleteTokens(event.accessToken, event.refreshToken);
      yield* _mapUserLoggedOutToState(event);
    }

  }

  Stream<AuthenticationState> _mapAppStartedToState(AppStarted event) async* 
    {
      yield AuthenticationStateLoading();
      try{
       // if(event?.accessToken == null || event.refreshToken == null) {
      //   yield AuthenticationStateUnAuthenticated();
      // } else {
     //   print('accessToken is\t${event?.accessToken}');
      //   print('refreshToken is\t${event?.refreshToken}');
     //   try {
     //     final currentUser = await _repository.getCurrentUser();
     //     yield AuthenticationStateAuthenticated(user: currentUser, );
    //   } catch(error2) {
    //     print(error2);
    //   }
    // }

    final hasTokens = await _repository.checkHasTokens(event.accessToken, event.refreshToken); //simple read operation using the Keys used to write tokens values
    print('hasTokens $hasTokens');
    final currentUser = await _repository.getCurrentUser();
    // if(hasTokens && currentUser != null){
    if(hasTokens){
      yield AuthenticationStateAuthenticated(user: currentUser, );
    } else {
      yield AuthenticationStateUnAuthenticated();
    }
  } catch(err){
    print(err);
   // yield AuthenticationStateFailure(errorMessage: err.message ?? 'Error Completing Your Request, Try Again!');
    yield AuthenticationStateFailure(errorMessage: 'Error Completing Your Request, Try Again!');
  }
}

Stream<AuthenticationState> _mapUserSignedUpToState(UserSignedUp event) async* {
  yield AuthenticationStateAuthenticated(user: event.user);
}

Stream<AuthenticationState> _mapUserLoggedInToState(UserLoggedIn event) async* {
  yield AuthenticationStateAuthenticated(user: event.user);
}

Stream<AuthenticationState> _mapUserLoggedOutToState(UserLoggedOut event) async* {
  await _repository.logOut();
  yield AuthenticationStateUnAuthenticated();
}

}

and UserRepositoryImpl Snippet, as Suggested:

class UserRepositoryImpl extends UserRepository {

final UserRemoteDataSource dataSource;
UserRepositoryImpl({@required this.dataSource});

var storage;

@override
  Future<UserModel> loginUser(Map<String, dynamic> body) async {
  final userModel = await dataSource.loginUser(body);
  // var userId = userModel.id;
  // Future.delayed(Duration(seconds: 2), () async {
//   await storage.write(key: APIConstants.USER_ID_KEY, value: userId);
// });
  return userModel;
}

@override
 Future<UserModel> registerUser(Map<String, dynamic> body) async {
  final userModel = await dataSource.registerUser(body);
 // await storage.write(key: APIConstants.USER_ID_KEY, value: userModel.id);
  return userModel;
}

@override
 Future<UserModel> getCurrentUser() async {
  // final userId = await storage.read(key: APIConstants.USER_ID_KEY);
  // final user = await dataSource.getCurrentUser(userId);
  // return user;
 return null;
}

@override
Future<void> logOut() async {
  storage = FlutterSecureStorage();
  await storage.delete(key: APIConstants.ACCESS_TOKEN_KEY);
  await storage.write(key: APIConstants.REFRESH_TOKEN_KEY);
  await dataSource.logOut();
}

@override
Future<void> saveTokens(String accessToken, String refreshToken) async {
  final storage = FlutterSecureStorage();
  print('st() accesstokenval\t:$accessToken');
  await storage.write(key: APIConstants.ACCESS_TOKEN_KEY, value: accessToken);
  await storage.write(key: APIConstants.REFRESH_TOKEN_KEY, value: refreshToken);
}

@override
Future<void> deleteTokens(String accessToken, String refreshToken) async {
  storage = FlutterSecureStorage();
  await storage.delete(key: APIConstants.ACCESS_TOKEN_KEY);
  await storage.write(key: APIConstants.REFRESH_TOKEN_KEY);
}

@override
Future<bool> checkHasTokens(String accessToken, String refreshToken) async{
  storage = FlutterSecureStorage();
  final hasAT = await storage.read(key: APIConstants.ACCESS_TOKEN_KEY);
  final hasRT = await storage.read(key: APIConstants.REFRESH_TOKEN_KEY);
  return hasRT == null || hasAT == null ? false : true;
}

}

Nzadibe
  • 598
  • 2
  • 11
  • 28
  • Could you show us the user_repository_impl class and it's loginUser() method ? – FDuhen Apr 11 '21 at 08:15
  • I have fixed this error. Everything works but not so great, like instead of directly taking the user to home page, it briefly displays the login page. Thanks – Nzadibe Apr 12 '21 at 02:19

0 Answers0