Pass SharedPreferences to List widget - flutter

I'm trying to build a flutter application with a navigation bar and I need to pass some variables to my List widget. The problem is that the List widget is built before my variable is initialized so I can't use it.
I already tried to use "super.initState();" and things like that but the only thing I got (as best result) was an empty variable.
Here is my List widget which is used for my bottom navigation bar:
** Some code**
SharedPreferences prefs;
List<Widget> _widgetOptions = <Widget>[
TabScreen(currentUserId: prefs.getString("id")),
Text(
'Index 1: Business',
),
Text(
'Index 2: School var',
),
Scaffold(
body: Center(
child: Row(
),
),
),
];
With this code I have a message "Only satic members can be accessed in initializers" on the line "TabScreen(currentUserId: prefs.getString("id"))" which is logical as my List is initialized before I can access "prefs" variable but I can't manage to solve the problem.
Thanks for your help :)

have you tried to build it in didChangeDependencies? Most of the times this works for me. Until then i give the widget a placeholder Container. (or for fancy reasons a SpinKitDualRing)

String id;
Future<Null> getSharedPrefs() async
{
SharedPreferences prefs = await
SharedPreferences. getInstance() ;
id = prefs. getString("id" );
}
initState () {
super. initState () ;
id="" ;
getSharedPrefs() ;
}
Now you can use this id in list..

String _id;
SharedPreferences _sharedPreferences;
#override
void initState() {
// TODO: implement initState
super.initState();
getSharedPrefs();
}
getSharedPrefs() async{
_sharedPreferences = await SharedPreferences.getInstance();
_id = _sharedPreferences.getString("id");
setState(() {
});
}
This may help you

Related

shared preferences does not save radio button checkmark in Flutter

I implemented the shared preferences package in my Flutter app, with a list widget as radio button, that only save the language preference and not the checkmark.
So when i close the Language screen and come back, the language checkmark goes the the default one even if the language, saved in shared preferences is French or Italian.
This is my Language screen:
class LanguagesScreen extends StatefulWidget {
const LanguagesScreen({Key? key}) : super(key: key);
#override
State<LanguagesScreen> createState() => _LanguagesScreenState();
}
class Item {
final String prefix;
final String? helper;
const Item({required this.prefix, this.helper});
}
var items = [
Item(prefix: 'English', helper: 'English',), //value: 'English'
Item(prefix: 'Français', helper: 'French'),
Item(prefix: 'Italiano', helper: 'Italian'),
];
class _LanguagesScreenState extends State<LanguagesScreen> {
var _selectedIndex = 0;
final _userPref = UserPreferences();
var _selecLangIndex;
int index = 0;
final List<String> entries = <String>['English', 'French', 'Italian'];*/
//init shared preferences
#override
void initState() {
super .initState();
_populateField();
}
void _populateField() async {
var prefSettings = await _userPref.getPrefSettings();
setState((){
_selecLangIndex = prefSettings.language;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(...
),
body: CupertinoPageScaffold(
child: Container(
child: SingleChildScrollView(
child: CupertinoFormSection.insetGrouped(
children: [
...List.generate(items.length, (index) => GestureDetector(
onTap: () async {
setState(() => _selectedIndex = index);
if (index == 0){
await context.setLocale(Locale('en','US'));
_selecIndex = Language.English;
}
else if (index == 1){
await context.setLocale(Locale('fr','FR'));
_selecIndex = Language.French;
}
child: buildCupertinoFormRow(
items[index].prefix,
items[index].helper,
selected: _selectedIndex == index,
)
)),
TextButton(onPressed:
_saveSettings,
child: Text('save',
)
buildCupertinoFormRow(String prefix, String? helper, {bool selected = false,}) {
return CupertinoFormRow(
prefix: Text(prefix),
helper: helper != null
? Text(helper, style: Theme.of(context).textTheme.bodySmall,)
:null, child: selected ? const Icon(CupertinoIcons.check_mark,
color: Colors.blue, size: 20,) :Container(),
);
}
void _saveSettings() {
final newSettings = PrefSettings(language:_selecIndex);
_userPref.saveSettings(newSettings);
Navigator.pop(context);
}
}
this is the UserPreference:
class UserPreferences {
Future saveSettings(PrefSettings prefSettings) async {
final preferences = await SharedPreferences.getInstance();
await preferences.setInt('language' , prefSettings.language.index );
}
Future<PrefSettings> getPrefSettings() async {
final preferences = await SharedPreferences.getInstance();
final language = Language.values[preferences.getInt('language') ?? 0 ];
return PrefSettings(language: language);
}
}
enum Language { English, French, Italian}
class PrefSettings{
final Language language;
PrefSettings (
{required this.language});
}
I'm betting that the issue is in initState. You are calling _populateField, but it doesn't complete before building because it's an async method, and you can't await for it: so the widget gets build, loading the default position for the checkmark, and only after that _populateField completes...but then it's too late to show the saved data correctly.
In my experience, if I have not already instantiated a SharedPreferences object somewhere else in the code, I use this to load it:
class _LanguagesScreenState extends State<LanguagesScreen> {
[...]
#override
Widget build(BuildContext context) {
return FutureBuilder(
//you can put any async method here, just be
//sure that you use the type it returns later when using 'snapshot.data as T'
future: await SharedPreferences.getInstance(),
builder: (context, snapshot) {
//error handling
if (!snapshot.hasData || snapshot.connectionState != ConnectionState.done) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text(snapshot.error.toString()));
}
var prefs= snapshot.data as SharedPreferences;
//now you have all the preferences available without need to await for them
return Scaffold((
[...]
);
EDIT
I started writing another comment, but there are so many options here that there wasn't enough space.
First, the code I posted should go in your _LanguagesScreenState build method. The FutureBuilder I suggested should wrap anything that depends on the Future you must wait for to complete. I put it up at the root, above Scaffold, but you can move it down the widgets' tree as you need, just remember that everything that needs to read the preferences has to be inside the FutureBuilder.
Second, regarding SharedPreferences.getInstance(), there are two ways: the first is declaring it as a global variable, and loading it even in the main method where everything starts. By doing this you'll be able to reference it from anywhere in your code, just be careful to save the changes everytime is needed. The second is to load it everytime you need, but you'll end up using a FutureBuilder a lot. I don't know if any of these two options is better than the other: the first might have problems if somehow the SharedPreferences object gets lost, while the second requires quite more code to work.

In Flutter: Is it possible to use a Future<bool> be used as a property in another widget?

I have a Card() widget which contains a ListTile() widget.
One of the ListTile() widget's properties is enabled. I would like to dynamically set the value of this enabled property by using the outcome of a Future<bool> which uses async and await. Is this possible?
Here is the Card() widget with the ListTile() in it
Card myCard = Card(
child: ListTile(
title: Text('This is my list tile in a card'),
enabled: needsToBeEnabled(1),
),
);
Here is my Future
Future<bool> cardNeedsToBeEnabled(int index) async {
bool thisWidgetIsRequired = await getAsynchronousData(index);
if (thisWidgetIsRequired == true) {
return true;
} else {
return false;
}
}
Other attempts
I have tried to use a Future Builder. This works well when I'm building a widget, but in this case I'm trying to set the widget's property; not build a widget itself.
You cannot do that for two reason:
enable does not accept Future<bool> but bool
you need to update the state after result is received (you need a StatefullWidget)
There are 1 million way to do what you want to do and one of this is FutureBuilder but if you want to not rebuild all widget you can use this flow (your main widget need to be Statefull):
create a local variable that contains your bool value, something like bool _enabled
on initState() method override you can launch the call that get asynchronous data and using the then() extension method you can provide the new state to your widget when the call will be completed.
Something like:
#override
void initState() {
super.initState();
getAsynchronousData(index).then((result) {
if (result == true) {
_enabled = true;
} else {
_enabled = false;
}
setState(() {});
});
}
assign the boolean var to the ListTile widget

Future<List<dynamic>> cannot be assigned to List<dynamic>

Getting the error (title) when I try to assign the return of this method to a widget parameter. The suggestions: is expect a List<dynamic> not a Future<List<dynamic>>. Is FutureBuilder the only way? The AutoCompleteTextField in this widget is a type ahead so will be calling getLocationSuggestionsList every .5 seconds after keystrokes stop (not sure if that matters in answering this question).
#override
Widget build(BuildContext context) {
return Container(
child: new Center(
child: Column(children: <Widget>[
new Column(children: <Widget>[
searchTextField = AutoCompleteTextField<dynamic>(
suggestions: getLocationSuggestionsList("sd"),
style: new TextStyle(color: Colors.black, fontSize: 16.0),
decoration: new InputDecoration(
.....
Future<List<dynamic>> getLocationSuggestionsList(String locationText) async {
List<String> suggestionList = List();
Map suggestionsKeyValuePairs = Map<String, String>();
dynamic data = await GoogleMapsServices.getAddressPrediction(
locationText,
LatLng(currentLocation.latitude, currentLocation.longitude),
);
if (data != null) {
for (dynamic predictions in data.predictions) {
suggestionsKeyValuePairs[predictions.description] = predictions.placeId;
if (!suggestionList.contains(predictions.description))
suggestionList.add(predictions.description);
}
return suggestionList;
} else {
return [''];
}
}
The cause for this error is that the suggestions parameter expects a List not a Future.
What you can do is create a state variable and assign the result of your getLocationSuggestionsList() function to that with a setState() call or any other state management mechanism so that whenever the state changes the UI builds again with the relevant data.
class YourClass extends StatefulWidget {
///
}
class _YourClassState extends State<YourClass>{
/// Your state variable here. Initialize with data that will be showing if actual data not available.
List<dynamic> suggestionList = ["];
initState(){
/// call you get suggestion function on init or any other lifecycle methods as per your need, may be inside build
getLocationSuggestionsList("sd");
super.initState();
}
#override
Widget build(context){
return AutoCompleteTextField<dynamic>(
suggestions: suggestionList,
style: new TextStyle(color: Colors.black, fontSize: 16.0),
decoration: new InputDecoration()
/// ....
);
}
void getLocationSuggestionsList(String locationText) async {
List<String> sList = List();
Map suggestionsKeyValuePairs = Map<String, String>();
dynamic data = await GoogleMapsServices.getAddressPrediction(
locationText,
LatLng(currentLocation.latitude, currentLocation.longitude),
);
if (data != null) {
for (dynamic predictions in data.predictions) {
suggestionsKeyValuePairs[predictions.description] = predictions.placeId;
if (!sList.contains(predictions.description))
sList.add(predictions.description);
}
} else {
sList = [''];
}
setState((){
suggestionList = List;
/// This will render your UI again with updates suggestionList
});
}
}
getLocationSuggestionsList() is async and return a future, if you want to get the result (List<dynamic>), you need to call it with await keyword.
await getLocationSuggestionsList("sd")
But, this is only possible into async functions/methods.
You can resolve this by many ways:
Use FutureBuilder
Do it with reactive programing architecture (Bloc, Rx, raw streams, etc...)
Do it like krishnakumarcn ;) https://stackoverflow.com/a/62187158/13569191

Is there a way I can use Future values in Dart to fill Textfields?

I've been learning Dart with flutter and was creating Weather App for practices , I managed to successfully create the UI , I used Dark Sky weather API since it has 7 day weather forecast but instead of taking the long road of decoding json and models , I used Dark Sky Library.
Here's the code for the API
Future<Null> getWeather() async {
Location location = Location();
await location.getCurrentPosition();
var darksky = DarkSkyWeather(
MY_API_KEY,
units: Units.SI,
language: Language.English,
);
var forecast = await darksky.getForecast(
location.latitude, location.longitude, excludes: [
Exclude.Hourly,
Exclude.Minutely,
Exclude.Alerts,
Exclude.Flags
]);
print(forecast.currently.temperature.round());
print(forecast.daily.data[0].temperatureMax);
}
I wanted to use the forecast variable outside the functions and to fill the text fields such the humidity temperature among other data in the package . How can I access it ? any help will be appreciated .
Thanks
Take a look at this code
class _MyHomePageState extends State<MyHomePage> {
String _data; // This holds the data to be shown
#override
void initState() {
_data = "Press the button"; // This is the initial data // Set it in initState because you are using a stateful widget
super.initState();
}
// Call this to set the new data
void setData() {
setState(() { // Remember to use setState() else the changes won't appear
_data = 'Hello World';
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Message',
),
Text(
'$_data', // See how the data is fed into this text widget
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: setData, // When I press the button, the new data is set
child: Icon(Icons.send),
),
);
}
}
Output:
Initially:
After pressing the button:
Do the same thing in your code.
// Your code modified a bit
// Make sure this is a stateful widget because you are changing values
double _tempNow; // Declare two vars inside the class
double _tempMax;
// Your initState
#override
void initState() {
_tempNow = 0; // Initial values
_tempMax = 0;
super.initState();
}
// Build method
#override
Widget build(BuildContext context) {
// Your layout Code.....
Text(
'Current: $_tempNow' // Here you set the tempNow
),
......
Text(
'Maximum: $_tempMax' // and tempMax in the text widgets
),
// End
}
Future<Null> getWeather() async {
Location location = Location();
await location.getCurrentPosition();
var darksky = DarkSkyWeather(
"8810b3633934b8c1975c51a2311dc1d0",
units: Units.SI,
language: Language.English,
);
var forecast = await darksky.getForecast(
location.latitude, location.longitude, excludes: [
Exclude.Hourly,
Exclude.Minutely,
Exclude.Alerts,
Exclude.Flags
]);
// Change the values here.
// After the data is fetched, The changes will be made automatically
setState(() {
_tempNow = forecast.currently.temperature.round();
_tempMax = forecast.daily.data[0].temperatureMax;
});
}
In quite a similar manner, you can use text fields too. In that case, You will need to use TextEditingController and set the text there but, it seems in your case, you need read-only text to show the information to the user so a simple Text widget is enough

State variable is not updated after getting data from SharedPreferences

I have the following problem: there's a state class:
class _HomeScreenState extends State<HomeScreen> {
String _name = "David";
#override
void initState() {
super.initState();
getNameFromPreferences();
}
#override
Widget build(BuildContext context) {
...
child: Text(
"Good Morning, \n"+_name,
style: TextStyle(color: Colors.black, fontSize: 28, fontWeight: FontWeight.bold),
),
)
}
...........................
void getNameFromPreferences() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
_name = prefs.getString(Strings.nameKey);
log(_name);
}
So, here you can see that I'm trying to get data from preferences and set it to variable in state. I watched in logs, that this data is really exists and that it sets to _name almost in the moment. But on the screen I still see default value David but not my data from SharedPreferences. So, why such situation happens?
Just change your code as follow,
getNameFromPreferences() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
_name = prefs.getString(Strings.nameKey);
log(_name);
setState(() {});
}
This will solve your issue.
Because sharedprefs package in async manner. so it takes some time to configure your data. till then your build method gets built. So, you have to use setState again to rebuild your view to render ui changes.
You didn't call setState().
void getNameFromPreferences() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
_name = prefs.getString(Strings.nameKey);
log(_name);
setState(() {}); // add this
}