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();
Related
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);
...
}
im using flutter 2.8.1 and shared_preferences: ^2.0.13
i build a todoapp with sharedpreferences all works fine from storing data to localstorage and load the data in initState but now i want to delete spesific item/selected item in the localstorage but when i use prefs.remove('key'); all the item is removed, i only want to remove the selected item only.
let me know if you need more information with the code.
the key contain this, for example i want to remove item with id: 64, not the key
I/flutter ( 9643): [{"id":64,"set_name":"asd"},{"id":55,"set_name":"asdasdwqe"}]
remove function code
Future deleteData() async {
final SharedPreferences prefs = await _prefs;
prefs.remove('sets');
setState(() {});
}
save function code
Future saveData() async {
final SharedPreferences prefs = await _prefs;
List items = c.setList.map((item) => item.toJson()).toList();
prefs.setString('sets', jsonEncode(items));
}
There isn't easy way but to overwrite new values over old value. This is one of the drawbacks of using SharedPreferences over traditional databases.
You will have to read the data from sharedPrefs, remove the concerned value and save it back to sharedPrefs.
I am saving the app's theme to the device using SharedPreferences, in this way:
void _serializeTheme() async {
SharedPreferences _preferences = await SharedPreferences.getInstance();
_preferences.setString("theme", "dark");
}
Upon opening the app I can use _preferences.getString("theme") to detect the theme, but as SharedPreferences.getInstance() is async, I need to wrap the home of my application in a FutureBuilder which waits for the theme to load before displaying the page and renders a container while waiting. My problem is that for the brief time that the app spends loading the theme the screen flashes with the color of the container which, as I have no idea of which theme the user has saved before loading it, has to be a fixed color which may be in discordance with the background color of the home page. To prevent this is there a way I can load the theme before displaying anything at all?
So, is there a way to run an asyncronous function before opening the application? Or if this isn't fiesable, is there a way to read data syncronously from the device so that I could read it inside initState() so that I can know the theme that the user saved before rendering anyting?
In your case, I would get await data in before runApp(),
Example:
main()async{
// Here
SharedPreferences _preferences = await SharedPreferences.getInstance();
runApp(YourApp());
}
or
void main() {
runZonedGuarded(() async {
// Here
SharedPreferences _preferences = await SharedPreferences.getInstance();
runApp(YourApp());
}, (_, s) {});
}
You have to show something. You can set a splash screen of your choice using https://pub.dev/packages/flutter_native_splash, and then use that same screen to show when the FutureBuilder doesn't yet have the completed future, before transitioning to your main theme when it's ready.
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.
I have this code in flutter using SharedPreferences to store data:
Future<bool> setUserStatus(String userStatus) async{
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('userStatus', 'active');
return true;
}
Is it possible to use this same setUserStatus in another file, which will get this main.dart imported to it, and change the SharedPreferences data to something else based on the actions taken in the other file
Do this,
await setUserStatus( status );
if you don't want to wait for the future to complete just remove await from the begining.
Calling prefs.clear()will erase all the preferences set on the device. so I would suggest not to use that here. If you want to clear a particular preference just use
prefs.remove(key) or prefs.setString(key,null)