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

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

Related

How to unit test a class that is created by provider?

So let's say I have a Counter class like this
class Counter extends ChangeNotifier {
int _i = 0;
int get myCounter => _i;
void increment() {
_i++;
notifyListeners();
}
void decrement() {
_i--;
notifyListeners();
}
}
I want to write a test file for it, so I expose its instance like this. The problem is, after I expose it, how do I access the instance of the class I just created? Like say, I increased _i value through a button, how will I access the instance that is created by Provider in order to test it?
I was looking to do the same but then I found this answer https://stackoverflow.com/a/67704136/8111212
Basically, you can get the context from a widget, then you can use the context to get the provider state
Btw, you should test a public variable like i instead of _i
Code sample:
testWidgets('showDialog', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(home: Material(child: Container())));
final BuildContext context = tester.element(find.byType(Scaffold)); // It could be final BuildContext context = tester.element(find.byType(Container)) depending on your app
final Counter provider = Provider.of<Counter>(context, listen: false);
expect(provider.i, equals(3));
});
You first initialize the Provider in your main.dart file using
ChangeNotifierProvider
after that you can use the class anywhere in your code by either using the Consumer widget or by using:
final counter = Provider.of<Counter>(context)
Here is a good post/tutorial about how to use Provider

Getting null value when used to check values in Shared preferences

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.

Flutter/Dart : How to wait for asynchronous task before app starts?

I am working on a dart application where I want to fetch the data present in cache (SharedPreferences) and then show it on UI (home screen) of the app.
Problem : Since SharedPreferences is an await call, my home page loads, tries to read the data and app crashes because data fetch has not yet happened from SharedPreferences, and app loads before that.
How can I not start the app until cache read from SharedPreferences is done?
This is required because I have to display data from SharedPreferences on home page of the app.
Various view files of my project call static function : MyService.getValue(key) which crashes as cacheResponseJson has not populated yet. I want to wait for SharedPreferences to complete before my app starts.
Class MyService {
String _cacheString;
static Map < String, dynamic > cacheResponseJson;
MyService() {
asyncInit();
}
Future < void > asyncInit() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
_cacheString = sharedPreferences.getString(“ConfigCache”);
cacheResponseJson = jsonDecode(ecsCacheString);
}
static String getValue(String key) {
return cacheResponseJson[key];
}
}
void main() {
MyService s = MyService();
}
Any help would be highly appreciated!
You can run code in your main() method, before the call to runApp() that kicks off your application.
For example:
void main() async {
WidgetsFlutterBinding.ensureInitialized(); // makes sure plugins are initialized
final sharedPreferences = MySharedPreferencesService(); // however you create your service
final config = await sharedPreferences.get('config');
runApp(MyApp(config: config));
}
Can you try wrapping the function asyncInit() in initstate then in the function then setstate the values
_cacheString = sharedPreferences.getString(“ConfigCache”);
cacheResponseJson = jsonDecode(ecsCacheString);
I hope it works.
avoid using initialization etc outside the runApp() function, you can create a singleton
class MyService{
MyService._oneTime();
static final _instance = MyService._oneTime();
factory MyService(){
return _instance;
}
Future <bool> asyncInit() async {
//do stuff
return true;
}
}
and incorporate that in the UI like this
runApp(
FutureBuilder(
future: MyService().asyncInit(),
builder: (_,snap){
if(snap.hasData){
//here you can use the MyService singleton and its members
return MaterialApp();
}
return CircularProgressIndicator();
},
)
);
if you take this approach you can do any UI related feedback for the user while the data loads

How to wait on getter in flutter?

Below is the code of a provider class. Whenever the app starts i want to get the forms which were saved in the shared preferences. However it is taking sometime to load the from sharedpreferences. So When i access the forms for the first time it is initially empty, im getting an empty list. Is there anyway to delay the getter until it has the objects of form model.
class FormProvider with ChangeNotifier {
FormProvider() {
loadformPreferences();
}
List<FormModel> _forms = [];
List<FormModel> get forms => _forms;
Future<void> saveForm(FormModel form) async {
_forms.add(form);
await saveformPreferences();
notifyListeners();
}
Future<void> saveformPreferences() async {
List<String> myforms = _forms.map((f) => json.encode(f.toJson())).toList();
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setStringList('forms', myforms);
}
Future<void> loadformPreferences() async {
// WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
var result = prefs.getStringList('forms');
if (result != null) {
_forms = result.map((f) => FormModel.fromJson(json.decode(f))).toList();
}
}
}
In Flutter, all actions related to building the UI must not be asynchronous (or expensive) in order to main a high frame-rate.
Thus, even if you could hypothetically find a way to "wait" for the results from SharedPreferences, this would not be desirable, since whatever waiting was done would block the UI from making progress.
Thus, there are a couple approaches to this common problem:
Handle initial state explicitly in the UI
The simplest solution is to explicitly handle the case where the Provider has not yet fetched its data, by representing some sort of initial state in your Provider. One easy way to do this is to initialize _forms to null instead of []. Then in your Widget.build method, you could do something specific (like show a loading spinner) when the result is null:
Widget build(BuildContext context) {
final provider = Provider.of<FormProvider>(context);
if (provider.forms == null) {
return CircularProgressIndicator();
}
// Otherwise, Do something useful with provider.forms
}
Construct your provider with the resolved data
Let's say that you don't use the FormProvider until the user performs an action, like click a button, at which point you push a new view onto the navigator with the FormProvider.
If you wish to guarantee that the FormProvider will always be initialized with the SharedPreferences values, then you can delay the construction of the new view until SharedPreferences has finished:
class MyButton extends StatelessWidget {
Widget build(BuildContext context) {
return Button(onClick: () async {
final forms = await _fetchFormsFromSharedPrefs();
Navigator.push(context, MaterialPageView(builder: (context) =>
Provider(create: (_) => FormProvider(forms))));
});
}
}

Purpose of MethodCall list in Flutter plugin unit tests

The default unit test setup when you create a plugin looks like this:
void main() {
const MethodChannel channel = MethodChannel(
'com.example/my_plugin');
setUp(() {
channel.setMockMethodCallHandler((MethodCall methodCall) async {
return '42';
});
});
tearDown(() {
channel.setMockMethodCallHandler(null);
});
test('getPlatformVersion', () async {
expect(await MyPlugin.platformVersion, '42');
});
}
However, in a lot of the source code I see people using a List<MethodCall> called log. Here is an example:
test('setPreferredOrientations control test', () async {
final List<MethodCall> log = <MethodCall>[];
SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
});
await SystemChrome.setPreferredOrientations(<DeviceOrientation>[
DeviceOrientation.portraitUp,
]);
expect(log, hasLength(1));
expect(log.single, isMethodCall(
'SystemChrome.setPreferredOrientations',
arguments: <String>['DeviceOrientation.portraitUp'],
));
});
I understand the mocking with setMockMethodCallHandler, but why use a list of MethodCall when you could just use one? If it were just one case I wouldn't may much attention, but I see the pattern over and over in the source code.
I believe the point is to verify that the method call handler was triggered exactly once (and therefore added ("logged") exactly one entry into the List<MethodCall>). If it were simply a MethodCall variable that changed from null to non-null, verifying that it was not triggered multiple times would be less straightforward.