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.