I have little experience in terms of keeping an app logged in using a token and then removing that token when the user hits Sign Out. Although I have managed to keep the app in a logged-in state, the trouble begins when I press the Sign Out button. Clicking on Sign out appears as if it is logged out. This is also the case when the app is only minimized, but as soon as the app is cleared from memory and started again, it takes me directly into the home screen. I believe the token is somehow not getting cleared off the local memory when I log out. Also, I keep getting the below error every time I start the app and logout. The code is below:
The error I get says:
Unhandled Exception: Null check operator used on a null value
The provider that facilitates Sign In, Sign Up and Sign Out.
class Network with ChangeNotifier {
String url = 'https://achievexsolutions.in/current_work/eatiano/';
var token;
var _authToken = '';
bool _isAuth = false;
bool get auth {
return _isAuth;
}
Future<String?> get authToken async {
SharedPreferences localStorage = await SharedPreferences.getInstance();
return _authToken = localStorage.getString('token')!; //This is where the above error points at
}
getToken() async {
SharedPreferences localStorage = await SharedPreferences.getInstance();
token = localStorage.getString('token')!;
}
authData(data, apiUrl) async {
var fullUrl = url apiUrl;
return await http.post(Uri.parse(fullUrl),
body: jsonEncode(data), headers: setHeaders());
}
logOut(apiUrl) async { //This is the Sign Out API
var fullUrl = url apiUrl;
SharedPreferences localStorage = await SharedPreferences.getInstance();
localStorage.remove('token'); //This is where I remove the token
return await http.get(Uri.parse(fullUrl), headers: setHeaders());
}
getData(apiUrl) async {
var fullUrl = url apiUrl;
await getToken();
return await http.get(Uri.parse(fullUrl), headers: setHeaders());
}
setHeaders() => {
'Content-type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $token'
};
}
The Sign In method in widget:
class SignInFormState extends State<SignInForm> {
final _passwordFocus = FocusNode();
final _key = GlobalKey<FormState>();
late var inputEmail;
late var inputPassword;
@override
void dispose() {
_passwordFocus.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
......
......
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
boxShadow: const [
BoxShadow(
color: Colors.black,
blurRadius: 20,
spreadRadius: 10,
offset: Offset(2, 1))
]),
child: InkWell(
onTap: () {
if (_key.currentState!.validate()) {
_login();
}
},
child: Button('Login')),
),
}
void _login() async {
final data = {'email': inputEmail, 'password': inputPassword};
var res = await Provider.of<Network>(context, listen: false)
.authData(data, 'api/auth/login');
final provider =
Provider.of<LocationProvider>(context, listen: false).loading;
print(json.decode(res.body));
var body = json.decode(res.body);
if (body['error'] == 'Unauthorized') {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Email or Password invalid',
style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold)),
backgroundColor: Colors.white,
action: SnackBarAction(
label: 'Close',
onPressed: () => Scaffold.of(context).hideCurrentSnackBar()),
));
} else {
SharedPreferences localStorage = await SharedPreferences.getInstance();
localStorage.setString('token', body['access_token']); //This is where I set the token
Navigator.of(context).pushReplacementNamed('/bottom-bar');
}
}
}
The main.dart file where the token is checked:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => Network()),
],
builder: (context, child) {
final provider = Provider.of<LocationProvider>(context).loading;
final authToken = Provider.of<Network>(context).authToken;
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
scaffoldBackgroundColor: const Color.fromRGBO(25, 29, 33, 1),
primaryColor: const Color.fromRGBO(252, 17, 17, 1)),
home: authToken != null ? BottomNavigation() : SignIn(), //This check doesn't seem to work
routes: {
'/sign-in': (context) => SignIn(),
'/sign-up': (context) => SignUp(),
'/otp-screen': (context) => OTP(),
'/bottom-bar': (context) => BottomNavigation(),
},
);
},
);
}
}
Logout Widget:
class Profile extends StatefulWidget {
ProfileState createState() => ProfileState();
}
class ProfileState extends State<Profile> {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body: Center(
......
......
......
SizedBox(height: height * 0.003),
Center(
child: Padding(
padding: EdgeInsets.only(left: width * 0.04),
child: InkWell(
onTap: () async {
var res = await Provider.of<Network>(context, listen: false)
.logOut('api/auth/logout'); //This is where the LogOut method Is invoked
Navigator.of(context).pushReplacementNamed('/sign-in');
},
child: Text(
'Sign Out',
textScaleFactor: textScale,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 12),
),
),
),
),
],
),
),
);
}
}
Any suggestion as to how the logout functionality can be facilitated properly would be appreciated. Also, everyone is open to suggest ways to improve the code.
CodePudding user response:
Can you try adding "await" in front of localstorage in the signout function? Maybe not adding await can be the problem since its an asynchronous operation. I can be wrong too!
CodePudding user response:
I guess this is about auth_token on home parametres. auth_token cant be null. Can you use (auth_token.isNotEmpty) ?