Getting null value when used to check values in Shared preferences - flutter

I am using shared preferences package in flutter to store boolean values.
I have stored boolean value with key name.
But iam getting nosuchmenthodexception.
Edit: Code in text
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
SharedPreferences pref;
Future<void> addStringToSF() async {
pref = await SharedPreferences.getInstance();
}
#override
void initState() async {
super.initState();
await addStringToSF();
}
#override
Widget build(BuildContext context) {
bool present = pref.containsKey('name');
return MaterialApp(
debugShowCheckedModeBanner: false,
home: present == true ? Home() : Register(),
);
}
}

You have not waited for your future to complete. In other words, you read from shared preferences, but basically said "I don't care if it's done yet, I'll just continue". Well, if you don't wait for it to be done, your value is not set yet.
Since it's a Stateful widget, you could assume your variable is false until your prefs variable is actually set. Once you are sure it's set, call setState, the build function will run again and will now have a valid value.
Alternatively, you could use a FutureBuilder to build your widget conditionally based on whether the future completed yet. You can find more information about that approach here: What is a Future and how do I use it?
Especially as a beginner or when coming from a different language (so basically everybody) you should install the pedantic package. It will give you a lot of warnings and hints what to do and not to do. It would have warned you of the fact that you missed to await a Future in line 26. Sure, experienced develpers see it, but it is so much easier to let a computer do it first. It's like compile errors. Sure you could find them on your own, but why would you not want your compiler to tell them which line they are on, right?

You need to wait for getting SharedPreference instance because this is async method,You can get like below,
Define object above init,
SharedPreferences prefs;
Inside init method,
SharedPreferences.getInstance().then((SharedPreferences _prefs) {
prefs = _prefs;
setState(() {});
});
Refer for more detail

You need to wait for prefs to be initialized, then call containsKey() on it.
Ok, you have waited for prefs in adStringToSF but inside initState, you have not waited for adStringToSF, so build and adStringToSF executes concurrently.

Related

Flutter Cubit InitState

I am at the begin of my Cubit learning and i tried to create a "Liter-Tracker" with sharedPrefs. Everything works but not the init state. I have to press a Button first because I initialize the drinkValue with 0. I tried to return an Int with the value from shared prefs but this dont work :( May you help me?
This is my cubit:
class DrinkCubit extends Cubit<DrinkState> {
DrinkCubit() : super(DrinkState(drinkValue: 0));
Future<void> loadCounter() async {
final prefs = await SharedPreferences.getInstance();
state.drinkValue = (prefs.getInt('counter') ?? 0);
}
Future<int> loadInitCounter() async {
final prefs = await SharedPreferences.getInstance();
return state.drinkValue = (prefs.getInt('counter') ?? 0);
}
}
and this my cubit state:
class DrinkState {
int drinkValue;
int? amount;
DrinkState({
required this.drinkValue,
});
}
I also tried something like this in my MainPage, how i usually init my state with setState:
#override
void initState() {
super.initState();
BlocProvider.of<DrinkCubit>(context).loadCounter();
}
Context is not accessible in initstate, try using didChangeDependencies life cycle method Flutter get context in initState method
Firstly, I strongly advise you to avoid the StatefulWidget when you use BLoC, but it doesn't mean you can't use it at all. Just be careful because setState() can rebuild BlocProvider inside the stateful widget.
As for the initialization process, I suggest you use this approach on the BlocProvider.
class DrinkScreen extends StatelessWidget {
const DrinkScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => DrinkCubit()..loadCounter(), // Or call other initialization method
child: DrinkView(),
);
}
}
This approach works really well if you reuse this screen multiple times, for example, you redirect to DrinkScreen every time you want to fill data and you dispose of the screen afterward (Navigate.pop(), etc). This way you can automatically initialize the cubit every time you redirect into this screen, you don't need to use StatefulWidget to init the cubit.

How can I get a changenotifier using a futureprovider with riverpod?

I am using shared_preferences for my app and I have made a Settings class with helper methods.
As part of the Settings method I use Settings.create to generate my SharedPreferences.
It looks like this:
class Settings extends ChangeNotifier {
final SharedPreferences prefs;
Settings(this.prefs);
static Future<Settings> create() async {
var prefs = await SharedPreferences.getInstance();
return Settings(prefs);
}
// ### Helper Methods ###
}
I have used this post to try a solution and have come up with this:
FutureProvider<Settings> createSettings = FutureProvider<Settings>((ref) {
return Settings.create();
});
ChangeNotifierProvider<Settings> settingsProvider = ChangeNotifierProvider<Settings> ((ref) {
final settingsInstance = ref.watch(createSettings).maybeWhen(
data: (value) => value,
orElse: () => null,
);
return Settings(settingsInstance.prefs);
});
The problem I have now run into is that I get an Error because null is returned as long as the future has not completed.
I have hit a wall and am out of ideas. Does anyone have an idea on how to solve this?
Yeah, two options. Depending on the trade-offs you want to make.
Options
Option 1 (just wait)
In main.dart inside the Future<void> main() async {} function, just wait for the call to get shared prefs and then manually set the state in a your providers (using a state provider).
So that looks like:
(providers.dart)
final settingsProvider = StateProvider<Settings>((_) => defaultValue);
// ^ You can also make the above nullable if you don't have a reasonable default. But to be fair, this default will never be called if you're always setting it in main.
(main.dart)
Future<void> main() async {
final settings = await Settings.create();
final container = ProviderContainer();
container.read(settingsProvider.state).state = settings;
}
Option 2 (same idea, just don't wait)
The same as the above code, but don't wait in main. Just don't wait. Here's the difference:
(main.dart)
Future<void> main() async {
final container = ProviderContainer();
Settings.create().then((settings){
container.read(settingsProvider.state).state = settings;
});
// The rest of your code...
runApp();
}
Overview / Comparison
Option #1 is more simple to work with, but you may not be okay with waiting if you want a fast startup. But I doubt that it matters. In that case, if you have reasonable defaults, then use option #2
For the code on using Riverpod in main(), please reference this Github issue comment:
https://github.com/rrousselGit/riverpod/issues/202#issuecomment-731585273

How do you initialise a provider with data taken from shared preferences?

I am new to flutter and dart so the answer to this may be simple, or I may be going about this COMPLETELY the wrong way.
I basically want to use Provider for state management, and Shared Preferences for local storage.
I have a class called 'ResolutionsProvider' which contains a list of custom 'Resolution' objects
which are used throughout my app. It contains methods for adding and removing Resolutions and it all works fine.
The problem is that I want the data to be persistent if the user restarts the app...
I'm trying to use SharedPreferences for this so I have methods within 'ResolutionsProvider' to save data to, and load data from Shared Preferences...
I know through print calls that the data is saving to shared preferences correctly...
My (potentially wrong) thinking is that I need to achieve the following...
App is run
ResolutionsProvider is instantiated as a ChangeNotifierProvider
ResolutionsProvider.loadData() is run to populate List from SharedPreferences...
I've tried to call ResolutionsProvider.loadData() from within an initState but I get the following error...
E/flutter (30660): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: dependOnInheritedWidgetOfExactType<_InheritedProviderScope<ResolutionsProvider?>>() or dependOnInheritedElement() was called before _AppState.initState() completed.
E/flutter (30660): When an inherited widget changes, for example if the value of Theme.of() changes, its dependent widgets are rebuilt. If the dependent widget's reference to the inherited widget is in a constructor or an initState() method, then the rebuilt dependent widget will not reflect the changes in the inherited widget.
E/flutter (30660): Typically references to inherited widgets should occur in widget build() methods. Alternatively, initialization based on inherited widgets can be placed in the didChangeDependencies method, which is called after initState and whenever the dependencies change thereafter.
So i guess the question is two fold...
If my approach is correct, how do I call the 'loadData' method to populate my provider on booting the app if I can't do so within initState
If my approach is incorrect, what's the right approach to achieve my basic objective?
Here's my main.dart code
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await LocalStorage.init();
runApp(
ChangeNotifierProvider(
create: (context) => ResolutionsProvider(),
child: const App(),
),
);
}
Here's the relevant code from my App.dart
class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
#override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
#override
void initState() {
loadData();
print("app initialised");
super.initState();
}
void loadData() async {
await Provider.of<ResolutionsProvider>(context).loadResolutions();
}
#override
Widget build(BuildContext context) {
return MaterialApp(blah blah blah);
Here's the relevant parts of my ResolutionProvider.dart
class ResolutionsProvider extends ChangeNotifier {
List<Resolution> resolutions = [];
// Interfacing with Shared Preferences
loadResolutions() async {
resolutions = await LocalStorage.getResolutions();
print('${resolutions.length} resolutions loaded');
// notifyListeners();
}
saveResolutions() async {
LocalStorage.saveResolutions(resolutions);
}

Flutter: Async function in Getx Controller takes no effect when initialized

Updates:
2021/06/11 After hours of debugging yesterday, I confirmed that the problem is caused by aws amplify configuration: _configureAmplify(). Because the location of the amplify server was set wrong, so _configureAmplify() takes several seconds to work... and therefore, the readPost() function did not work on initialization, as it must run after _configureAmplify()...
2021/06/10I made changes to my code according to S. M. JAHANGIR's advice, and updated the question. The issue still presists. The value of posts is not updated when called in initialization and the data only shows up after reload. (if I commented out the _controller.readPost() in UI, the value of posts is always empty.
I have this page that loads information from aws amplify with getx implemented. However, I found out the readPost() async funtion in getx controller dart file is not reading from database, when the controller instance is initialized. I have to add a _controller.readPost() in UI file to make it work. And the data only shows up after a reload of that UI page...
Getx Controller dart file:
class ReadPostController extends GetxController {
var isLoading = true.obs;
var posts = <Posty>[].obs;
#override
void onInit() {
_configureAmplify();
await readPost();
super.onInit();
// print('show post return value: $posts');
}
void _configureAmplify() {
final provider = ModelProvider();
final dataStorePlugin = AmplifyDataStore(modelProvider: provider);
AmplifyStorageS3 storage = new AmplifyStorageS3();
AmplifyAuthCognito auth = new AmplifyAuthCognito();
AmplifyAPI apiRest = AmplifyAPI();
// Amplify.addPlugin(dataStorePlugin);
Amplify..addPlugins([dataStorePlugin, storage, auth, apiRest]);
Amplify.configure(amplifyconfig);
print('Amplify configured');
}
// read all posts from databases
Future readPost() async {
try {
isLoading(true);
var result = await Amplify.DataStore.query(Posty.classType);
print('finish loading request');
result = result.sublist(1);
posts.assignAll(result);
// print(the value of posts is $posts');
} finally {
isLoading(false);
}
}
#override
void onClose() {
// called just before the Controller is deleted from memory
super.onClose();
}
}
And in the UI part:
class TabBody extends StatelessWidget {
TabBody({Key? key}) : super(key: key);
final ReadPostController _controller = Get.put(ReadPostController());
#override
Widget build(BuildContext context) {
_controller.readPost();//if commented out, _controller.post is empty
return Container(
child: Obx(
() => Text('showing:${_controller.posts[1].title}'),
));
}
}
In my understanding, the readPost() function should be called when the ReadPost_controller is initiallized. And the UI will update when the posts = <Posty>[].obs changes. Guys, what am I doing wrong here?
First, when you are calling readPost on onInit you are not awaiting. So change it to:
onInit() async{
...
await readPost();
...
}
Secondly, posts is a RxList so you need to use the assignAll method to update it.
Therefore, in your readPost method, instead of posts.value = reault you need to use posts.assignAll(result)
Calling from the UI works because readPost every time the build method is called by the Flutter framework and actually the UI shows the data from every previous call.
I think try with GetBuilder instead of Obx.
GetBuilder<ReadPostController>(
builder: (value) => Text('showing:${value.posts[1].title}'),
)
and also use update(). in readPost() method.

Where do I put commands that run once at beginning?

I really miss C programming. Flutter is quite confusing.
Here is the problem:
We have a function within the Stateful class Home. That creates a page called myMenu.
class Home extends StatefulWidget {
myMenu createState() => myMenu();
}
class myMenu extends State<Home>
{
void myProblemFunction(String stringItem) async {
final db = await myDatabaseClass.instance.database;
...
}
Whenever myProblemFunction runs, it will instantiate a new database instance.
I just want to put this command once (i.e.:
final db = await myDatabaseClass.instance.database
) anywhere in my document (this is my main.dart file (by the way). Now, I could put it in the initState() - sure.. but that's a headache, because a) I would have to make it async, and I feel that will cause me problems, and b) declaring final db... (etc) in the initState() will not make the db variable visible to my functions.
What do I do?
what are the elements in myMenu Class that are triggered? Should I put this function inside the widget build method? But surely if the widget is refreshed, then this function will also be called (again) - I just want to call it once.
Thanks
you can use FutureBuilder
example:
FutureBuilder(
future: myDatabaseClass.instance.database,
builder: (BuildContext context,AsycnSnapshot snapshot){
//your code here
},
)
this will only load the Future once when the widget is built.
put your code that you want to run only once in initState():
#override
void initState() {
super.initState();
// TODO code you want to run only once
}