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
Related
I am trying to integrate a search and filter textfield in my app, but I am not completely sure of the errors my compiler is throwing. I think I am having type issues, or that I have not set up my inheritances correctly?
Ok, so basically I am sending a http request to an api and receiving this data where I have set up my class to display it. In a futurebuilder I have some conditional logic trying to sort the right data I need into separate lists(Im not sure if this the right thing to do but I digress) I then create a map (resultMap) and within that I loop through the arrays and nest a map (innerMap).
class _HomePageState extends State<HomePage> {
final controller = TextEditingController();
String? searchString = '';
late final Future<ProjectModel> futureProjects;
List<ProjectSearch> searchList = []; //I think this causing this issues.....
List<String?> jobNames = [];
List<String?> jobNumbers = [];
List<String?> techs = [];
List<String?> pms = [];
List<String?> address = [];
List<String?> majors = [];
List<String?> budget = [];
Map resultMap = {};
#override
void initState() {
super.initState();
futureProjects = fetchProjects();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(
'Upcoming / Live Projects',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
backgroundColor: ColorConstants.darkScaffoldBackgroundColor,
),
drawer: const CustomDrawer(),
backgroundColor: ColorConstants.lightScaffoldBackgroundColor,
// ignore: avoid_unnecessary_containers
body: Center(
child: FutureBuilder<ProjectModel>(
future: futureProjects,
builder: (context, snapshot) {
if (snapshot.hasData) {
var data = snapshot.data!;
var columns = data.columns;
var rows = data.rows;
for (var item in rows!) {
var cells = item.cells;
for (var elements in cells!)
if (elements.columnId != null) {
if (elements.columnId == 2057691532158852) {
var displayValues = elements.displayValue;
if (displayValues != null) {
jobNames.add(displayValues);
}
..... continuing conditional logic
//Here is where I nest my arrays to the resultMap map:
int index = 0;
for (int i = 0; i < jobNames.length; i++) {
Map innerMap = {};
innerMap['name'] = jobNames[index];
innerMap['address'] = address[index];
innerMap['jobNumber'] = jobNumbers[index];
innerMap['major'] = majors[index];
innerMap['tech'] = techs[index];
innerMap['pM'] = pms[index];
innerMap['budget'] = budget[index];
index++;
resultMap[i] = innerMap;
}
I have been looking trough some tutorials and it looks like it is easier to apply string search functions using .contains() but these only work on lists. So I convert my resultMap using a forEach to create my searchList list.
resultMap.forEach((key, value) => searchList.add(value));
Here is my TextFormField:
TextFormField(
textAlign: TextAlign.center,
controller: controller,
onChanged: search,
.....
And then my ListView.builder:
return MaterialButton(
onPressed: () => showModalBottomSheet<void>(
backgroundColor: Colors.transparent,
context: context,
builder: (BuildContext context) {
final projectData = searchList[index];
return
Text(
projectData['name'],
And finally the function I am using to search:
void search(String query) {
List finder = searchList;
final suggestions = searchList.where((check) {
final projectName = check['name'].toLowerCase();
final input = query.toLowerCase();
return projectName.contains(input);
}).toList();
setState(() {
finder = suggestions;
});
}
I hope this makes sense...
When I get rid of the model it doesn't break down but the search function doesnt filter thru the array. However here is the compile error:
type 'List<dynamic>' is not a subtype of type 'List<ProjectModel>' of 'function
result'
Try this showSearch() method
It is an inbuilt functionality which is simple to use.
I think the problem arises when you convert map to list. please try below procedure to convert map to list when data comes from api...
obviously do anything(search) with list data in your app.
just change api url from below
Future<List<ProjectModel>> fetchProjectData() async {
final response =
await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
final parsed = jsonDecode(response.body);
return parsed.map<ProjectModel>((json) => ProjectModel.fromJson(json)).toList();
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load data');
}
}
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.
I tried to used google map autocomplete, but I got the this error LateInitializationError: Field 'searchResults' has not been initialized.
How can solve it problem?
I still tried to used this way, but still falue.
void initState() {
searchResults;
}
This is my code
late List<PlaceSearch> searchResults;
final placeService = PlaceSerive();
searchPlaces(String searchTerm) async {
searchResults = await placeService.getAutoComplete(searchTerm);
}
...
...
void initState() {
searchResults;
}
...
...
...
child: ListView.builder(
itemCount: searchResults.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(searchResults[index].description,
style: TextStyle(color: Colors.black)),
);
},
),
While searchResults will get data from future it is better to use FutureBuilder for statelesswidget or nullabe data.
On state class
List<PlaceSearch>? searchResults;
#override
void initState() {
super.initState();
searchPlaces("yourSearchTerm")
}
searchPlaces(String searchTerm) async {
searchResults = await placeService.getAutoComplete(searchTerm);
setState((){});
}
Now you can check whether list is null or not.
child: searchResults==null? Text("loading") :ListView.builder(...)
You are not calling the searchPlaces function in the init function so it can't initialize the searchResults variable. To solve it do this:
#override
initState(){
super.initState();
searchPlaces();
}
and remember to set state in searchPlaces function.
I have a widget where on initState, I need to loop over some controls and load data via an API.
I want to use/reference this data in the widget control key.
This is summary of code initializing data.
List<dynamic> controlData = [];
#override
initState(){
loadAllOptionData();
super.initState();
}
void loadAllOptionData() async {
List<dynamic> listDataControls = [];
Future.wait(widget.fields.where((element) => element['header'] !=null).map((e) async {
if(e['uitype']==7){
List<dynamic> options = await ApiManager.getListData(widget.tableId, e);
listDataControls.add({e['data'] : options});
}
}).toList());
this.setState(() {
controlData= listDataControls;
});
}
In the widget render tree, I have this check
if(controlData.length>0){
return Container(
margin: EdgeInsets.all(20),
child: Text('has data')
);
}
Right now, it's not working, as it never shows "has data". How do I get this data loaded, such that I can reference in widget tree?
As you can see in first part I'm checking that a certain value contains in a document from Firestore and returns a boolean value. Now I'm calling that function in a build and based on that return value I'm changing a chip color (second part).
Now the problem is maybe because I'm calling it in a build function so its being called continuously and on that build and it costing me a ton of reads in Firestore or maybe the function is inefficient. How can I write this more efficiently?
checkAtt(String name, id , date) async{
var ref = _db.collection('subjects').document(id).collection('Att').document(date);
var docref = await ref.get();
return docref.data.containsKey(name)
?true
:false;
}
class PresentChip extends StatefulWidget {
final candidate;
PresentChip(
this.candidate, {
Key key,
}) : super(key: key);
#override
_PresentChipState createState() => _PresentChipState();
}
class _PresentChipState extends State<PresentChip> {
var isSelected = false;
var c = false;
#override
Widget build(BuildContext context) {
final SelectSub selectSub = Provider.of<SelectSub>(context);
final Date date = Provider.of<Date>(context);
db.checkAtt(widget.candidate, selectSub.selectsub, date.datenew).then((result){
print(result);
setState(() {
c = result;
});
});
return Container(
child: ChoiceChip(
label: Text('Present'),
selected: isSelected,
onSelected: (selected) {
db.gibAtt(
widget.candidate, selectSub.selectsub, date.datenew.toString());
setState(() {
isSelected = selected;
});
},
backgroundColor: !c ?Colors.red :Colors.green ,
selectedColor: !c ?Colors.red :Colors.green ,
));
}
}
Assuming you only want to read once from firestore, you need a FutureBuilder.
return Container(
child: FutureBuilder(
future: db.checkAtt(widget.candidate, selectSub.selectsub, date.datenew),
builder: (context, snapshot) {
if(snapshot.hasData)
return ChoiceChip(
...
backgroundColor: !snapshot.data ?Colors.red :Colors.green,
selectedColor: !snapshot.data ?Colors.red :Colors.green,
);
//Return another widget if the future has no data
return Text('Future has no data');
}
)
);
If you need your UI to react to changes from firestore, use a StreamBuilder.
You can remove the following bloc from your build method:
db.checkAtt(widget.candidate, selectSub.selectsub, date.datenew).then((result){
print(result);
setState(() {
c = result;
});
});