How to solve error "type 'Null' is not a subtype of type 'String' in type cast"

1,834

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.

Share:
1,834
TEZZ
Author by

TEZZ

Updated on January 02, 2023

Comments

  • TEZZ
    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 and calendar 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
    TEZZ over 2 years
    I'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.