In my app built with Flutter, I am using the provider package to add state management to my app. Additionally, I am using the shared preferences package to keep track of the login state of my user (token based). The app consumes a Laravel API that makes use of Sanctum.
Everything is working as expected. However, after logging out the user and logging back in with a different user, causes the data of the previous user to be shown. I noticed the token of the old user keeps persisting in the providers, which causes old data to load.
main.dart
Future main() async {
await dotenv.load(fileName: ".env");
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => AuthProvider(),
child: Consumer<AuthProvider>(builder: (context, authProvider, child) {
return MultiProvider(
providers: [
ChangeNotifierProvider<CategoryProvider>(
create: (context) => CategoryProvider(authProvider)),
ChangeNotifierProvider<TransactionProvider>(
create: (context) => TransactionProvider(authProvider)),
ChangeNotifierProvider<ProfileProvider>(
create: (context) => ProfileProvider(authProvider))
],
child: MaterialApp(
title: 'Flutter App',
routes: {
'/': (context) {
final authProvider = Provider.of<AuthProvider>(context);
return authProvider.isAuthenticated ? Home() : Login();
},
'/login': (context) => Login(),
'/register': (context) => Register(),
'/profile': (context) => Profile(),
'/categories': (context) => Categories(),
},
));
}));
}
}
Given the above example, I was expecting for any change to my AuthProvider, to rebuild the Providers that are listed in the Consumer widget.
auth_provider.dart
class AuthProvider extends ChangeNotifier {
bool isAuthenticated = false;
late String token;
AuthProvider() {
init();
}
Future<void> init() async {
this.token = await getToken();
if (this.token.isNotEmpty) {
this.isAuthenticated = true;
}
ApiService apiService = ApiService(this.token);
notifyListeners();
}
ApiService apiService = ApiService('');
Future<void> register(String name, String email, String password,
String passwordConfirm, String deviceName) async {
this.token = await apiService.register(name, email, password, passwordConfirm, deviceName);
setToken(this.token);
this.isAuthenticated = true;
notifyListeners();
}
Future<void> login(String email, String password, String deviceName) async {
this.token = await apiService.login(email, password, deviceName);
setToken(this.token);
this.isAuthenticated = true;
notifyListeners();
}
Future<void> logout() async {
this.token = '';
this.isAuthenticated = false;
setToken(this.token);
final prefs = await SharedPreferences.getInstance();
prefs.clear();
notifyListeners();
}
Future<void> setToken(token) async {
final prefs = await SharedPreferences.getInstance();
prefs.setString('token', token);
}
Future<String> getToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('token') ?? '';
}
}
In the logout()
function, I am clearing the token.
category_provider.dart
class CategoryProvider extends ChangeNotifier {
List<Category> categories = [];
late ApiService apiService;
late AuthProvider authProvider;
CategoryProvider(AuthProvider authProvider) {
this.authProvider = authProvider;
this.apiService = ApiService(authProvider.token);
init();
}
Future init() async {
categories = await apiService.fetchCategories();
notifyListeners();
}
Future<void> addCategory(String name) async {
try {
Category addedCategory = await apiService.addCategory(name);
categories.add(addedCategory);
notifyListeners();
} catch (Exception) {
print(Exception);
}
}
// omitted functions
}
The ApiService is a class that receives the passed token and does API calls for the providers.
api.dart
class ApiService {
late String token;
ApiService(String token) {
this.token = token;
}
final String baseUrl = dotenv.env['APP_URL'].toString() '/api/';
Future<List<Category>> fetchCategories() async {
http.Response response =
await http.get(Uri.parse(baseUrl 'categories'), headers: {
HttpHeaders.contentTypeHeader: 'application/json',
HttpHeaders.acceptHeader: 'application/json',
HttpHeaders.authorizationHeader: 'Bearer $token',
});
List categories = jsonDecode(response.body)['data'];
return categories.map((category) => Category.fromJson(category)).toList();
}
// omitted functions
}
Why does the notifiyListeners()
in the logout function of auth_provider.dart
not trigger the consumers to rebuild? Am I missing something else that might cause this issue?
Update after answer
In the providers array of main.dart
, I changed the ChangeNotifierProvider to ChangeNotifierProxyProvider. The difference is that the ChangeNotifierProxyProvider allows for a update()
callback, so the provider can get updated if AuthProvider
updates.
Code example:
ChangeNotifierProxyProvider<AuthProvider, CategoryProvider>(
create: (context) => CategoryProvider(authProvider),
update: (context, authProvider, categoryProvider) => CategoryProvider(authProvider)
),
CodePudding user response:
The Consumer
is being updated. Your Provider
s aren't recreating their values.
Provider.create
is only called once, the first time that the value is needed. After a user logs out and another user logs in, the same CategoryProvider
instance still exists, so as far as Provider
knows, there's no reason to create another one. The ApiService
instance stored in CategoryProvider
still uses the old token, which causes old data to load.
To update the token, you will need to either update or recreate CategoryProvider
with the new token. One option for this is ChangeNotifierProxyProvider
, which provides an update
callback parameter.
CodePudding user response:
You have used multiple providers inside a single provider try changing that