Flutter - await/async on a List - why does this only work when not using declarations? - flutter

Still new to Flutter :(. Can anyone help...
I have a class that stores a bunch of project information. One part of this is a list of topics (for push notification), which it grabs from a JSON file.
I apply a getter for the list of topics, and when getting it it calls an async function which will return a List
Future<List<String>> _pntopics() async{
final _json = await http.get(Uri.parse(_topicsUrl));
if (_json.statusCode == 200) {
return (jsonDecode(_json.body));
}else {
return [""];
}
}
Future<List<String>> get topics => _pntopics();
In my main.dart file, it calls this value like so...
Future<List<String>> _topiclist = await projectvalues.topics;
The response is however empty, pressumably because it is a Future - so it is grabbing the empty value before it is filled.
But I can't remove the "Future" part from the async method, because asnc methods require a Future definition.
Then I decided to remove the declarations entirely:
_pntopics() async{
final _json = await http.get(Uri.parse(_topicsUrl));
if (_json.statusCode == 200) {
return (jsonDecode(_json.body));
}else {
return [""];
}
}
get topics => _pntopics();
and in main.dart, a general declaration...
var _topiclist = await projectvalues.topics;
...and this works!
So what declaration should I actually be using for this to work? I'm happy to not use declarations but we're always to declare everthing.

You should return back Future<List<String>> return types to the function and the getter but for _topicslist you must use var, final or List<String> declaration because:
(await Future<T>) == T
i.e.
var _topiclist = await projectvalues.topics; // The type of _topiclist is List<String>
final _topiclist = await projectvalues.topics; // The type of _topiclist is List<String>
UPDATE
Your code should be:
Future<List<String>> _pntopics() async{
final _json = await http.get(Uri.parse(_topicsUrl));
if (_json.statusCode == 200) {
return List<String>.from(jsonDecode(_json.body));
}else {
return <String>[""];
}
}
Doing this you force _pnptopics returns List<String> as jsonDecode returns List<dynamic>.
P.S. It is good practice do not use dynamic types where they can be changed to specified types.

Related

Unhandled Exception: type 'Null' is not a subtype of type 'List<dynamic>' in type cast

Objective is to convert a String to List using map and return the value to a function call.
I am using SharedPreferences to save a list of object called where in I save the data at a point and get the data when it is to be put on view.
The below block is the function where the error is occurring.
void getData() async {
final prefs = await SharedPreferences.getInstance();
final String taskString = prefs.getString('task_data').toString();
List<Task> tasksData = Task.decode(taskString);
_tasks = tasksData;
notifyListeners();
}
decode() looks basically does the conversion.
static List<Task> decode(String tasks) {
return (jsonDecode(tasks) as List<dynamic>).map<Task>((task) {
return Task.fromJson(task);
}).toList();
It advises to check for null condition in type cast of decode(). But on performing the check, it gives the same error.
your response might be not a proper map so it cannot decode that data using the jsonDecode function so it returns Null, so you can use your function like this might be helpful for you :
static List<Task> decode(String tasks) {
var data = (jsonDecode(tasks) as List<dynamic>?);
if(data != null){
return (jsonDecode(tasks) as List<dynamic>?)!.map<Task>((task) {
return Task.fromJson(task);
}).toList();
} else {
return <Task>[];
}
}

Future<dynamic> is not a subtype of List<dynamic>

So I am trying to pass a list of String values from firestore table, but I am getting an exception type 'Future<dynamic>' is not a subtype of type 'List<dynamic>'
This is the function
getLectureList(String userId) async {
var collection = FirebaseFirestore.instance.collection('students');
var docSnapshot = await collection.doc(userId).get();
Map<String, dynamic>? data = docSnapshot.data();
List<String> _lectureList =
await data!['attendance']; //This line is kinda giving me trouble
userInfo = FirestoreWrapper()
.getStudentFromData(docId: currentUser(), rawData: data);
return _lectureList;
}
And this is the function where I am getting the exception thrown
#override
void initState() {
lectureList = getLectureList(currentUser()); // Getting an exception here
NearbyConn(context).searchDevices(devices: deviceList);
super.initState();
}
tried using await in the getLectureList() method but still getting the same problem
Why do you await your data? You already got it.
List<String> _lectureList = data!['attendance'];
Please note that I don't know what your data structure looks like, so I cannot tell you if this is correct, I can only tell you that it is more correct than before, because the await did not belong there.
You are getting an exception here lectureList = getLectureList(currentUser()); because the the parameter required by the getLectureList() method is the userId which is a string. I do not know what currentUser() return but I'm assuming it's the userId that you need when calling the getLectureList() method. Based on the error, it looks like currentUser() is an async method that returns a future after some time.
You're not awaiting that future. You shouldn't make the initState() method async so move the code block out of it into a separate method and then call it from initState().
Something like this,
#override
void initState() {
super.initState();
_getData();
}
void _getData() async {
lectureList =
getLectureList(await currentUser());
NearbyConn(context).searchDevices(devices: deviceList);
}
or
#override
void initState() {
super.initState();
_getData();
}
void _getData() async {
String _userID = await currentUser();
lectureList = getLectureList(_userID);
NearbyConn(context).searchDevices(devices: deviceList);
}
Which I recommend so you can see all the parts.
Making your method parameters required named parameters also help you to easily see what is needed to pass to a function/class/.
Eg.
getLectureList({required String userId}){
...
}
Your IDE will alert you on the type of object the function requires and it makes things clearer.
Ultimately, I think typing your classes makes it so much more easier to fetch data from fireStore Typing CollectionReference and DocumentReference
This way you can easily do this,
final moviesRef = FirebaseFirestore.instance.collection('movies').withConverter<Movie>(
fromFirestore: (snapshot, _) => Movie.fromJson(snapshot.data()!),
toFirestore: (movie, _) => movie.toJson(),
);
and get your data this way,
Future<void> main() async {
// Obtain science-fiction movies
List<QueryDocumentSnapshot<Movie>> movies = await moviesRef
.where('genre', isEqualTo: 'Sci-fi')
.get()
.then((snapshot) => snapshot.docs);
// Add a movie
await moviesRef.add(
Movie(
title: 'Star Wars: A New Hope (Episode IV)',
genre: 'Sci-fi'
),
);
// Get a movie with the id 42
Movie movie42 = await moviesRef.doc('42').get().then((snapshot) => snapshot.data()!);
}
Keeps everything dry and tidy.
< The data comes to list format thats why showing the exception of datatype >
List<String> lectureList = await getLectureList(currentUser()); // use
Future<List<String>> getLectureList(String userId) async {
- your code -
}
Instead of
List _lectureList =
await data!['attendance'];
Try this
_lectureList = await data![] As List

returning a String when getting error: type 'Future<dynamic>' is not a subtype of type 'String'

I can't work out how to return a string from a function in Dart (a Flutter app).
I am using SharedPreferences to capture input from the user. I have two functions, one to save preferences:
save(key, value) async {
final prefs = await SharedPreferences.getInstance();
prefs.setString(key, value);
print('saved $value');
}
and one to read preferences:
read(key) async {
final prefs = await SharedPreferences.getInstance();
final value = prefs.getString(key) ?? 0;
print('$value');
}
This is working, but when I try to replace the print line with a return:
read(key) async {
final prefs = await SharedPreferences.getInstance();
final value = prefs.getString(key) ?? 0;
return('$value');
}
to return a string for the value, it throws an error:
type 'Future' is not a subtype of type 'String'
I have tried calling it many MANY different ways, but can't figure out what I assume is an incredibly basic problem. I noticed in some posts that this is a suggested solution, which works to print out the value, but I don't want to print it, i want it as a String variable:
read(mykey).then((value) => '$value');
I need to combine the value with other some other string values and make some minor manipulations (so printing it isn't helpful)
UPDATE
I have defined the function as #Stijn2210 suggested, but am still having problems getting the output i need.
Future<String> read(key) async {
final prefs = await SharedPreferences.getInstance();
final value = await prefs.getString(key) ?? '';
return value;
}
When I call this function from my app (this is a simplified snippet):
void onDragEnd(DraggableDetails details, User user) {
final minimumDrag = 100;
Future<String> myvalue;
if (details.offset.dx > minimumDrag) {
user.isSwipedOff = true;
save(user.imgUrl, 'Dog');
}
myvalue = read(user.imgUrl);
print(myvalue);
It's printing :
Instance of 'Future'
Whereas I want myvalue to be 'Dog'... Appreciate any insights!!
Really appreciate your answer #Stijn2202
Solution was to edit the method definition:
Future<void> onDragEnd(DraggableDetails details, User user) async
and then call the read function from the method with this:
final String myvalue = await read(user.imgUrl);
getString is a Future, which you can handle by using await or as you are doing, using then
However, in my opinion using await is your better option. This would look like this:
Future<String> getMyString() async {
final prefs = await SharedPreferences.getInstance();
final value = await prefs.getString(key) ?? '';
// Don't use 0, since it isnt an int what you want to return
return value;
}
EDIT:
based on your code snippet, this is how you should call your read method:
Future<void> onDragEnd(DraggableDetails details, User user) async {
final minimumDrag = 100;
if (details.offset.dx > minimumDrag) {
user.isSwipedOff = true;
save(user.imgUrl, 'Dog');
}
final String myvalue = await read(user.imgUrl);
print(myvalue);
}
Now I'm not sure if onDragEnd is actually allowed to be Future<void>, but let me know if it isn't
Just await for the value. It will return Dog and not instance of Future.
String someName=await myvalue;
As the value is Future, await keyword will wait until the task finishes and return the value

How to return a List, after a Method fills it, Flutter

I'm stuck with a problem and I wondered if you can help me.
I have a functions (in Flutter) that returns a List of Items. Now this List of Items should be Filled by an other function, which goes thought my Database and collect the right items. My Problem is, that my Function runs after the Return Statement... Here is some Code:
Future<List<MaterialItem>> getItems(String path, String fach) async {
// This is a empty List that I want to fill
List<MaterialItem> list = [];
// That's my Function, that fills the List
var result = await _db
.collection("$path/$fach/")
.get()
.then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
// Here the List gets filled
list.add(MaterialItem.fromSnapshot(doc.data() as Map<String, dynamic>));
});
});
// Here the List should be returned, but after my Function fills it.
return list;
}
Hope you know what my problem is, and someone can help me.
I think you could solve this using a Completer. Your function should return the Future property of the Completer and the database call should then complete it.
Take a look at the API and the example:
https://api.dart.dev/stable/2.12.4/dart-async/Completer-class.html
For example: (pseudo code)
Future<List<MaterialItem>> getItems(String path, String fach) async {
// declare a completer
Completer<List<MaterialItem>> completer = Completer();
List<MaterialItem> list = [];
final result = await _db
.collection("$path/$fach/")
.get()
.then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
list.add(MaterialItem.fromSnapshot(doc.data() as Map<String, dynamic>));
});
// let the database call complete the completer
completer.complete(list);
});
// return the future value of the completer
return completer.future;
}

List.map returns List<Future>

I got a class for handling SharePreferences
class SharedPreferencesUtils {
static Future<String> getSharedPreference(String key) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString(key);
}
}
I try to use this class from another class to get all my sharedPreferences with this method:
void getAllPrefs() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
var keyList = prefs.getKeys().toList();
var valueList = keyList.map((key) async {
String value = await SharedPreferencesUtils.getSharedPreference(key);
return value;
}).toList();
print("KEY LIST IS $keyList");
print("VALUE LIST IS $valueList");
}
And, while the keyList works well, the valueList just returns:
VALUE LIST IS [Instance of 'Future<String>', Instance of 'Future<String>', Instance of 'Future<String>', Instance of 'Future<String>', Instance of 'Future<String>', Instance of 'Future<String>', Instance of 'Future<String>', Instance of 'Future<String>', Instance of 'Future<String>', Instance of 'Future<String>']
I don't really get why I am not getting the actual String value corresponding to the key, as I understood Futures, in this case, the execution should await until the var value gets the String value that I am asking for.....am I wrong?
Note: there are values stored in SharedPreferences, that is for sure.
This is a good one :)
I will only need to mention one key concept and you will see why this is happening: Any async function returns a Future.
In your case, the map call uses an async callback and hence the values in your lists are Futures.
There is a helper in the Future class: Future.wait
You can simply pass your Iterable to it and it will return a list with resolved futures:
Future<void> getAllPrefs() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final keyList = prefs.getKeys().toList();
final valueList = await Future.wait(keyList.map((key) async {
String value = await SharedPreferencesUtils.getSharedPreference(key);
return value;
}));
print("KEY LIST IS $keyList");
print("VALUE LIST IS $valueList");
}
How do you do it without the helper? Well, not use map because it requires a callback, but you need to stay in the same scope if you want to get rid of Future values as any outside function would need to be async. So here you go:
Future<void> getAllPrefs() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final keyList = prefs.getKeys().toList();
final valueList = List<String>(keyList.length);
for (int i = 0; i < valueList.length; i++)
valueList[i] = await SharedPreferencesUtils.getSharedPreference(keyList[i]);
print("KEY LIST IS $keyList");
print("VALUE LIST IS $valueList");
}
One practice that helps you to remember that all async functions return futures is using Future<void> as the return type instead.
If you use Future.wait, i.e. still use your map call, you can make it a lot more concise like this:
await Future.wait(keyList.map(SharedPreferencesUtils.getSharedPreferences));
You can just use keyList.map(SharedPreferencesUtils.getSharedPreferences) because getSharedPreferences already takes a String and returns a Future<String>, which is equivalent to what you were doing before :)