can't initialized Shared Pref using GetIt in flutter - flutter

I want to implement a session management system using Shared Preference in my flutter app. For Dependency injection, I use GetIt library. But when I run the app, it says 'flutter: Error while creating Session'
'The following ArgumentError was thrown building Builder(dirty):
Invalid argument (Object of type SharedPreferences is not registered inside GetIt.
Did you forget to pass an instance name?
(Did you accidentally do GetIt sl=GetIt.instance(); instead of GetIt sl=GetIt.instance;)): SharedPreferences'
Session.dart
abstract class Session {
void storeLoginInfo(String accessToken);
bool isUserLoggedIn();
String getAccessToken();
void deleteLoginInfo();
}
SessionImpl.dart
class SessionImpl extends Session {
SharedPreferences sharedPref;
SessionImpl(SharedPreferences sharedPref) {
this.sharedPref = sharedPref;
}
#override
void storeLoginInfo(String accessToken) {
sharedPref.setBool('login_status', true);
sharedPref.setString('access_token', accessToken);
}
#override
bool isUserLoggedIn() {
final isLoggedIn = sharedPref.getBool('login_status') ?? false;
return isLoggedIn;
}
#override
String getAccessToken() {
return sharedPref.getString('access_token') ?? "";
}
#override
void deleteLoginInfo() {
if (sharedPref.containsKey('login_status')) sharedPref.remove('login_status');
if (sharedPref.containsKey('access_token')) sharedPref.remove('access_token');
}
}
ServiceLocator.dart
final serviceLocator = GetIt.instance;
Future<void> initDependencies() async {
_initSharedPref();
_initSession();
}
Future<void> _initSharedPref() async {
SharedPreferences sharedPref = await SharedPreferences.getInstance();
serviceLocator.registerSingleton<SharedPreferences>(sharedPref);
}
void _initSession() {
serviceLocator.registerLazySingleton<Session>(() => SessionImpl(serviceLocator()));
}
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown],
);
await initDependencies();
runApp(MyApp());
}

It seems the only thing you are missing is to await the _initSharedPref function in the initDependencies function. Like follows:
Future<void> initDependencies() async {
await _initSharedPref();
_initSession();
}
After that the object should be registering without problems.

Related

"Flutter" Fetch data from shared_preferences and provider before running the app

I am creating a flutter app, I am using shared_preferences to store the themeMode and using provider to manage state. I am able to fetch the themeMode before my app starts but not able to run the provider function that sets the themeMode.
Following is my code
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences.getInstance().then((prefs) {
var isDarkTheme = prefs.getBool("isDarkTheme") ?? false;
return runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => ThemeManager(),
),
],
child: MyApp(isDarkActive: isDarkTheme),
),
);
});
}
My Provider "State Management" code
class ThemeManager with ChangeNotifier {
bool _isDark = false;
void setThemeMode(bool themeMode) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool("isDarkTheme", themeMode);
_isDark = themeMode;
debugPrint(_isDark.toString());
notifyListeners();
// return true;
}
void fetchTheme() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var darkMode = prefs.getBool('isDarkTheme');
if (darkMode != null) {
_isDark = darkMode;
} else {
_isDark = false;
}
notifyListeners();
}
bool get isDark => _isDark;
}
You need to initialise a single global shared preference variable which you can use everywhere in your app so that you don't have to initialise it every time you when you want to use it.
so create a signleton class like this,
class SharedPreferencesHelper {
SharedPreferencesHelper._();
late SharedPreferences prefs;
Future<void> initialise() async {
prefs = await SharedPreferences.getInstance();
}
static final SharedPreferencesHelper instance = SharedPreferencesHelper._();
}
now initialise this in main() like this,
void main()async{
WidgetsFlutterBinding.ensureInitialized();
await SharedPreferencesHelper.instance.initialise();
}
now only use this variable where ever you want like this,
SharedPreferencesHelper.instance.prefs.getBool('key');
or create some functions inside your helper class,
Future<bool> getProperty(String key) async {
return await prefs.getBool(key);
}

Flutter Unhandled exception: ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized

I am trying to create an Isolate un Flutter and then use this isolate to fetch some data from Firebase Realtime Database.
I am creating de Isolate in a file called home.dart (not main) and here is my code for that file. I have a class to create the Isolate and the function for the Isolate to execute. Inside this function I am trying to fetch the data.
void elIsolate(SendPort sPort) async {
print("Fetching data");
final databaseReference = FirebaseDatabase.instance.reference().child("categories");
DataSnapshot info;
/*databaseReference.once().then((DataSnapshot snapshot) {
info = snapshot;
print(info.value);
});*/
print("new isolate created");
IsolateChannel channel = IsolateChannel.connectSend(sPort);
channel.stream.listen((data) {
print('newIsolate received : $data');
});
channel.sink.add("hi");
}
class _MyHomePageState extends State<MyHomePage> {
List list = [];
void initState(){
WidgetsFlutterBinding.ensureInitialized();
super.initState();
print("Init state");
loadIsolate();
}
Future loadIsolate() async {
await Firebase.initializeApp();
print("Load isolate");
ReceivePort rPort = ReceivePort();
IsolateChannel channel = IsolateChannel.connectReceive(rPort);
channel.stream.listen((data) {
print('rootIsolate received : $data');
channel.sink.add('How are you');
});
await Isolate.spawn(elIsolate, rPort.sendPort);
/*await Isolate.spawn(getAllWorkers, receivePort.sendPort);
receivePort.listen((message) {
print(message);
});*/
}
}
Then I have my main.dart. I added this line inside the main function: WidgetsFlutterBinding.ensureInitialized();
Here is my code
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
bool resp;
await SharedPreferences.getInstance().then((prefs) {
resp = prefs.getBool('isUser');
if (resp == null) {
FirebaseAuth _auth = FirebaseAuth.instance;
resp = (_auth.currentUser != null);
prefs.setBool('isUser', resp);
}
});
runApp(MyApp(user: resp));
}
flutter_isolate: ^2.0.2
onPressed: () {
FlutterIsolate.spawn(_isolateEntrypoint, "");
}
// A "top level" function (i.e. not inside a class or make it static)
_isolateEntrypoint(String foo) {
WidgetsFlutterBinding.ensureInitialized();
...
}
Make sure that authorization and initialization were made on the same main thread (top level or static).
Now this FlutterEngine will be able to communicate with Firebase Realtime Database but the main FlutterEngine won't. In practice, depending on the app, an app may want to communicate with Realtime Database from either engine (or both). In background apps, more likely from here rather than the main isolate, but again that depends on the app.

Flutter: Shared preferences singleton not initializing properly

I'm very new to Flutter and Dart, comming from android, bringing some of my habbits with me, I want to implement a SharedPreferences singleton object to simplify and avoid repetition (duplication).
this is my SharedPreferences singleton class:
import 'package:shared_preferences/shared_preferences.dart';
import 'package:synchronized/synchronized.dart';
class MySharedPreferences {
static MySharedPreferences _instance;
SharedPreferences _preferences;
// keys
final String _logged = "LOGGED";
final String _accessToken = "ACCESS_TOKEN";
MySharedPreferences._() {
_initSharedPreferences();
}
static MySharedPreferences getInstance() {
var lock = new Lock();
if (_instance == null) {
lock.synchronized(() => {_instance = new MySharedPreferences._()});
return _instance;
} else
return _instance;
}
_initSharedPreferences() async {
_preferences = await SharedPreferences.getInstance();
}
bool checkLogged() {
return _preferences.getBool(_logged);
}
void setLogged(bool logged) {
_preferences.setBool(_logged, logged);
}
well most of this logic is what i used to do in android, and used to work perfectly, but when i tried testing it, the singleton is always null, here is the test:
import 'package:flutter_test/flutter_test.dart';
import 'package:reportingsystem/local/my_shared_preferences.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
test('Test the shared_preferences', () {
MySharedPreferences preferences = MySharedPreferences.getInstance();
preferences.setLogged(true);
expect(preferences.checkLogged(), true);
preferences.setLogged(false);
expect(preferences.checkLogged(), false);
});
}
The test fails because the "preferences" object is null, i don't know what wrong, and i don't find much about it in the docs.
here is the stacktrace:
dart:core Object.noSuchMethod
package:reportingsystem/local/my_shared_preferences.dart 34:18 MySharedPreferences.setLogged
test\shared_preferences_test.dart 8:17 main.<fn>
test\shared_preferences_test.dart 6:39 main.<fn>
NoSuchMethodError: The method 'setBool' was called on null.
Receiver: null
Tried calling: setBool("LOGGED", true)
Here's an example where you must call init when first calling the Singleton, and then you'll be able to access it synchronously.
class MySharedPreferences {
static final MySharedPreferences _instance = MySharedPreferences._internal();
MockSharedPreferences prefereces;
factory MySharedPreferences() {
return _instance;
}
Future<void> init() async {
if (prefereces != null) {
return;
}
prefereces = await Future.delayed(Duration(seconds: 1), () => MockSharedPreferences());
}
MySharedPreferences._internal();
}
class MockSharedPreferences {
final Map<String, bool> data = {};
void setBool(String key, bool value) {
data[key] = value;
print('data $data');
}
}
Then you can use it without await after first initialization, like this:
Future<void> main() async {
await first();
anyOther();
}
void anyOther() {
MySharedPreferences singleton = MySharedPreferences();
singleton.prefereces.setBool('first', true);
}
Future<void> first() async {
MySharedPreferences singleton = MySharedPreferences();
await singleton.init();
singleton.prefereces.setBool('notFirst', true);
}

How to correctly save the value in sharedPreferences? - Flutter

Where am I going wrong?
I have login with google to get the token and send it to graphgl, this token is saved (it was meant to be) in sharedpreferences, but it is not saving, I have the following action (mobx).
#action
Future loginWithGoogle() async {
user = await _authRepository.getGoogleLogin();
final idToken = await user.getIdToken();
print('Bearer ${idToken.token}');
sharedPreferenceService.setToken('Bearer ${idToken.token}');
}
Services shared.
class SharedPreferenceService {
SharedPreferences _prefs;
Future<bool> getSharedPreferencesInstance() async {
_prefs = await SharedPreferences.getInstance().catchError((e) {
print("shared prefrences error : $e");
return false;
});
return true;
}
Future setToken(String token) async {
await _prefs.setString('token', token);
}
Future clearToken() async {
await _prefs.clear();
}
Future<String> get token async => _prefs.getString('token');
}
SharedPreferenceService sharedPreferenceService = SharedPreferenceService();
Action login in view.
#action
Future loginWithGoogle() async {
try {
loading = true;
await auth.loginWithGoogle();
Modular.to.pushReplacementNamed('/index');
} catch (e) {
loading = false;
}
}
The login happens normal but it accuses error when it goes to index, informing that it received null the getString("token").
I/flutter ( 3198): ClientException: Unhandled Failure NoSuchMethodError: The method 'getString' was called on null.
I/flutter ( 3198): Receiver: null
I/flutter ( 3198): Tried calling: getString("token")
This token string is not being saved.
Sorry for bad english
Just copied your code and made some changes just check:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
SharedPreferenceService sharedPreferenceService = SharedPreferenceService();
#override
void initState() {
super.initState();
loginWithGoogle();
getSharedValues();
}
getSharedValues() async{
bool value = await sharedPreferenceService.getSharedPreferencesInstance();
if(value)
print(await sharedPreferenceService.token);
}
loginWithGoogle() async {
// this is the where you get your bearer, but time being I have taken sample bearer
String token =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJZb3VuaXNaYXJnYXIiLCJlbWFpbCI6InlvdW5pc0BiYXh0dXJlLmNvbSIsImp0aSI6IjlhNjc2OTVlLTBiZmEtNDdmMy04ZTVlLWVhYWMzY2VmNmRlOSIsIklkIjoiMSIsIkVtYWlsIjoieW91bmlzQGJheHR1cmUuY29tIiwiZXhwIjoxNTgzODQ2ODU0LCJpc3MiOiJQYWNpZmljIFByaW50aW5nIiwiYXVkIjoiUGFjaWZpYyBQcmludGluZyJ9.CKxBwAB7YeOKJRmoCg4_JAhJKHP2qXb7KJXPysqmbAs';
bool value = await sharedPreferenceService.getSharedPreferencesInstance();
if (value == true) {
sharedPreferenceService.setToken('Bearer $token');
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(home: Scaffold(body: Center(child: Text('sample'))));
}
}
class SharedPreferenceService {
SharedPreferences _prefs;
Future<bool> getSharedPreferencesInstance() async {
_prefs = await SharedPreferences.getInstance().catchError((e) {
print("shared prefrences error : $e");
return false;
});
return true;
}
Future setToken(String token) async {
await _prefs.setString('token', token);
}
Future clearToken() async {
await _prefs.clear();
}
Future<String> get token async => _prefs.getString('token');
}
Thank you very much, I made the correction in the action.
#action
Future loginWithGoogle() async {
user = await _authRepository.getGoogleLogin();
final idToken = await user.getIdToken();
print('Bearer ${idToken.token}');
bool value = await sharedPreferenceService.getSharedPreferencesInstance();
if (value == true) {
sharedPreferenceService.setToken('Bearer ${idToken.token}');
}
}

Flutter: How to use SharedPreferences synchronously?

I am using Shared Preferences in my Flutter app and what I would like to do is store SharedPreferences as a field on startup and then use it synchronously in the app. However I'm not sure if I'm not missing anything.
What I want to achieve is instead of:
method1() async {
SharedPreferences sp = await SharedPreferences.getInstance();
return sp.getString('someKey');
}
to
SharedPreferences sp;
//I would probably pass SharedPreferences in constructor, but the idea is the same
someInitMethod() async {
sp = await SharedPreferences.getInstance();
}
method1() {
return sp.getString('someKey');
}
method2() {
return sp.getString('someKey2');
}
method3() {
return sp.getString('someKey3');
}
In that way I would achieve synchronous access to sharedPrefs. Is it bad solution?
EDIT:
What is worth mentioning is that getInstance method will only check for instance and if there is any than it returns it, so as I see it, is that async is only needed to initialize instance. And both set and get methods are sync anyway.
static Future<SharedPreferences> getInstance() async {
if (_instance == null) {
final Map<String, Object> fromSystem =
await _kChannel.invokeMethod('getAll');
assert(fromSystem != null);
// Strip the flutter. prefix from the returned preferences.
final Map<String, Object> preferencesMap = <String, Object>{};
for (String key in fromSystem.keys) {
assert(key.startsWith(_prefix));
preferencesMap[key.substring(_prefix.length)] = fromSystem[key];
}
_instance = new SharedPreferences._(preferencesMap);
}
return _instance;
}
I use the same approach as the original poster suggests i.e. I have a global variable (actually a static field in a class that I use for all such variables) which I initialise to the shared preferences something like this:
in globals.dart:
class App {
static SharedPreferences localStorage;
static Future init() async {
localStorage = await SharedPreferences.getInstance();
}
}
in main.dart:
void main() {
start();
}
Async.Future start() async {
await App.init();
localStorage.set('userName','Bob');
print('User name is: ${localStorage.get('userName)'}'); //prints 'Bob'
}
The above worked fine but I found that if I tried to use App.localStorage from another dart file e.g. settings.dart it would not work because App.localStorage was null but I could not understand how it had become null.
Turns out the problem was that the import statement in settings.dart was import 'package:<packagename>/src/globals.dart'; when it should have been import 'globals.dart;.
#iBob101 's answer is good, but still, you have to wait before you use the SharedPreferences for the first time.
The whole point is NOT to await for your SharedPreferences and be sure that it will always be NOT NULL.
Since you'll have to wait anyway let's do it in the main() method:
class App {
static SharedPreferences localStorage;
static Future init() async {
localStorage = await SharedPreferences.getInstance();
}
}
And the main method:
void main() async{
await SharedPref.initSharedPref();
runApp(MyApp());
}
the line await SharedPref.initSharedPref(); takes ~100ms to execute. This is the only drawback as far as I can see.
But you definitely know that in every place in the app your sharedPreferenes instance in NOT NULL and ready for accessing it:
String s = App.localStorage.getString(PREF_MY_STRING_VALUE);
I think it's worthwhile
The cleanest way is to retrieve SharedPreferences in main method and pass it to MyApp as a dependency:
void main() async {
// Takes ~50ms to get in iOS Simulator.
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
runApp(MyApp(sharedPreferences: sharedPreferences));
}
class MyApp extends StatefulWidget {
final SharedPreferences sharedPreferences;
const MyApp({Key key, this.sharedPreferences})
: assert(sharedPreferences != null),
super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
// You can access shared preferences via widget.sharedPreferences
return ...
}
I made a simple way to using this PrefUtil class:
import 'package:shared_preferences/shared_preferences.dart';
class PrefUtil {
static late final SharedPreferences preferences;
static bool _init = false;
static Future init() async {
if (_init) return;
preferences = await SharedPreferences.getInstance();
_init = true;
return preferences;
}
static setValue(String key, Object value) {
switch (value.runtimeType) {
case String:
preferences.setString(key, value as String);
break;
case bool:
preferences.setBool(key, value as bool);
break;
case int:
preferences.setInt(key, value as int);
break;
default:
}
}
static Object getValue(String key, Object defaultValue) {
switch (defaultValue.runtimeType) {
case String:
return preferences.getString(key) ?? "";
case bool:
return preferences.getBool(key) ?? false;
case int:
return preferences.getInt(key) ?? 0;
default:
return defaultValue;
}
}
}
In main.dart:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
PrefUtil.init();
.....
Save it like:
PrefUtil.setValue("isLogin", true);
Get the value like:
PrefUtil.getValue("isLogin", false) as bool
By this, it will initialize only once and get it where ever you need.
You can use FutureBuilder to render the loading screen while waiting for SharedPreferences to be intialized for the first time in a singleton-like class. After that, you can access it synchronously inside the children.
local_storage.dart
class LocalStorage {
static late final SharedPreferences instance;
static bool _init = false;
static Future init() async {
if (_init) return;
instance = await SharedPreferences.getInstance();
_init = true;
return instance;
}
}
app_page.dart
final Future _storageFuture = LocalStorage.init();
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _storageFuture,
builder: (context, snapshot) {
Widget child;
if (snapshot.connectionState == ConnectionState.done) {
child = MyPage();
} else if (snapshot.hasError) {
child = Text('Error: ${snapshot.error}');
} else {
child = Text('Loading...');
}
return Scaffold(
body: Center(child: child),
);
},
);
}
my_page.dart
return Text(LocalStorage.instance.getString(kUserToken) ?? 'Empty');
call shared prefs on startup of a stateful main app (we call ours a initState() override of a StatefulWidget after super.initState())
after shared prefs inits, set the value to a field on main (ex: String _someKey)
inject this field into any child component
You can the call setState() on _someKey at you leisure and it will persist to children injected with your field