Flutter SharedPreferences non-nullable instance fields - flutter

I have a problem when I want to using the shared preferences to store and display the value. On the code, I would need to create the public class for the bellow function to use.
However it would return the follow error:
error: Non-nullable instance field 'sharedPreferences' must be initialized. (not_initialized_non_nullable_instance_field at [login] lib/main.dart:32)
SharedPreferences sharedPreferences; //line:32
Here is my full code.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:login/login_page.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Code Land",
debugShowCheckedModeBanner: false,
home: MyHomePage(),
theme: ThemeData(
accentColor: Colors.white70
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
SharedPreferences sharedPreferences;
#override
void initState() {
super.initState();
checkLoginStatus();
}
checkLoginStatus() async {
sharedPreferences = await SharedPreferences.getInstance();
if(sharedPreferences.getString("token") == null) {
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (BuildContext context) => LoginPage()), (Route<dynamic> route) => false);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Code Land", style: TextStyle(color: Colors.white)),
actions: <Widget>[
FlatButton(
onPressed: () {
sharedPreferences.clear();
sharedPreferences.commit();
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (BuildContext context) => LoginPage()), (Route<dynamic> route) => false);
},
child: Text("Log Out", style: TextStyle(color: Colors.white)),
),
],
),
body: Center(child: Text("Main Page")),
drawer: Drawer(),
);
}
}```

change the line like this for the compiler to know that you will initialize it later:
late SharedPreferences sharedPreferences;

This should do the trick, if you don't intend to initialize sharedPref right away.
SharedPreferences? sharedPreferences;
Dart sound null safety has been enabled as default in flutter since 2.2 if I'm correct.
Check out this article for a starter on the topic.
https://dart.dev/null-safety

From the Dart documentation: "The late modifier lets you use non-nullable types and final in places you otherwise might not be able to, at the expense of runtime checking. It also gives you lazy-initialized fields." https://dart.dev/null-safety/understanding-null-safety
Your SharedPreferences declaration should have the late modifier:
late SharedPreferences sharedPreferences;

Related

flutter - Variable that changed the value using sharedPreferences does not work properly -Has a simple example code

I created a button that allows users to change the size of letters.
I hope that the font size value will remain changed even if I run the app again.
But in my code, it goes back to its original state.I made a simple code of the problem I faced. I made three files.
1.HomePage.dart
import 'dart:async';
import 'dart:convert';
// import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import './main.dart';
import './count_page.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late CountPage _countPage;
#override
Widget build(BuildContext context) {
String prefsFont = "prefs_font";
_countPage = Provider.of<CountPage>(context, listen: true);
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.green,
title: Text('Test_Code'),
centerTitle: true, // 중앙 정렬
elevation: 0.0,
),
body: Container(
color: Colors.white,
child: Text('Font_size',style: TextStyle(fontSize: _countPage.font),),
),
bottomNavigationBar: BottomAppBar(
color: Colors.lime,
child: Container(
height: 200,
child: Row(
children: [
IconButton(
onPressed: () async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setDouble('prefs_font', 10);
setState(() {
_countPage.font = (prefs.getDouble('prefs_font') ?? 40) ;
});
},
icon: Icon(Icons.looks_one_outlined)),
IconButton(
onPressed: () async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setDouble('prefs_font', 40);
setState(() {
_countPage.font = (prefs.getDouble('prefs_font') ?? 40) ;
});
},
icon: Icon(Icons.looks_two_outlined)),
IconButton(
onPressed: () async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setDouble('prefs_font', 80);
setState(() {
_countPage.font = (prefs.getDouble('prefs_font') ?? 40) ;
});
},
icon: Icon(Icons.looks_3_outlined)),
],
),
),
),
);
}
}
2.main.dart
import 'dart:convert';
// import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import './HomePage.dart';
import 'count_page.dart';
void main() {
// WidgetsFlutterBinding.ensureInitialized();
// MobileAds.instance.initialize();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => CountPage(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
),
home: HomePage(),
),
);
}
}
3.count_page.dart
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class CountPage extends ChangeNotifier {
double _font = 40;
double get font => _font;
set font(double font) {
_font = font;
}
}
I made it small so that the composition is similar to the project I am working on.
The value changes even if you press the button, but it goes back to the beginning when you run the app again.
What is the problem?
So basically what's happening here is, you're only storing the font inside your SharedPrefrence. But you're not fetching it back when the app starts. Your CountPage class isn't storing the font size. It's shared preference that's storing it. So you have to just fetch the data from SharedPrefrence on the app start. And then use it in your code. A small example will be
Initialize SharedPrefrence so you can use later.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final sharedPref = await SharedPrefrence.getInstance(); // Instance will be created.
runApp(const MyApp());
}
2.Then make your class use SharedPrefrence instance by default
class CountPage extends ChangeNotifier {
final SharedPrefrence pref;
CountPage({required this.pref});
double get font => pref.getDouble('prefs_font') ?? 40;
setFont(double font) async {
await prefs.setDouble('prefs_font', font);
notifyListeners(); //This is necessary so that when the value changes the UI gets an update.
}
}
Then pass the SharedPrefrence instance to that class, so it can use it.
class MyApp extends StatelessWidget {
final SharedPrefrence pref;
const MyApp({Key? key, required this.pref}) : super(key: key); // Getting the instance from main.
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => CountPage({pref: pref}),//Passing the instance.
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
),
home: HomePage(),
),
);
}
}
Also on your save button it looks like you're using a getter to set the value, which shouldn't work. But now you can just call it like this
onPressed: () async {
setState(() { // Now this setState is optional bcz you're saying the notifier to notify it's listeners, whenever you're setting the value.
await _countPage.setFont(\* font size for this button */);
});
},

Flutter splash screen error - Navigator operation requested with a context that does not include a Navigator. How can I solve this error

Edit: (main.dart)
Added Sentry which actually starts the app
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = _sentryDSN;
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
// We recommend adjusting this value in production.
options.tracesSampleRate = _sentryTracesSampleRate;
options.attachStacktrace = true;
options.enableAppLifecycleBreadcrumbs = true;
},
appRunner: () => runApp(const SplashScreen()),
);
// or define SENTRY_DSN via Dart environment variable (--dart-define)
}
New to flutter, creating a splash screen to an app that was built with MaterialApp but getting an error. HOw can I solve this without a onPress function
Error:
Exception has occurred.
FlutterError (Navigator operation requested with a context that does not include a Navigator.
The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.)
import 'package:flutter/material.dart';
import 'package:loopcycle/screens/loopcycle_main.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
_navigateToMainApp();
}
void _navigateToMainApp() async {
await Future.delayed(const Duration(milliseconds: 2000), () {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const LoopcycleMainApp()));
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(
builder: (context) => const Center(
child: Text("test"),
)),
);
}
}
Thank you in advance.
EDIT: I changed the solution after you provided more information about the code.
This error is happening because you are using a context that does not have a Navigator in it, this is happening probrably because the widget that you are getting the context is parent of the MaterialApp() widget, to solve it you should create another widget that is a child of the MaterialApp() instead of using the parent widget, let me give you an example instead:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: GestureDetector(
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SomeWidget(),
),
),
child: Container(
height: 300,
width: 300,
color: Colors.red,
),
),
);
}
}
This may give an error because you are using the context of a widget that is the parent of the MaterialApp() widget, to solve it just create another widget that is a child of MaterialApp().
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: AnotherWidget(),
);
}
}
class AnotherWidget extends StatelessWidget {
const AnotherWidget({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SomeWidget(),
),
),
child: Container(
height: 300,
width: 300,
color: Colors.red,
),
),
);
}
}
I was playing with your code, and fixed it for you, and there are basically two ways to solve it, you can create a MaterialApp() before calling the SplashScreen() in the runApp() function like so:
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:loopcycle/screens/loopcycle_main.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = _sentryDSN;
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
// We recommend adjusting this value in production.
options.tracesSampleRate = _sentryTracesSampleRate;
options.attachStacktrace = true;
options.enableAppLifecycleBreadcrumbs = true;
},
appRunner: () => runApp(
const MaterialApp(
home: SplashScreen(),
),
),
);
// or define SENTRY_DSN via Dart environment variable (--dart-define)
}
class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
_navigateToMainApp();
}
void _navigateToMainApp() async {
await Future.delayed(const Duration(milliseconds: 2000), () {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const LoopcycleMainApp()));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (context) => const Center(
child: Text("test"),
)),
);
}
}
Or you can create an intermediate widget to hold the MaterialApp() and then inside this widget you can call SplashScreen(), like so:
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:loopcycle/screens/loopcycle_main.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = _sentryDSN;
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
// We recommend adjusting this value in production.
options.tracesSampleRate = _sentryTracesSampleRate;
options.attachStacktrace = true;
options.enableAppLifecycleBreadcrumbs = true;
},
appRunner: () => runApp(const MyApp()),
);
// or define SENTRY_DSN via Dart environment variable (--dart-define)
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: SplashScreen(),
);
}
}
class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
_navigateToMainApp();
}
void _navigateToMainApp() async {
await Future.delayed(const Duration(milliseconds: 2000), () {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const LoopcycleMainApp()));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (context) => const Center(
child: Text("test"),
)),
);
}
}
In this second solution, the intermediate widget is the MyApp() widget, and in my opinion, I consider this solution as being the best one for your problem, because if you ever wanted to load a different screen based on the different states, for example:
If a user is signed in you load a home page, and if a user is not signed in you load a sign up page.
Doing this, or anything similar is much easier when you have this intermediate widget that holds the MaterialApp(), and you can even create some logic to integrate the Splash Screen too, but I don't know what you are trying to achieve, so pick the solution you find the best for your problem.

Flutter switch animation is not showing in Alert Dialog

I have an alert dialog with a switch. If I press the switch, the boolean changes like I want it to (I have it set so the homepage background color changes based on the bool), but the switch animation does not register until I close out of the alert dialog and re-open it again. I tried wrapping it in a StatefulBuilder like I saw in another post, but in that case, the animation works but the bool does not change. I am trying to share this bool value throughout my app, so I'm using Shared Preferences.
here is the full code
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage();
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool bulb = false;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: bulb ? Colors.white : Colors.blue,
appBar: AppBar(actions: <Widget>[
IconButton(
icon: Icon(Icons.settings),
onPressed: () {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
content: Column(children: [
Switch(
value: bulb,
onChanged: (bool isChecked) async {
final prefs = await SharedPreferences.getInstance();
setState(() {
bulb = isChecked;
prefs.setBool('bulb', isChecked);
});
},
),
StatefulBuilder(builder:
(BuildContext context, StateSetter setState) {
return Column(children: [
Switch(
value: bulb,
onChanged: (bool isChecked) async {
final prefs =
await SharedPreferences.getInstance();
setState(() {
bulb = isChecked;
prefs.setBool('bulb', isChecked);
});
},
),
]);
})
])));
})
]),
body: Center(),
);
}
}
Solution:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage();
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool bulb = false;
Future openDialog() => showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setState) => AlertDialog(
content: Column(children: [
Switch(
value: bulb,
onChanged: (bool isChecked) async {
final prefs = await SharedPreferences.getInstance();
setState(() {
bulb = isChecked;
prefs.setBool('bulb', isChecked);
});
},
),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
child: Text("Save")),
]))));
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: bulb ? Colors.white : Colors.blue,
appBar: AppBar(actions: <Widget>[
IconButton(
icon: Icon(Icons.settings),
onPressed: () async {
await openDialog();
setState(() {});
}),
]),
body: Center(),
);
}
}
You have to use the StatefulBuilder itself.
Now, when you are calling setState inside the dialog, only the state inside the dialog is changing. The state of the screen remains the same. This is why switch changed animation is happening but the bulb value in the screen is not changing.
The workaround is, you can use a callback function to call setState in the screen too, when the value of switch changes.

What is the proper way of using SharedPreferences with Provider in Flutter?

I am newbie to state management using provider in flutter.
I've created a model named as Counter:
import 'package:flutter/foundation.dart';
class Counter with ChangeNotifier {
int value = 0;
void increment() {
value++;
notifyListeners();
}
void decrement() {
value--;
notifyListeners();
}
}
Now when value changes I can save it locally using SharedPreferences in order to start from that value next time.
But, I do not know what would be a proper way of loading data from local and set value in Counter class.
Should I load saved data in main.dart file when app is initalized and then setValue to that data?
Or are there any solutions, for example, loading data directly in my Counter class?
create a SharedPreferencesProvider
import 'package:shared_preferences/shared_preferences.dart';
class SharedPreferencesProvider {
final Future<SharedPreferences> sharedPreferences;
SharedPreferencesProvider(this.sharedPreferences);
Stream<SharedPreferences> get prefsState => sharedPreferences.asStream();
}
then create a Provider and with a StreamProvider as shown below
return MultiProvider(
providers: [
Provider<SharedPreferencesProvider>(create: (_) => SharedPreferencesProvider(SharedPreferences.getInstance())),
StreamProvider(create: (context) => context.read<SharedPreferencesProvider>().prefsState, initialData: null)
then consume the state within a Widget build with a context.watch
#override
Widget build(BuildContext context) {
sharedPrefs = context.watch<SharedPreferences>();
Try to use the future builder and then set it to the provider and be able to use SharedPreferences everywhere in the app:
#override
Widget build(BuildContext context) {
return FutureBuilder<SharedPreferences>(
future: SharedPreferences.getInstance(),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data != null) {
return MultiProvider(providers: [
Provider<SharedPreferences>(
create: (context) => snapshot.data!,
),
],
);
}
},
);
}
And you can use context.read() everywhere.
The question is leaning toward opinion. I'm also new to flutter -- the below may not be the best way, but it does work, so maybe it will help someone.
If it's the top level app, you can initialize the counter before actually using it, displaying a loading page during the load time (imperceptible in this case). You must include the first runApp however, otherwise shared_preferences will not be able to correctly access the file containing these preferences on the device.
A similar thing can be done with with FutureBuilder, but you must await a delay prior to attempting to read from shared_preferences.
(I don't think the loading page or delay are necessary if you aren't using the widget as your top level widget, which would probably be better anyway. In that case, probably FutureBuilder would be the correct solution. (?))
To note:
I added an async "constructor" to the Counter class that initializes from the shared_preferences.
I access the Counter via provider library in _MyHomePageState.build with context.watch<Counter>(), which causes this to rebuild on changes (without requiring calls to setState.
I've added async Counter._updatePreferences which is called in Counter.increment and Counter.decrement, which saves the current value of the Counter to the shared_preferences.
Imports and main for first method
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
Future<void> main() async {
// run "loading" app while awaiting counter, then run app again
runApp(
const MaterialApp(
home: Center(
child: Text('Loading'),
),
)
);
final Counter counter = await Counter.fromPreferences();
runApp(
ChangeNotifierProvider<Counter>.value(
value: counter,
child: const MyApp(),
)
);
}
Imports and main (with FutureBuilder)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
// Get counter in future builder
runApp(
FutureBuilder<Counter>(
future: Counter.fromPreferences(),
builder: (BuildContext context, AsyncSnapshot<Counter> snapshot) {
Widget returnWidget = const MaterialApp(
home: Center(
child: Text('Loading'),
),
);
if (snapshot.connectionState == ConnectionState.waiting) {
} else if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
print(snapshot.error);
} else if (snapshot.hasData) {
final Counter counter = snapshot.data!;
returnWidget = ChangeNotifierProvider<Counter>.value(
value: counter,
child: const MyApp(),
);
} else {
print('No data');
}
} else if (snapshot.connectionState == ConnectionState.none) {
print('null future');
} else {
print(snapshot.connectionState);
}
return returnWidget;
},
),
);
}
MyApp and MyHomePage
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Counter App',
home: MyHomePage(title: 'Counter App Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
final Counter counter = context.watch<Counter>();
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'${counter.value}',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <FloatingActionButton>[
FloatingActionButton(
onPressed: counter.increment,
child: const Icon(Icons.add),
),
FloatingActionButton(
onPressed: counter.decrement,
child: const Icon(Icons.remove),
),
],
),
);
}
}
Counter Class (ChangeNotifier)
class Counter extends ChangeNotifier {
int value = 0;
static Future<Counter> fromPreferences() async {
final Counter counter = Counter();
// Must be included if using the FutureBuilder
// await Future<void>.delayed(Duration.zero, () {});
final SharedPreferences prefs = await SharedPreferences.getInstance();
final int value = prefs.getInt('counterValue') ?? 0;
counter.value = value;
return counter;
}
Future<void> _updatePreferences() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setInt('counterValue', value);
}
void increment() {
value++;
notifyListeners();
_updatePreferences();
}
void decrement() {
value--;
notifyListeners();
_updatePreferences();
}
}
Complete Example
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
Future<void> main() async {
// run "loading" app while awaiting counter, then run app again
runApp(
const MaterialApp(
home: Center(
child: Text('Loading'),
),
)
);
final Counter counter = await Counter.fromPreferences();
runApp(
ChangeNotifierProvider<Counter>.value(
value: counter,
child: const MyApp(),
)
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Counter App',
home: MyHomePage(title: 'Counter App Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
final Counter counter = context.watch<Counter>();
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'${counter.value}',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <FloatingActionButton>[
FloatingActionButton(
onPressed: counter.increment,
child: const Icon(Icons.add),
),
FloatingActionButton(
onPressed: counter.decrement,
child: const Icon(Icons.remove),
),
],
),
);
}
}
class Counter extends ChangeNotifier {
int value = 0;
static Future<Counter> fromPreferences() async {
final Counter counter = Counter();
// Must be included if using the FutureBuilder
// await Future<void>.delayed(Duration.zero, () {});
final SharedPreferences prefs = await SharedPreferences.getInstance();
final int value = prefs.getInt('counterValue') ?? 0;
counter.value = value;
return counter;
}
Future<void> _updatePreferences() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setInt('counterValue', value);
}
void increment() {
value++;
notifyListeners();
_updatePreferences();
}
void decrement() {
value--;
notifyListeners();
_updatePreferences();
}
}
Use the shared_preferences plugin
enter link description here
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SharedPreferences Demo',
home: SharedPreferencesDemo(),
);
}
}
class SharedPreferencesDemo extends StatefulWidget {
SharedPreferencesDemo({Key key}) : super(key: key);
#override
SharedPreferencesDemoState createState() => SharedPreferencesDemoState();
}
class SharedPreferencesDemoState extends State<SharedPreferencesDemo> {
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Future<int> _counter;
Future<void> _incrementCounter() async {
final SharedPreferences prefs = await _prefs;
final int counter = (prefs.getInt('counter') ?? 0) + 1;
setState(() {
_counter = prefs.setInt("counter", counter).then((bool success) {
return counter;
});
});
}
#override
void initState() {
super.initState();
_counter = _prefs.then((SharedPreferences prefs) {
return (prefs.getInt('counter') ?? 0);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("SharedPreferences Demo"),
),
body: Center(
child: FutureBuilder<int>(
future: _counter,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const CircularProgressIndicator();
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
return Text(
'Button tapped ${snapshot.data} time${snapshot.data == 1 ? '' : 's'}.\n\n'
'This should persist across restarts.',
);
}
}
})),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
Reference site : https://pub.dev/packages/shared_preferences#-example-tab-

Flutter : Navigator operation requested with a context that does not include a Navigator

I have a scenario wherein I check the value of SharePreferences based on the value it will redirect the user to HomePage or LandingPage. I am not sure where did I got wrong? but I am getting this error below: I guess its not getting the context right any idea how do I get it?.
Unhandled Exception: Navigator operation requested with a context that does not include a Navigator.
E/flutter (11533): The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.
Here is my code:
import 'package:credit/src/pages/landing.dart';
import 'package:flutter/material.dart';
import 'package:credit/src/pages/credit/home.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
_LoadingPageState createState() => _LoadingPageState();
}
class _LoadingPageState extends State<MyApp> {
#override
void initState() {
super.initState();
getUserStatus().then((userStatus) {
if (userStatus == null) {
Navigator.of(context)
.push(MaterialPageRoute<Null>(builder: (BuildContext context) {
return LandingPage();
}));
} else {
Navigator.of(context)
.push(MaterialPageRoute<Null>(builder: (BuildContext context) {
return HomePage();
}));
}
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: CircularProgressIndicator(),
));
}
}
Future<String> getUserStatus() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String userStatus = prefs.getString('userstatus');
print("==On Load Check ==");
print(userStatus);
return userStatus;
}
When you call Navigator.of(context) framework goes up in widget tree attached to provided context and tries to find the closest Navigator.
The widget tree you showed does not have one, so you need to include Navigator in the widget tree.
Easiest option is to use MaterialApp with your widget passed as home. MaterialApp is creating navigator inside itself. (CupertinoApp does it too)
Updated code from original example:
import 'package:credit/src/pages/landing.dart';
import 'package:flutter/material.dart';
import 'package:credit/src/pages/credit/home.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: LoadingPage(),
);
}
}
class LoadingPage extends StatefulWidget {
LoadingPage({Key key}) : super(key: key);
_LoadingPageState createState() => _LoadingPageState();
}
class _LoadingPageState extends State<LoadingPage> { // note type update
#override
void initState() {
super.initState();
getUserStatus().then((userStatus) {
if (userStatus == null) {
Navigator.of(context)
.push(MaterialPageRoute<Null>(builder: (BuildContext context) {
return LandingPage();
}));
} else {
Navigator.of(context)
.push(MaterialPageRoute<Null>(builder: (BuildContext context) {
return HomePage();
}));
}
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: CircularProgressIndicator(),
));
}
}
Future<String> getUserStatus() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String userStatus = prefs.getString('userstatus');
print("==On Load Check ==");
print(userStatus);
return userStatus;
}
I have changed my code from
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo App',
theme: ThemeData(
primarySwatch: white,
scaffoldBackgroundColor: Colors.white,
),
home: Scaffold(
appBar: AppBar(
title: Text('Demo App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
HomeScreen(title: 'Demo Home')));
},
child: Text('Open Home Screen'))
],
),
),
),
);
}
To
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo App',
theme: ThemeData(
primarySwatch: white,
scaffoldBackgroundColor: Colors.white,
),
home: InitScreen());
}
}
class InitScreen extends StatelessWidget {
const InitScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Demo App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
HomeScreen(title: 'Demo Home')));
},
child: Text('Open Home Screen'))
],
),
),
);
}
What changed?
Create a separate widget for home code in MyApp with InitScreen
What was the issue?
When we try to push Route by using Navigator.of(context), flutter will
try to find Navigator in the widget tree of the given context. In the
initial code, there was no widget that has Navigator. So, create a
separate widget for home code. And the MaterialApp widget in MyApp
will have Navigator.