Home > Blockchain >  How to solve error "type 'Null' is not a subtype of type 'String' in type c
How to solve error "type 'Null' is not a subtype of type 'String' in type c

Time:12-20

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>

CodePudding user response:

You're probably sending/getting Null value from your api call and it's not matching with your type string. or the field name is not same. Please check the value and field name.

CodePudding user response:

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.

  • Related