'Flutter : Strange behavior of Shared Preferences

I have a problem with inconsistent shared preferences value. I will try to describe it as simple as possible.

I'm using Firebase Cloud Messaging for push notifications. When app is in background and notification came in, background handler bellow is invoked.

Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  final SharedPreferences prefs = await SharedPreferences.getInstance();
  final int counter = (prefs.getInt('badge') ?? 0) + 1;

  prefs.setInt('badge', counter).then((bool success) {
    print(counter);
  });
}

My widget uses WidgetsBindingObserver to determine lifecycle state. When I enter the app, state of that widget is onResume and there I want to read that badge value from shared preferences like this.

void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      final SharedPreferences prefs = await SharedPreferences.getInstance();
      final int counter = (prefs.getInt('badge') ?? 0); 
      print(counter);
    }
  }

Scenario 1:

  • App opened, notification came in - set badge field to 1.
  • App in background, notification came in - background handler set badge field to 2.
  • App resumed, read that badge field, it's still 1.

Scenario 2:

  • App opened, notification came in - set badge field to 1.
  • App in background, notification came in - background handler set badge field to 2.
  • App in background, notification came in - background handler set badge field to 3.
  • App resumed, read that badge field, it's still 1.

Question: Any idea why field isn't updated?



Solution 1:[1]

SharedPreferences can be used on background events handlers. The problem is that the background handler run in a different isolate so, when you try to get a data, the shared preferences instance is empty. To avoid this you simply have to force a refresh:

SharedPreferences prefs= await SharedPreferences.getInstance();
await prefs.reload();
final int counter = (prefs.getInt('badge') ?? 0);

In the same mode, if the shared preferences can be modified in a background hadler, be sure you call this "reload" function in the main isolate when you try to read from theirs.

Solution 2:[2]

SharedPreferences or any other local storage won't work in the _firebaseMessagingBackgroundHandler.

You should capture it on getInitialMessage or onMessageOpenedApp. https://firebase.flutter.dev/docs/messaging/notifications/

TL;DR: getInitialMessage gets triggered when the application is opened from a terminated state. While onMessageOpenedApp gets triggered when the application is opened from background state.

FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage message) {
  if (message != null) {
    Navigator.of(context).pushNamed('/messages', arguments: message.data);
  }
});

FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
  if (message != null) {
    Navigator.of(context).pushNamed('/messages', arguments: message.data);
  }
});

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 David B.F.
Solution 2 harlandgomez