I am trying to use the "map" method/function on a Map object in Dart. (I know, it's a mouthful, and hard to search in google).
https://api.flutter.dev/flutter/dart-core/Map/map.html
It's essentially like List.map, generating a new Map by applying conversions to the keys and value pairs.
Why might you use this? Maybe you need to rename some keys in your Map object.
Example Code / Error
void main() {
Map myMap={"a":1, "b":2};
print(myMap);
print(myMap.map((k,v) {
k=k+k;
v=v*10;
return {k:v};
// I expect { aa:10,bb:20 }
}));
}
I get a compilation error:
Error: A value of type 'Map<dynamic, dynamic>' can't be returned from a function with return type 'MapEntry<dynamic, dynamic>'.
- 'Map' is from 'dart:core'.
- 'MapEntry' is from 'dart:core'.
return {k:v};
^
Error: Compilation failed.
Since I didn't find any examples online, I'm writing up the answer here.
In the code above, I expected it to take the separate Map items in the return statement and merge them into one Map object. But, instead, it gives an error. It wants a MapEntry object.
So the correct code is
void main() {
Map myMap={"a":1, "b":2};
print(myMap);
print(myMap.map((k,v) {
k=k+k;
v=v*10;
return MapEntry(k, v);
}));
print(myMap);
}
Furthermore, the console output is
{a: 1, b: 2}
{aa: 10, bb: 20}
{a: 1, b: 2}
Appendix on variable passing
Note: I worried (and wondered) if modifying the k and v variables in the convert function would modify anything in the original object. So, I checked. No change when the keys and values are immutable objects (like strings and numbers).
If, instead, you have a mutable object, like a map, then it does change that object.
See, for example:
void main() {
Map myMap={"a":1, "b":2};
print(myMap);
print(myMap.map((k,v) {
k=k+k;
v=v*10;
return MapEntry(k, v);
}));
print(myMap);
print("======");
Map myMap2={"c":1, "d":{"changed": "no"}};
print(myMap2);
print(myMap2.map((k,v) {
k=k+k+k;
if (k=="ddd") {
v["changed"]="yes";
}
return MapEntry(k, v);
}));
print(myMap2);
}
Which returns
{a: 1, b: 2}
{aa: 10, bb: 20}
{a: 1, b: 2}
======
{c: 1, d: {changed: no}}
{ccc: 1, ddd: {changed: yes}}
{c: 1, d: {changed: yes}}
See https://dartpad.dev/?id=57382792d2fa7a50566c228678a1d4da&null_safety=true
Related
The code below defines a generic myFirstWhereFunction with 3 arguments:
Generic list
Generic value to search in the list
Generic default value to return if the searched value is not in the passed generic list
The code:
void main() {
const List<int> intLst = [1, 2, 3, 4];
print(myFirstWhereFunc(intLst, 4, -1));
print(myFirstWhereFunc(intLst, 5, -1));
const List<String> strLst = ['coucou', 'go', 'bold', 'tooltip'];
print(myFirstWhereFunc(strLst, 'go', 'not exist'));
print(myFirstWhereFunc(strLst, 'ok', 'not exist'));
}
T myFirstWhereFunc<T>(List<T> lst, T searchVal, T defaultVal) {
return lst.firstWhere((element) => element == searchVal, orElse: <T> () {
return defaultVal;
});
}
But this code generates an exception.
One solution is to replace the generic myFirstWhereFunc return type by dynamic (code below):
dynamic myFirstWhereFunc<T>(List<T> lst, T searchVal, T defaultVal) {
return lst.firstWhere((element) => element == searchVal,
orElse: () => defaultVal);
}
But is there another way of solving the problem ?
I believe that the problem is that when you do:
print(myFirstWhereFunc(intLst, 4, -1));
there are two possible ways to infer the type of myFirstWhereFunc:
Bottom-up (inside-out): myFirstWhereFunc is called with a List<int> and with int arguments, so its type could be myFirstWhereFunc<int>. This is what you want.
Top-down (outside-in): print has an Object? parameter, so myFirstWhereFunc could be myFirstWhereFunc<Object?> so that it returns an Object?. This is what actually happens and is what you do not want.
Dart ends up with two possible ways to infer the generic type parameter, both seem equally valid at compilation-time, and it picks the one that you happen to not want. Picking the other approach likely would result in undesirable outcomes for different code examples. Arguably inference could try both ways and pick the narrower type if one approach leads to a subtype of the other. I'm not sure offhand if that would break code, but I wouldn't be surprised. (I also suspect that it's been suggested in https://github.com/dart-lang/language/issues somewhere...)
Changing myFirstWhereFunc's return type to dynamic is a crude workaround to the problem because it makes myFirsyWhereFunc's type no longer inferrable from print's parameter type.
If you split the line up and use a temporary variable, inference can infer myFirstWhereFunc independently of print and then should do what you want:
var intResult = myFirstWhereFunc(intLst, 4, -1);
print(intResult);
I have a question on how to easily use map as a parameter in Dart. Is there any easy way of passing all the key-values pairs of a map object to a function?
For example, I have a map and a function like this:
const testMap = {"a": 1, "b":2};
int testFunc(a, b){
return a + b;
}
and I want to use them like this:
testFunct(**testMap) // not possible in dart (though possible in python)
which should give us 3 as the result.
Any smart solution like this? Thanks!
Please note that ** is the python-way of passing the parameters inside dictionary: docs
It's not directly possible the way you write it, but you can do something similar using Function.apply.
You have to either pass the arguments as positional, and then you need to know the order:
const testMap = {"a": 1, "b":2};
int testFunc(a, b){
return a + b;
}
void main() {
print(Function.apply(testFunc, testMap.values.toList()));
}
or you have to use named parameters and then the map keys should be symbols:
const testMap = {#a: 1, #b:2};
int testFunc({required int a, required int b}){
return a + b;
}
void main() {
print(Function.apply(testFunc, [], testMap));
}
Dart distinguishes positional and named parameters, so if you want to refer to a parameter by name, it should be named.
The former approach is dangerous because you don't use the names of the map. It should really just be const testArguments = [1, 2]; and not include a map which doesn't help you.
Using Function.apply is not type safe. It's as type-safe as calling something with type dynamic or Function — which means not safe at all — so use it carefully and only when necessary.
Once in a while we all need to quickly return multiple values from a function, and look for a way to create a new type on the fly.
In Python I can return a tuple
def get_trio1():
return (True, 23, "no")
(_, first1, second1) = get_trio1()
print(first1)
print(second1)
ignore one of the values, and retrieve both of the other two on the fly in one assignment.
I can likewise return an array.
def get_trio2():
return [True, 23, "no"]
[_, first2, second2] = get_trio2()
print(first2)
print(second2)
But both of these are brittle. If I edit the code to add a value, particularly if it's within the three already defined, the revised code could fail silently.
Which is why the nicest solution is to create a dict on the fly.
def get_trio3():
return {"b": True, "i": 23, "s": "no"}
r = get_trio3()
print(r["i"])
print(r["s"])
The use of named members means that maintaining the code is considerably safer.
What is the closest I can do to get the same safety in Dart? Is defining a class for the return type necessary?
In case it matters, the context is avoiding List<List<dynamic>> when returning a future.
Future<List<dynamic>> loadAsset() async =>
return await Future.wait([
rootBundle.loadString('assets/file1.txt'),
rootBundle.loadString('assets/file2.txt'),
]);
Update
Using Stephen's answer for a future introduces a problem. Future.wait is hardwired to use an array Iterable.
Future<Map<String, dynamic>> loadAsset() async =>
return await Future.wait({
"first": rootBundle.loadString('assets/file1.txt'),
"second": rootBundle.loadString('assets/file2.txt'),
});
Your loadAsset function returns a Future<List<dynamic>> because that's how you declared it. You could have declared it to return a Future<List<String>> instead.
Future.wait is hardwired to use an array.
Especially since Dart is a statically-typed language, you can't really expect it to take both a List and some Map with your custom semantics. You could write your own version:
Future<Map<String, T>> myFutureWait<T>(Map<String, Future<T>> futuresMap) async {
var keys = futuresMap.keys.toList();
var values = futuresMap.values.toList();
var results = await Future.wait<T>(values);
return Map.fromIterables(keys, results);
}
Use a map.
Map<String, dynamic> foo() => {'A': 'a', 'B': 'b', 'C': false }
var result = foo();
print result['B']; // prints b;
I need to add or overwrite items to a LinkedHashMap and at the same time it should return the map modified.
This is the code I have:
MyModel.fromJson(json);
json variable is LinkedHashMap, and I need to add items before calling fromJson function.
I tried with addAll function:
MyModel.fromJson(json.addAll({ ... }));
but it returns void so I can use for calling fromJson function.
You can use the cascade notation for calling a method on a object and still return the same object reference instead of the result of the method:
void main() {
final map = {"A": 1};
print(map..addAll({"B": 2})); // {A: 1, B: 2}
}
Is there a combination of Dart spread operators and null-aware operators that will do this?
[
1,
...twoOrNull() // this will be inserted only if it's null. something else than the ... operator will be here.
3,
]
So the list will be either [1, 2, 3] or [1, 3]. I guess twoOrNull() can return [2] or [], but it would be nice if it could return 2 or null.
Is this possible without introducing a variable?
There is a null-aware spread operator (...?), but your twoOrNull() function would have to return either [2]or null; the spread operator expands an iterable within another collection literal, and it doesn't make sense to "spread" an int.
There's also Dart's collection-if construct, but it would require either calling twoOrNull() twice or saving the result in a variable:
[
1,
if (twoOrNull() != null) twoOrNull(),
3,
]
See the Lists section from the Dart Language Tour for more information about spread and collection-if.
An one-liner without side effect:
[
1,
...[twoOrNull()]..removeWhere((x) => x == null),
3,
]
The idea here is to map from an int twoOrNull() to a list of either [2] or [], then use the spreading operator ... to unfold it.
Note that having one twoOrNull() in this case is fine, but as soon as you start having more elements that need null checking before being added to the list, readability will suffer. In that case, consider delaying the null check to after you have added the element to the list, i.e.
[
1,
twoOrNull(),
3,
]..removeWhere((x) => x == null)
This will make the code a lot more straightforward and readable.
EDIT:
For the best readability in the list, it would be perfect to have a twoOrNullList() function returning either [2] or []. Then you can use it pretty much similar to what you proposed:
var twoOrNullList() => [twoOrNull()]..removeWhere((x) => x == null)
[
1,
...twoOrNullList(),
3,
]
Yet another solution.
Iterable<T> emit<T>(T? p) sync* {
if (p != null) {
yield p;
}
}
[
1,
...emit(twoOrNull()),
3,
]