I have a background notification handler in my flutter app, which is a top-level function like so:
Future<void> _onBackgroundMessage(RemoteMessage message) async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final chatClient = StreamChatClient(STREAM_API_KEY);
print(Utils.user?.uid);
print(Utils.user?.streamToken);
chatClient.connectUser(
su.User(id: Utils.user?.uid ?? ''),
Utils.user?.streamToken ?? '',
connectWebSocket: false,
);
NotificationUtils.handleGetStreamNotification(message, chatClient);
SharedPreferences prefs = await SharedPreferences.getInstance();
int appBadgeCounter = prefs.getInt(appBadgePrefsKey) ?? 0;
FlutterAppBadger.updateBadgeCount(appBadgeCounter + 1);
}
In the notification handler, I need to connect to a separate server using a uid and a token. I have a Utils singleton class where I store an app user as a variable. I initialize this Utils app variable in my home page stateful widget class and persist it throughout the app. This way, I can minimize my number of database calls for user data.
However, in this top-level notification handler, the Utils.user is always null. This top level function exists in my home page stateful widget class, but it is a top level function still.
I want to avoid making database calls for every single notification that the app receives. Is there a way to get this Utils. user data without getting null??
you have to setUser before using it in chatClient.connectUser and along with you can check if user is null or not if null initialize it then it stops does not make extra calls.
Future<void> _onBackgroundMessage(RemoteMessage message) async {
...
await Firebase.initializeApp();
final chatClient = StreamChatClient(STREAM_API_KEY);
if(Utils.user != null) {
Utils.setUser();
}
print(Utils.user?.uid);
print(Utils.user?.streamToken);
...
}
Related
I'm having a mobile app with many screen (sreen A, screen B, screen C ...)
The requirement in my application to include two types of users. One type of user will have access to all screen and the second type user (not loggin) will only have access to Screen A , Screen B. How can I do that ?
My idea is store token after user loggin by SharedPreferences. And check the token is null or not. If not null, user can access all screen. But I don't know where to put this code ? At the main.dart or each screen ?
getToken() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
String token = sharedPreferences.getString("token");
//print(token);
return token;
}
//if token != null { ..can access all Screen } else { ... }
Wrap your home screen in a Futurebuilder. use getToken() as the future for this future builder. Based on the data returned from your function, return the screen you want.
you have two options:
create a global.dart file and save getToken() and also a variable for the storedvalue of token (so only check for the value of sharedPref once) in there. then you can access it as follow:
import './globals.dart' as globals;
globals.accessToken // for getting token value
//or
globals.getToken() // for getting token directly from sharedPref
use state managements like provider
I read other answers about initializing SharedPreferences in Flutter app (e.g. this one), but I want to take another route: initialize SharedPreferences object once at the very beginning, and then just pass it around to specific clients as required (a.k.a. dependency injection).
I tried to make my main method async and then use await:
void main() async {
var prefs = await SharedPreferences.getInstance();
runApp(MyApp(prefs));
}
Intuitively, I expected that the execution will halt until prefs is initialized, and then proceeds to runApp. Unfortunately, I get a white screen instead of my UI, so I guess I'm missing something here.
I also tried to use then:
void main() async {
SharedPreferences.getInstance().then((prefs) => runApp(MyApp(prefs)));
}
Same result: white screen.
Is there a way to initialize SharedPreference in this manner in Flutter?
add this before you preference instance
WidgetsFlutterBinding.ensureInitialized();
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?
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.
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);
}
});
I have screen with tabs and each screen implements AutomaticKeepAliveClientMixin. When I navigate to this screen(with tabs), each tab in initState fetches data from server like that:
fetchData()async{
final token = await getToken();//refresh if it is expired.
return fetchData(token);
}
I think it'd better if I initialize data for all the tabs in one request, because I can catch only one refresh token expired and socket exception in single place.
fetchAllData()async{
final token = await getToken();//refresh if it is expired.
return fetchAllData(token);
}
How would you build logic for screen and requests like that? Is my approach is something similar to what you use?
I would recommend you to use a Provider (https://pub.dev/packages/provider). By subscribing to the same Provider, you will be able to reuse the data you've once fetched. For instance, I've used this approach to provide to my App (at different places) the current user:
class UserModel extends ChangeNotifier {
User _currentUser;
void setUser(User user) {
_currentUser = user;
notifyListeners();
}
Future<User> getUser(BuildContext context) async {
if (_currentUser == null) {
_currentUser = await getUserRequest(context, hasRedirect: false);
}
return _currentUser;
}
}
Hope it will fit your needs !
You can add your fetchAllData method to the initState of the widget that holds all of the tabbed widgets. Then, you can you can pass the relevant data to the contructors of each of the tabbed widgets. Not the best solution, but it should work.
I'd still recommend Provider. State management systems are not all inclusive, nor are exclusive. Depending on how your state is presented your could use more than one state management system. Helll, the bloc library already includes the provider library.
So I call
SharedPReference prefs = await SharedPreferences.getInstance();
In my parent Main.dart. Then when the user presses a button in the ButtonBar it gets sent away using
Navigator.push(context, MaterialPageRoute(
builder: (BuildContext context) => Agenda(prefs: prefs,)));
I'm concerned this is not the most robust or performant way to use shared Preferences on child screens. For instance say the user navigated away from Main.dart before I have an instance of SharedPreferences, then the child screen - Agenda.dart - will have null passed into it as the value of prefs.
What happens then? Is the child screen stuck trying to access fields which are null? When Main.dart async process gets an instance of SharedPreferences does it feed it through to the child screen/class?
Would it be more robust to summon SharedPreferences in every class that uses it? Using:
SharedPReference prefs = await SharedPreferences.getInstance();
I feel passing prefs around is better because it reduces bottlenecks, but I don't fully understand.
When I modify values in SharedPreferences in child classes, then BACK button to it's parent screen, will the parent screen be accessing an updated SharedPreference, automagically?
getInstance() will check _completer , because _completer is static
the first time execution will keep reference in SharedPreferences._(this._preferenceCache)
the next time you call getInstance() will always directly return _completer.future; reference of cache, see picture below
no matter where you call getInstance() , even in SecondRoute
Source code of getInstance()
class SharedPreferences {
SharedPreferences._(this._preferenceCache);
static Completer<SharedPreferences> _completer;
...
static Future<SharedPreferences> getInstance() async {
if (_completer == null) {
_completer = Completer<SharedPreferences>();
try {
final Map<String, Object> preferencesMap =
await _getSharedPreferencesMap();
_completer.complete(SharedPreferences._(preferencesMap));
} on Exception catch (e) {
// If there's an error, explicitly return the future with an error.
// then set the completer to null so we can retry.
_completer.completeError(e);
final Future<SharedPreferences> sharedPrefsFuture = _completer.future;
_completer = null;
return sharedPrefsFuture;
}
}
return _completer.future;
}
What happens then? Is the child screen stuck trying to access fields which are null? When Main.dart async process gets an instance of SharedPreferences does it feed it through to the child screen/class?
This depends on your code. If you await for the SharedPreferences instance and then navigate, it's OK. But if you dont await it, then null will be passed to the next screen and no, it wont be feed through to the child screen/class.
When I modify values in SharedPreferences in child classes, then BACK button to it's parent screen, will the parent screen be accessing an updated SharedPreference, automagically?
Yes, it will, but not automagically. When you pass an object to another class or function in Dart, actually the object itself is not passed. A reference to that object is passed. A reference to an object is like an address to an object's place in memory.
And generally about your question, you can make use of the get_it and injectable packages. get_it is a simple Service Locator for Dart and Flutter projects with some additional goodies and Injectable is a convenient code generator for get_it.
Using these two packages you can register modules, which is a good place for SharedPrefrences. Take a look at this example to learn more about it.
Another way can be making use of an inherited widget. You can provide a value to the whole widget tree, and then you can access that value (that in your case would be SharedPrefrences instance) anywhere in your widgets.