Flutter SharedPreferences Null check operator used on a null value - flutter

I've made a class for shared preferences. The class is as follows
class StorageUtil {
static StorageUtil? _storageInstance;
static SharedPreferences? _preferences;
static Future<StorageUtil?> getInstance() async {
if (_storageInstance == null) {
_storageInstance = StorageUtil();
}
if (_preferences == null) {
_preferences = await SharedPreferences.getInstance();
}
return _storageInstance;
}
addStringtoSF(String key, String value) async {
print(' inside sharedPreferences file $key $value'); // Receives data here
await _preferences!.setString(key,
value); //Unhandled Exception: Null check operator used on a null value
}
When ever i try to store the values i'm getting a error 'Null check operator used on a null value'
This is how i'm passing down the values to the store function. I'm receiving the data inside the function. But cannot store the values inside it. What causes this?
String? userResponse = json.encode(authResponse);
print('This is userResponse type');
_storageUtil.addStringtoSF('userData', userResponse);

Try adding this WidgetsFlutterBinding.ensureInitialized();
in the very first line of you main() method in main.dart file if not present.
The problem over here is that
The class has a static function that is responsible for the initialization of variables and can be accessed without an object of class StorageUtil.
When the nonstatic function is called you need to create an object of StorageUtil class and then access that function due to which the static variables are not initialized which are initialized in the static function hence null.
From the snippet of code it seems you are willing to make a singleton class here is the correct code for it:
class StorageUtil {
static StorageUtil storageInstance = StorageUtil._instance();
static SharedPreferences? _preferences;
StorageUtil._instance(){
getPreferences();
}
void getPreferences()async{
_preferences = await SharedPreferences.getInstance();
}
addStringtoSF(String key, String value) async {
print(' inside sharedPreferences file $key $value'); // Receives data here
await _preferences!.setString(key,
value);
}
}
Where ever you want the preference to be used just call:
final StorageUtil storage = StorageUtil.storageInstance;
storage.AnyNonStaticFunctionName()// call for methods in the StorageUtil Class
this is the one and only object that will exist throughout the application.
Or
if you don't want to change your class then just add this in all the nonstatic functions at the top which uses _preferences
And also add this null check
if (_preferences == null) {
_preferences = await SharedPreferences.getInstance();
}
because you may be having multiple instances of the StorageUtil making the _preferences variable null each time.

Add this line also under your print line, before calling _preferences!.
if (_preferences == null) {
_preferences = await SharedPreferences.getInstance();
}

Related

Flutter can't access data because it's potentially null

How can I define my function nullable? I get the following error on Flutter (InsertData is the function in repository.dart):
`lib/services/categoriesservices.dart:13:30: Error: Property
'InsertData' cannot be accessed on 'Repository?' because it is
potentially null.
'Repository' is from 'package:sqflite2/repositories/repository.dart'
('lib/repositories/repository.dart'). Try accessing using ?. instead.
return await _repository.InsertData.call(`
repository.dart seen below:
import 'package:sqflite/sqflite.dart';
import 'package:sqflite2/repositories/databaseconnection.dart';
class Repository {
DataBaseConnection? _dataBaseConnection;
Repository() {
//initialize database connection
_dataBaseConnection = DataBaseConnection();
}
static Database? _database;
Future<Database?> get database async {
if (_database != null) {
return _database;
}
_database = await _dataBaseConnection.setDatabase();
return database;
}
//create function inserting data to database
InsertData(table, data) async {
var connection = await database;
return await connection.insert(table, data);
}
}
The function is initialized as seen below:
import 'package:sqflite2/models/category.dart';
import 'package:sqflite2/repositories/repository.dart';
class CategoryService {
Repository? _repository;
CategoryService() {
_repository = Repository();
}
saveCategory(Categori category) async {
return await _repository.InsertData("categories", category.categoryMap());
}
}
What am I missing ? I thought I already initiliazed the Repository with (?)
You need to access the member functions, variables of your nullable object with ?
operator. Just declaring nullable will not satisfy the compiler. It can be null while accessing insertData function.
It performs a null check before accessing the function.
Try the below snippet with ? operator.
saveCategory(Categori category) async {
return await _repository?.InsertData("categories", category.categoryMap());
}
If you are certain that _repository object is not null while accessing the saveCategory(Categori category) function. You can use ! operator for force assurance that the object is not null (Not recommended).
return await _repository!.InsertData("categories", category.categoryMap());
You might also wanna look at late modifier
About nullable function
Return values
All functions return a value. If no return value is specified, the
statement return null; is implicitly appended to the function body.
Therefore, if you know the return type of your function, specify it. If the function may return null, use '?' after the return type.

Flutter - await/async on a List - why does this only work when not using declarations?

Still new to Flutter :(. Can anyone help...
I have a class that stores a bunch of project information. One part of this is a list of topics (for push notification), which it grabs from a JSON file.
I apply a getter for the list of topics, and when getting it it calls an async function which will return a List
Future<List<String>> _pntopics() async{
final _json = await http.get(Uri.parse(_topicsUrl));
if (_json.statusCode == 200) {
return (jsonDecode(_json.body));
}else {
return [""];
}
}
Future<List<String>> get topics => _pntopics();
In my main.dart file, it calls this value like so...
Future<List<String>> _topiclist = await projectvalues.topics;
The response is however empty, pressumably because it is a Future - so it is grabbing the empty value before it is filled.
But I can't remove the "Future" part from the async method, because asnc methods require a Future definition.
Then I decided to remove the declarations entirely:
_pntopics() async{
final _json = await http.get(Uri.parse(_topicsUrl));
if (_json.statusCode == 200) {
return (jsonDecode(_json.body));
}else {
return [""];
}
}
get topics => _pntopics();
and in main.dart, a general declaration...
var _topiclist = await projectvalues.topics;
...and this works!
So what declaration should I actually be using for this to work? I'm happy to not use declarations but we're always to declare everthing.
You should return back Future<List<String>> return types to the function and the getter but for _topicslist you must use var, final or List<String> declaration because:
(await Future<T>) == T
i.e.
var _topiclist = await projectvalues.topics; // The type of _topiclist is List<String>
final _topiclist = await projectvalues.topics; // The type of _topiclist is List<String>
UPDATE
Your code should be:
Future<List<String>> _pntopics() async{
final _json = await http.get(Uri.parse(_topicsUrl));
if (_json.statusCode == 200) {
return List<String>.from(jsonDecode(_json.body));
}else {
return <String>[""];
}
}
Doing this you force _pnptopics returns List<String> as jsonDecode returns List<dynamic>.
P.S. It is good practice do not use dynamic types where they can be changed to specified types.

Flutter Null Safe Config Class with shared_preferences

In flutter 1.x, I implemented a Config class using the Flutter shared_preferences package; the code looks like this:
import 'package:shared_preferences/shared_preferences.dart';
class Config {
static final Config _config = Config._internal();
factory Config() => _config;
final accessTokenKey = 'accessToken';
String _accessToken;
SharedPreferences prefs;
Config._internal() {
loadData();
}
void loadData() async {
prefs = await SharedPreferences.getInstance();
_accessToken = prefs.getString(accessTokenKey) ?? '';
}
String get accessToken {
return _accessToken;
}
set accessToken(String accessToken) {
_accessToken = accessToken;
_saveString(accessTokenKey, accessToken);
}
_saveString(String key, String value, {String printValue = ''}) {
String printVal = printValue.length > 0 ? printValue : value;
prefs.setString(key, value);
}
}
I’m creating a new project in Flutter 2.x and trying to use the same code, but due to changes associated with null safety I’m having some difficulty getting the updated code just right.
The updated documentation for the package says to initialize the _prefs object like this:
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Then create a local prefs object using:
final SharedPreferences prefs = await _prefs;
This is fine, but I don’t want to have to make every class method that uses shared_preferences async then recreate the variable. At the same time I can’t create it as a class variable without initializing it first. Can someone please show me a cleaner way to do this, or do I just have to redeclare it every time I use it?
Also, how do I initialize the config object in my other classes? In my 1.x code, I would just do this:
final Config config = new Config();
then start accessing the properties of the config object. How do I initialize it with all of the async code in the class now?
Here’s where the updated code is today:
import 'package:shared_preferences/shared_preferences.dart';
import '../models/device.dart';
class Config {
static final Config _config = Config._internal();
factory Config() => _config;
final accessTokenKey = 'accessToken';
String _accessToken = '';
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Config._internal() {
print('Config constructor');
loadData();
}
Future<void> loadData() async {
final SharedPreferences prefs = await _prefs;
_accessToken = prefs.getString(accessTokenKey) ?? '';
}
String get accessToken {
return _accessToken;
}
set accessToken(String accessToken) {
_accessToken = accessToken;
_saveString(accessTokenKey, accessToken);
}
_saveString(String key, String value, {String printValue = ''}) {
String printVal = printValue.length > 0 ? printValue : value;
print('Config: _saveString("$key", "$printVal")');
final SharedPreferences prefs = await _prefs;
prefs.setString(key, value);
}
}
You can get instance of SharedPreferences as static field in init method:
static SharedPreferences? _prefs; //or: static late SharedPreferences _prefs;
static init() async {
_prefs = await SharedPreferences.getInstance();
}
And call init() somewhere like in build() method of first widget run, for once.Now you can use _prefs everywhere as you want.
If I want to show you a complete class to use SharedPreferences, it looks like this:
import 'package:shared_preferences/shared_preferences.dart';
class SharedPreferencesRepository {
static SharedPreferences? _prefs;
static init() async {
_prefs = await SharedPreferences.getInstance();
}
static putInteger(String key, int value) {
if (_prefs != null) _prefs!.setInt(key, value);
}
static int getInteger(String key) {
return _prefs == null ? 0 : _prefs!.getInt(key) ?? 0;
}
static putString(String key, String value) {
if (_prefs != null) _prefs!.setString(key, value);
}
static String getString(String key) {
return _prefs == null ? 'DEFAULT_VALUE' : _prefs!.getString(key) ?? "";
}
static putBool(String key, bool value) {
if (_prefs != null) _prefs!.setBool(key, value);
}
static bool getBool(String key) {
return _prefs == null ? false : _prefs!.getBool(key) ?? false;
}
}
I hope this useful for you.
If you need to wait for some async work to finish before getting an instance of a class, consider using a static method (not a factory constructor, since constructors must always return the base type).
You can use late fields to allow them to be non-null before you initialize them:
class Config {
late String _accessToken;
String get accessToken => _accessToken;
Config._(); // private constructor to prevent accidental creation
static Future<Config> create() async {
final config = Config();
final preferences = await SharedPreferences.getInstance();
config._accessToken = await preferences.getString('<your key>');
return config;
}
}
If you want to make sure this is initialized before running your app, you can initialize it in your main() method before you call runApp() to give control to the Flutter framework:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); // make sure all plugins loaded etc.
final config = await Config.create();
print(config.accessToken);
runApp(MyApp());
}

How to save data of type bool in shared_preferences flutter

I created a separate calss page to working with shared preferences from all the different application pages. Save or edit data. I can save String data with ease, but I am facing a problem saving data of type bool. I try to save data of type bool to store the status of the user logged in or not. I searched for solutions for a long time, but couldn't find.
full code:
import 'package:shared_preferences/shared_preferences.dart';
class MyPreferences {
static const ID = "id";
static const STATE = "state";
static final MyPreferences instance = MyPreferences._internal();
static SharedPreferences _sharedPreferences;
String id = "";
String state = "";
MyPreferences._internal() {}
factory MyPreferences() => instance;
Future<SharedPreferences> get preferences async {
if (_sharedPreferences != null) {
return _sharedPreferences;
} else {
_sharedPreferences = await SharedPreferences.getInstance();
state = _sharedPreferences.getString(STATE);
id = _sharedPreferences.getString(ID);
return _sharedPreferences;
}
}
Future<bool> commit() async {
await _sharedPreferences.setString(STATE, state);
await _sharedPreferences.setString(ID, id);
}
Future<MyPreferences> init() async {
_sharedPreferences = await preferences;
return this;
}
}
Can somebody help me to make bool data.
thank you
Just add a couple methods to your class.
void updateLoggedIn(bool value) {
_sharedPreferences.setBool('logged_in', value);
}
bool isLoggedIn() => _sharedPreferences.getBool('logged_in') ?? false;
Then on login just run
MyPreferences.instance.updateLoggedIn(true)
And the same thing passing in false on logout.
Then whenever you want to check logged in status just run
if(MyPreferences.instance.isLoggedIn()) {
// whatever needs to happen
}

Flutter: I want to create a global variant. After close the app, it can be saved for the next time I open the app again

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Global {
static SharedPreferences _prefs;
static String key;
static Future init() async {
WidgetsFlutterBinding.ensureInitialized();
_prefs = await SharedPreferences.getInstance();
var _key = _prefs.getString("key");
if (_key != null) {
key = _key;
}
}
static saveKey(value) => _prefs.setString("key", value);
}
Here I create a global class. And I set and get value by this way.
Global.key = value; //set
Global.key; //get
But after I close the app, the value is gone. Any suggestions?
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Global {
static SharedPreferences _prefs;
static String key;
String get init => key; // adding a getter
static Future init() async {
WidgetsFlutterBinding.ensureInitialized();
_prefs = await SharedPreferences.getInstance();
var _key = _prefs.getString("key");
if (_key != null) {
key = _key;
}
}
static saveKey(value) => _prefs.setString("key", value);
}
Global().init =value; or Global.init = value; //set
Global().init; //get Global.init; //get
Are you ever calling your Init function in your Main method?
void main() async {
await Global.init();
runApp(MyApp());
}
Assuming you're doing that, the other mistake here is how you're trying to store your value. Use the saveKey function you have in place that's what it's there for. Throw this on one of your pages.
ElevatedButton(
onPressed: () {
Global.saveKey('Test');
},
child: Text('Test'),
),
Your Global class was perfectly fine, its how you were trying to use it. Try this though, only change is that I included a null check so you don't have to create an extra variable that's not used. Also added a print statement that will print the stored value when you re-start the app. It all works fine on my end.
class Global {
static SharedPreferences _prefs;
static String key;
static Future init() async {
WidgetsFlutterBinding.ensureInitialized();
_prefs = await SharedPreferences.getInstance();
key = _prefs.getString('key') ?? ''; // null check here
debugPrint(key);
}
static saveKey(value) => _prefs.setString("key", value);
}