ListTile onTap: Need to Check JWT in Secure Storage and Then Proceed with Named Page Navigation

223 Views Asked by At

New to Flutter, and I'm stumped on how to proceed with my App.

My user has already been authenticated via an API call, and the user's JWT is already stored in Flutter Secure Storage (I have verified that the token can be read from storage).

I would like to read the token from storage (before changing routes) to make sure that it hasn't expired. If the token has expired, I'd like to route the user back to the login page using a named route '/'. If the token hasn't expired, I'd like to route the user to a named route called '/devices'.

I'm using the ListTile's onTap; to call an inline FutureBuilder where my future: is secureStorage.readSecureData('jwt'),. This is working fine.

ListTile:

ListTile _tile(String title, int pid) => ListTile(
      leading: const Icon(Icons.cloud_download_outlined, color: customGreen),
      title: Padding(
        padding: const EdgeInsets.only(bottom: 10.0),
        child: Text(
          title,
          style: globalTheme.textTheme.headline3,
        ),
      ),
      subtitle: Text(
        'Property ID: ' + pid.toString(),
        style: globalTheme.textTheme.caption,
      ),
      onTap: () => FutureBuilder(
        future: secureStorage.readSecureData('jwt'),
        builder: (context, snapshot) {
          if (snapshot.hasData) return const CircularProgressIndicator();
          if (snapshot.data != '') {
            var propertyInfo = {'pid': pid, 'propertyName': title};
            var jwt = snapshot.data.toString().split('.');
            if (jwt.length != 3) {
              Navigator.of(context).pushNamed('/');
            } else {
              var payload = json.decode(
                  ascii.decode(base64.decode(base64.normalize(jwt[1]))));
              if (DateTime.fromMillisecondsSinceEpoch(payload['exp'] * 1000)
                  .isAfter(DateTime.now())) {
                Navigator.of(context)
                    .pushNamed('/devices', arguments: propertyInfo);
              } else {
                Navigator.of(context).pushNamed('/');
              }
            }
          } else {
            Navigator.of(context).pushNamed('/');
          }
          throw Exception('Error: builder is null');
        },
      ),
    );

My problem is that my code stops on my builder:. I've use the debugger to step through the code and have found that it simply won't continue past this line of code. No error is indicated.

Is this a type problem, or maybe I'm using FutureBuilder incorrectly?

EDIT 4/13/22:

I'm using the following class to read data from secure storage.

import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecureStorage {
  final storage = const FlutterSecureStorage();

  Future<void> writeSecureData(String key, String value) async {
    await storage.write(key: key, value: value);
  }

  Future<String?> readSecureData(String key) async {
    var readData = await storage.read(key: key);
    print('KEY: $key, JWT: $readData');
    return readData;
  }

  Future<void> deleteSecureData(String key) async {
    await storage.delete(key: key);
  }
}

I noticed that this class is returning Future<String?> as my future, but my FutureBuilder is expecting a <String?>. I am using async and await to read the data from storage so I expected to return jwt as a String (and it is a string when I print it in the class) but that is apparently not the case when I return it.

onTap: () {
        var jwt = secureStorage.readSecureData('jwt');
        print('JWT: $jwt');
      },

The above yields:

flutter: JWT: Instance of 'Future<String?>'
flutter: KEY: jwt, JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsInVzZXJuYW1lIjoicmZyZWkiLCJpYXQiOjE2NDk4MjM0NzcsImV4cCI6MTY0OTgyMzU5N30.SiM5sXoqqWtOddLTCsBbdgnNBKSEFVSa8k2gZxg_Fhk

EDIT 4/13/22:

I found a solution. In addition to reading secure storage asynchronously, I also make my call to my secure routing class asynchronous like this:

onTap: () async {
  var jwt = await secureStorage.readSecureData('jwt');
  var propertyInfo = {'pid': pid, 'propertyName': title};
  print(jwt);
  Navigator.of(context).pushNamed('/devices', arguments: propertyInfo);
},

Now I have access to my token. Note that FutureBuilder is no longer needed. Removal of FutureBuilder, however, also removes BuildContext which is needed for named routing. To overcome this problem, I pass the build context from ListView to ListTile.

With access to both of these items, I can finally write the logic I need for gated routing and then use named routing to route the user accordingly.

0

There are 0 best solutions below