Am I doing anything wrong with sharedPreferences in flutter.
SharedPreferences prefs;
int score;
int storedScore;
final String uid;
StoredData(this.uid) {
_initialize();
_readValues();
}
_initialize() async {
prefs = await SharedPreferences.getInstance();}
_readValues() {
score = prefs.getInt("score") ?? 0;
storedScore = prefs.getInt("storedScore") ?? 0;}
error:
I/flutter (18698): The method 'getInt' was called on null.
I/flutter (18698): Receiver: null
I/flutter (18698): Tried calling: getInt("score")
The relevant error-causing widget was:
I/flutter (18698): HomeScreen file:///D:/flutter/trivia_iq/trivia_iq/lib/main.dart:24:33
This is not in the main.dart file but I am getting this.
Any help will be appreciated.
This gives null as shared preference object object haven't created till now, and you are using it somewhere.
You have to add Future as you do await for some background work
Future<Void> _initialize() async {
prefs = await SharedPreferences.getInstance();
}
Calling Function Like:
_initialize().then((value) => _readValues());
In "StoredData()" constructor you should use await also when calling "_initialize()" method:
await _initialize();
If not "_readValues()" would be called when sharedPreferences are not yet initialized.
But because use of async on constructors is not permitted, you should change initialize() like this:
_initialize() async {
prefs = await SharedPreferences.getInstance();
score = prefs.getInt("score") ?? 0;
storedScore = prefs.getInt("storedScore") ?? 0;
}
In your widget you can do something like this:
class MyWidget extends StatelessWidget{
#override
Widget build(BuildContext context){
return MaterialApp(
home: FutureBuilder(
future: StoredData._initialize(),
builder: (_,snap){
if (snap.connectionState==ConnectionState.done)
return Text("Settings loaded");
else
return Text("Loading settings...");
}
),);
}
}
class StoredData {
static SharedPreferences _prefs;
static int score;
static int storedScore;
final String uid;
StoredData(this.uid);
static Future _initialize() async {
_prefs = await SharedPreferences.getInstance();
score = _prefs.getInt("score") ?? 0;
storedScore = _prefs.getInt("storedScore") ?? 0;
}
}
StoredData(this.uid) {
_initialize().then((value) => _readValues());
shouldStoreInFirebase();}
Future<void> _initialize() async {
prefs = await SharedPreferences.getInstance();}
_readValues() {
score = prefs.getInt("score") ?? 1;
storedScore = prefs.getInt("storedScore") ?? 0;
}
Related
i am trying to add product to cart using local database but its giving error stack overflow
here is my cart provider class
class CartProvider with ChangeNotifier {
DBHelper db = DBHelper();
int _counter = 0;
int get counter => _counter;
double _totalPrice = 0.0;
double get totalPrice => _totalPrice;
late Future<List<Cart>> _cart;
Future<List<Cart>> get cart => _cart;
Future<List<Cart>> getData() async {
_cart = db.getCartList();
return _cart;
}
void _setPrefItems() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setInt('cart_item', _counter);
prefs.setDouble('total_price', _totalPrice);
notifyListeners();
}
void _getPrefsItems() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
_counter = prefs.getInt('cart_item') ?? 0;
_totalPrice = prefs.getDouble('total_price') ?? 0.0;
notifyListeners();
}
void addCounter (){
_counter++;
_setPrefItems();
notifyListeners();
}
void removeCounter (){
_counter--;
_setPrefItems();
notifyListeners();
}
int getCounter(){
_getPrefsItems();
_counter++;
return _counter;
}
void addTotalPrice (double productPrice){
_totalPrice = _totalPrice + productPrice;
_setPrefItems();
notifyListeners();
}
void removeTotalPrice (double productPrice){
_totalPrice = _totalPrice - productPrice;
_setPrefItems();
notifyListeners();
}
double getTotalPrice(){
_getPrefsItems();
return _totalPrice ;
}
}
here is how i am adding it to cart on button click i am getting product from product model class
InkWell(
onTap: () {
dbHelper.insert(
Cart(
id: widget.products.id,
productId: widget.products.id.toString(),
initialPrice: widget.products.price.round(),
productPrice: widget.products.price.round(),
image: widget.products.image,
productName: widget.products.title,
cartDescription: widget.products.description,
quantity: 1,
)
).then((value) {
cart.addTotalPrice(widget.products.price);
cart.addCounter();
print('add to cart');
}).onError((error, stackTrace) {
print(error.toString());
print('error');
});
my database class where i am creating local database where i insert and create database table
class DBHelper {
static Database? _db;
Future<Database?> get db async {
if (_db != null) {
return db;
}
_db = await initDatabase();
}
initDatabase() async {
io.Directory documentDirectory = await getApplicationDocumentsDirectory();
String path = join(documentDirectory.path, 'cart.db');
var db = await openDatabase(path, version: 1, onCreate: _oncreate);
return db;
}
_oncreate(Database db, int version) async {
await db.execute(
'CREATE TABLE cart (id INTEGER PRIMARY KEY, productId VARCHAR UNIQUE, productName TEXT, initialPrice INTEGER, productPrice INTEGER, quantity INTEGER, cartDescription TEXT, image TEXT)');
}
Future<Cart> insert(Cart cart) async {
var dbClient = await db;
await dbClient!.insert('cart', cart.toMap());
return cart;
}
Future<List<Cart>> getCartList() async {
var dbClient = await db;
final List<Map<String, Object?>> queryResult =
await dbClient!.query('cart');
return queryResult.map((e) => Cart.fromMap(e)).toList();
}
}
kindly help me out when i am adding it to cart its simply print
I/flutter ( 4595): Stack Overflow
on stack trace error
I/flutter ( 6159): #0 DBHelper.insert (package:heem/database/bd_helper.dart:34:19)
I/flutter ( 6159): <asynchronous suspension>
I/flutter ( 6159): #1 FutureExtensions.onError.<anonymous closure> (dart:async/future.dart:1013:15)
I/flutter ( 6159): <asynchronous suspension>
I think the problem is in this getter:
Future<Database?> get db async {
if (_db != null) {
return db;
}
_db = await initDatabase();
}
In return db; you have infinite recursion.
Generally speaking, getters that perform async work are a code smell. Also, what if somebody calls this getter multiple times in parallel?
In my opinion, you should either:
Expose initDatabase method. In every other method require that database is already initialized. This way, you're delegating the responsibility of ensuring initialization before any other operation is performed to the caller of this service (probably your business logic layer). Briefly documenting this assumptions would also be a good idea.
Create a private _ensureInitialized() method which will encapsulate all the logic necessary to ensure correctness:
Completer<Database>? _initCompleter;
Database? _database;
Future<Database> _ensureInitialized() async {
if(_database != null) return _database;
if(_initCompleter != null) return await _initCompleter!.future;
_initCompleter = Completer<Database>();
final database = await _initDatabase();
_database = database;
_initCompleter.complete(database);
return database;
}
Future<void> insert(...) async {
final database = await _ensureInitialized();
...
}
SharedPreferences prefs;
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Above is how it is initialized
prefs.setString('emailPrefs1', email).then((bool success) {
print('${prefs.getString('emailPrefs1')}');
});
Value is set successfully after this
_getPrefs() async {
prefs = await _prefs;
String emailPrefs1 = prefs.getString('emailPrefs1');
if (emailPrefs1 != null) {
setState(() {
emailController.text = emailPrefs1;
});
}
print(emailPrefs1);
}
But it returns null after initializing this activity in init state.
#override
void initState() {
super.initState();
_getPrefs();
}
I am using shared_preferences: ^0.5.6 version.
if you are sure your 'emailPref' is set, this should work:
class _MyHomePageState extends State<MyHomePage> {
TextEditingController _emailController;
SharedPreferences _prefs;
Future<SharedPreferences> _getPrefs() async{
return await SharedPreferences.getInstance();
}
#override
void initState(){
super.initState();
_emailController = TextEditingController();
_getPrefs().then((prefs){
_prefs = prefs; //If you need your SharedPreference Object later on
_emailController.text = prefs.getString('emailPrefs1');
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(child: Text(_emailController.text))
);
}
#override
void dispose() {
super.dispose();
_emailController.dispose();
}
}
How is the private _prefs being initialized? You can either share more of your code or just pull the value from what you definitely saved to.
void getPrefs() async {
prefs = await SharedPreferences.getInstance(); // this is what you saved to
String emailPrefs1 = prefs.getString('emailPrefs1');
print(emailPrefs1);
if (emailPrefs1 != null) {
setState(() {
emailController.text = emailPrefs1;
});
}
}
You can also print the value straight from the instance when you do save to confirm a successful save.
prefs.setString('emailPrefs1', email).then((bool success) {
print('${prefs.getString('emailPrefs1')}');
});
I have a page with this code:
class _HomeScreenState extends State<HomeScreen> {
bool isFirstLoading = true;
#override
void initState() {
super.initState();
if (isFirstLoading) {
getInfo();
setState(() {
isFirstLoading = false;
});
} else {
getInfoFromSharedPref();
}
}
Future<http.Response> getInfo() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
Loader.show(context,
isAppbarOverlay: true,
isBottomBarOverlay: true,
progressIndicator: CircularProgressIndicator());
var url = kLinkAPI + "/getInfo";
var response =
await http.post(url, headers: {"Content-Type": "application/json"});
var resObj = jsonDecode(response.body);
if (response != null) {
setState(() {
if (resObj.length > 0) {
address = resObj[0]['address'];
countryInfo = resObj[0]['country_info'];
phone = resObj[0]['phone'];
latitude = resObj[0]['latitude'];
longitude = resObj[0]['longitude'];
isFirstLoading = false;
prefs.setString('address', address);
prefs.setString('countryInfo', countryInfo);
prefs.setString('phone', phone);
prefs.setString('latitude', latitude);
prefs.setString('longitude', longitude);
}
});
}
Loader.hide();
}
void getInfoFromSharedPref() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
address = prefs.getString('address');
countryInfo = prefs.getString('countryInfo');
phone = prefs.getString('phone');
latitude = prefs.getString('latitude');
longitude = prefs.getString('longitude');
});
}
}
I would like to make sure that the first time I enter the page, the isFirstLoading variable is set to false and then calls the getInfo function with the http call while if it is false it takes from the shared preferences.
isFirstLoading is now always true
how could I solve?
I think you're overcomplicating your code. Let me know if this solves your issue.:
class _HomeScreenState extends State<HomeScreen> {
SharedPreferences prefs;
#override
void initState() {
super.initState();
getInfo();
}
// ...
}
Now, the first time this widget is inserted into the tree:
initState() will be called once.
Therefore, getInfo() will be called. getInfo() will make the http call and update the prefs variable using setState, which you have already done.
Whenever the widget is reloaded, the prefs variable will not be lost since it is a stateful widget.
Next, if you would like to save the preference settings locally instead of making an http call every time the user opens the app, you should handle that inside of getInfo() itself. Something like this:
getInfo() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.getBool("isFirstLoading") == false) {
// setState to update prefs variable
} else {
// make http call
// save prefs (optional)
// setState to update prefs variable
}
}
If I undestand correctly, you are trying to only call the getInfo method on the first load, and the getInfoFromSharedPref all the other time.
My suggestion is to save the isFirstLoading bool as a preference like so:
class _HomeScreenState extends State<HomeScreen> {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool isFirstLoading = prefs.getBool("isFirstLoading") ?? true;
#override
void initState() async {
super.initState();
if (isFirstLoading) {
await getInfo();
await prefs.setBool("isFirstLoading", false);
isFirstLoading = false;
} else {
getInfoFromSharedPref();
}
}
Future<http.Response> getInfo() async {
// …
}
void getInfoFromSharedPref() async {
// …
}
}
I am new in flutter.I try to learn SharedPreferences and i have this exception.
How can i solve this?
class _MyAppState extends State {
Future<SharedPreferences> prefs = SharedPreferences.getInstance();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
RaisedButton(
onPressed: () {addStringToSF();},
),
Text(getStringValuesSF()),
],
),
);
}
addStringToSF() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('stringValue', "abc");
}
getStringValuesSF() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String stringValue = prefs.getString('stringValue');
return stringValue;
}
}
default async function return dynamic we have to do type casting
Future<String> getStringValuesSF() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String stringValue = prefs.getString('stringValue');
return stringValue;
}
I will just extend answer from #Abhishek as I needed similar but didn't work as epxected on TextFormField.
So I made up a bare loadString method to get any kind of key from sharedPrefs:
Future<String> loadString(String key) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString(key) ?? '';
}
Next in the same class I created init form void to use above method (I still think this way of working with Future is bit not optimal in Dart, anyway..), this will load data into controller:
Future<void> _initForm() async {
final clientBusinessRegistrationID = await loadString('clientBusinessRegistrationID');
_clientBusinessRegistrationIDController.value =
_clientBusinessRegistrationIDController.value.copyWith(
text: clientBusinessRegistrationID);
}
I also added this block in same class:
SharedPreferences? preferences;
Future<void> initializePreference() async{
preferences = await SharedPreferences.getInstance();
}
and finally in initState() I call it and it works:
#override
void initState() {
super.initState();
// setupLocator();
initializePreference().whenComplete((){
setState(() {});
});
_clientBusinessRegistrationIDController.text = 'Initial';
_initForm();
}
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