如何解决错误“类型‘Null’不是类型转换中类型‘String’的子类型”

TEZ*_*EZZ 6 http dart flutter dart-null-safety

我一直在尝试调试此错误类型“Null”不是类型转换中“String”类型的子类型,但无法找到产生错误的确切位置,除了触发 POST API 调用时生成错误之外。

店铺等级

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);
}
Run Code Online (Sandbox Code Playgroud)

后期功能-创建店铺

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);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

店铺服务档案

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;
  }
}
Run Code Online (Sandbox Code Playgroud)

基本 Http 服务文件

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;
  }
}
Run Code Online (Sandbox Code Playgroud)

基本上我想做的就是创建商店,它只需要正文中的 2 个字段,number其他calendar字段将在数据库中默认。

代码在该行末尾失败else if (method == 'post') response = await httpClient.post(Uri.parse(url), headers: headers, body: payload); ,但我不知道问题出在哪里,因为我已经放入?了变量。

http包已经是最新版本了 http: ^0.13.4

我已经在 POSTMAN 中尝试了以下正文来进行 POST 调用,并且它可以正常工作:

//Test 1
{
        "id": null,
        "shopNumber": "87678675",
        "created": null,
        "updated": null,
        "calendar": 1
    }

//Test 2
    {
        "shopNumber": "87678675",
        "calendar": 1
    }
Run Code Online (Sandbox Code Playgroud)

堆栈跟踪:

#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>
Run Code Online (Sandbox Code Playgroud)

TEZ*_*EZZ 0

更改http-0.13.4/lib/src/utils.dart中的以下代码片段对错误进行了排序:"type 'Null' is not a subtype of type 'String' in typecast"。参考我得到此代码的 github 问题https://github.com/dart-lang/http/issues/75

这会从有效负载中删除所有空值,或者您可以在其中放置一些其他代码来管理空值。

从:

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("&");
     }
Run Code Online (Sandbox Code Playgroud)

到:

 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
Run Code Online (Sandbox Code Playgroud)

在解决这个“类型'int'不是类型转换中'String'类型的子类型”之后,我还遇到了另一个类似的错误。我相信这是由于 http-0.13.4/lib/src/utils.dart 中的这段代码所致,其中 http 客户端尝试将所有内容映射到上面引用的字符串。

String mapToQuery(Map<String, String> map, {Encoding encoding})
Run Code Online (Sandbox Code Playgroud)

为了避免所有这些问题,我只是改变了我的功能:

从:

Future<String> callApi(String url, String token, {String method: 'get', Map<String, dynamic>? payload})
Run Code Online (Sandbox Code Playgroud)

到:

Future<String> callApi(String url, String token, {String method: 'get', String? payload})
Run Code Online (Sandbox Code Playgroud)

shop.toJson()然后我没有使用它而是将其替换为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;
  }
Run Code Online (Sandbox Code Playgroud)

通过将有效负载更改为 aString而不是它会触发不同的输出,正如您在http-0.13.4/lib/src/base_client.dartMap中的这段代码中看到的那样

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".');
      }
Run Code Online (Sandbox Code Playgroud)

以前我触发body is Map将值转换为字符串:

else if (body is Map) {
            request.bodyFields = body.cast<String, String>();
          }
Run Code Online (Sandbox Code Playgroud)

这是将 mynullint值转换为String导致两个错误的原因。

现在我正在触发:

if (body is String) {
            request.body = body;
          }
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助将来遇到这个问题的人。