How to solve error "type 'Null' is not a subtype of type 'String' in type cast"
Changing the code snippet below in http-0.13.4/lib/src/utils.dart sorted the error: "type 'Null' is not a subtype of type 'String' in type cast". Reference to github issue where I got this code https://github.com/dart-lang/http/issues/75
This removes all the null from the payload, or you can put some other code in there to manage nulls.
FROM:
String mapToQuery(Map<String, String> map, {Encoding encoding}) {
var pairs = <List<String>>[];
map.forEach((key, value) =>
pairs.add([Uri.encodeQueryComponent(key, encoding: encoding),
Uri.encodeQueryComponent(value, encoding: encoding)]));
return pairs.map((pair) => "${pair[0]}=${pair[1]}").join("&");
}
TO:
String mapToQuery(Map<String, String> map, {Encoding encoding}) {
+ map.keys
+ .where((k) => (map[k] == null)).toList() // -- keys for null elements
+ .forEach(map.remove);
+
var pairs = <List<String>>[];
map.forEach((key, value) =>
pairs.add([Uri.encodeQueryComponent(key, encoding: encoding),
Uri.encodeQueryComponent(value, encoding: encoding)]));
return pairs.map((pair) => "${pair[0]}=${pair[1]}").join("&");
}
enter code here
I also faced another similar error after resolving this "type 'int' is not a subtype of type 'String' in type cast". I believe it is due to this code in http-0.13.4/lib/src/utils.dart where the http client tries to map everything to string as referenced above.
String mapToQuery(Map<String, String> map, {Encoding encoding})
To avoid all these issues I just changed my function:
FROM:
Future<String> callApi(String url, String token, {String method: 'get', Map<String, dynamic>? payload})
TO:
Future<String> callApi(String url, String token, {String method: 'get', String? payload})
Then instead of using shop.toJson()
I replaced it with json.encode(shop)
Future<Shop> create(Shop shop) async {
var token = await this.appContext.currentUser!.getIdToken();
final response = await callApi('$_cardApi/', token, method: 'post', payload: json.encode(shop));
// return Shop.fromJsonModel(json.decode(response));
return shop;
}
By changing the payload to a String
rather than Map
it triggers a different output as you can see in this peiece of code in http-0.13.4/lib/src/base_client.dart
if (headers != null) request.headers.addAll(headers);
if (encoding != null) request.encoding = encoding;
if (body != null) {
if (body is String) {
request.body = body;
} else if (body is List) {
request.bodyBytes = body.cast<int>();
} else if (body is Map) {
request.bodyFields = body.cast<String, String>();
} else {
throw ArgumentError('Invalid request body "$body".');
}
Previously I was triggering body is Map
which cast the values to Strings:
else if (body is Map) {
request.bodyFields = body.cast<String, String>();
}
This was casting my null
and int
values to String
which is causing the two errors.
Now I am triggering:
if (body is String) {
request.body = body;
}
Hope this helps someone who is facing this problem in the future.
TEZZ
Updated on January 02, 2023Comments
-
TEZZ over 1 year
I've been trying to debug this error type 'Null' is not a subtype of type 'String' in type cast but could not find the the exact place where the error is being produced besides that it is genereated when trigger a POST API call.
Shop Class
import 'package:freezed_annotation/freezed_annotation.dart'; part 'shop.freezed.dart'; part 'shop.g.dart'; @freezed class Shop with _$Shop { factory Shop({ String? id, @JsonKey(name: 'shopNumber')String? number, @JsonKey(name: 'created') String? createdAt, @JsonKey(name: 'updated') String? updatedAt, int? calendar}) = _Shop; factory Shop.fromJson(Map<String, dynamic> json) => _$ShopFromJson(json); static Shop fromJsonModel(Map<String, dynamic> json) => Shop.fromJson(json); }
Post Function - Create shop
Future<void> postShop() async { Shop shop; shop = await shopSvc.create(Shop(calendar: widget.calendar?.id, number: _shopNumber)); var newCalendar = widget.calendar?.copyWith(shop: shop); Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => ShopInfoScreen(calendar: newCalendar!))); } } catch (e) { print(e); } } }
Shop Service File
class ShopService extends BaseHttpService { final AppContext appContext; late String _shopApi; shopService(http.Client client, this.appContext) : super(httpClient: client) { _shopApi = '${Globals.ApiEndpoint}/user/${appContext.currentCustomer.id}/shops/'; } Future<Shop> create(Shop shop) async { var token = await this.appContext.currentUser!.getIdToken(); final response = await callApi('$_cardApi/', token, method: 'post', payload: shop.toJson()); // return Shop.fromJsonModel(json.decode(response)); return shop; } }
Base Http Service file
class BaseHttpService { final Client httpClient; BaseHttpService({required this.httpClient}); @protected Future<String> callApi(String url, String token, {String method: 'get', Map<String, dynamic>? payload}) async { late Response response; Map<String, String> headers = {'Authorization': 'Bearer $token'}; if (method == 'get') response = await httpClient.get(Uri.parse(url), headers: headers); else if (method == 'post') response = await httpClient.post(Uri.parse(url), headers: headers, body: payload); else if (method == 'put') response = await httpClient.put(Uri.parse(url), headers: headers, body: payload); else if (method == 'delete') response = await httpClient.delete(Uri.parse(url), headers: headers); if (response.statusCode >= 300) { print('statusCode : ' + response.statusCode.toString()); print(response.body.toString()); throw ClientException('Failed to load story with id $url'); } return response.body; } }
Basically all I'm trying to do is create shop which only requires 2 fields in the body,
number
andcalendar
and the other fields will be defaulted in the DB.The code is failing at the end of this line
else if (method == 'post') response = await httpClient.post(Uri.parse(url), headers: headers, body: payload);
but I do not know where the problem is as I've put?
in the variables already.The http package is already on the newest version
http: ^0.13.4
I'ved tried the below body for the POST call in POSTMAN and it works without a problem:
//Test 1 { "id": null, "shopNumber": "87678675", "created": null, "updated": null, "calendar": 1 } //Test 2 { "shopNumber": "87678675", "calendar": 1 }
Stacktrace:
#0 CastMap.forEach.<anonymous closure> (dart:_internal/cast.dart:288:25) #1 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:400:8) #2 CastMap.forEach (dart:_internal/cast.dart:287:13) #3 mapToQuery (package:http/src/utils.dart:17:7) #4 Request.bodyFields= (package:http/src/request.dart:137:12) #5 BaseClient._sendUnstreamed (package:http/src/base_client.dart:87:17) #6 BaseClient.post (package:http/src/base_client.dart:32:7) #7 BaseHttpService.callApi (package:shop/services/base-http.dart:18:60) #8 ShopService.create (package:shop/services/shop_service.dart:20:28) <asynchronous suspension> #9 _ShopAddScreenState.postShop (package:shop/shop_add_screen.dart:137:16) <asynchronous suspension>
-
TEZZ over 2 yearsI've already checked passing null value in POSTMAN and it works fine. I've also added
?
to the variables as part of the null-safety migration to flutter 2. I can sort of see that for that the problem is in the HTTP client when mapping the request body fields null to String but not sure how to handle that.