Flutter集成Mercado Pago Pix支付时出现Null转String类型错误
Mercado Pago Pix支付实现中Null类型错误问题
问题现象
调用Navigator.of(context).push(MaterialPageRoute(builder: ((context) => TelaDadosPix(resultado))))时触发类型错误,提示传递了Null而非String类型值。实际错误根源是TelaDadosPix的build方法中,读取widget.dadosPix["status"]、widget.dadosPix["qr_code"]等字段时这些值为Null,但Text组件要求必须传入String类型。
报错信息
EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ The following TypeErrorImpl was thrown building TelaDadosPix(dirty, state: _TelaDadosPix#e6117): Expected a value of type 'String', but got one of type 'Null' The relevant error-causing widget was: TelaDadosPix TelaDadosPix:file:///C:/Users/pedro/OneDrive/%C3%81rea%20de%20Trabalho/Horario/horario/lib/features/pix/presentation/cria_chave_pix.dart:52:52 When the exception was thrown, this was the stack: dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 294:49 throw_ dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 127:3 castError dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 742:12 cast dart-sdk/lib/_internal/js_dev_runtime/patch/core_patch.dart 674:17 as packages/horario/features/pix/presentation/tela_dados_pix.dart 50:21 build packages/flutter/src/widgets/framework.dart 5409:27 build packages/flutter/src/widgets/framework.dart 5297:15 performRebuild packages/flutter/src/widgets/framework.dart 5462:11 performRebuild packages/flutter/src/widgets/framework.dart 5016:7 rebuild packages/flutter/src/widgets/framework.dart 5279:5 [_firstBuild] packages/flutter/src/widgets/framework.dart 5453:11 [_firstBuild] packages/flutter/src/widgets/framework.dart 5273:5 mount packages/flutter/src/widgets/framework.dart 4182:15 inflateWidget packages/flutter/src/widgets/framework.dart 3707:18 updateChild packages/flutter/src/widgets/framework.dart 6434:14 mount packages/flutter/src/widgets/framework.dart 4182:15 inflateWidget packages/flutter/src/widgets/framework.dart 3707:18 updateChild
相关代码
CriaChavePix类
import 'dart:convert'; import 'package:mercadopago_sdk/mercadopago_sdk.dart'; import 'package:mp_flutter/mp_flutter.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:horario/core/configs/route.dart'; import 'package:horario/features/pix/presentation/tela_dados_pix.dart'; import 'package:http/http.dart' as http; class CriaChavePix extends StatefulWidget { Map<String, dynamic> dadosPix; CriaChavePix(this.dadosPix); @override _CriaChavePix createState() => _CriaChavePix(); } class _CriaChavePix extends State<CriaChavePix> { String chavePix = ''; Future<void> criaChavePix() async { var bodyMap = { "transaction_amount": 2000, "payment_method_id": "pix", "payer": { "email": "test@mail.com", "first_name": "first_name", "last_name": "last_name", "identification": { "type": "identification_type", "number": "number_cpf" } } }; var url = Uri.parse('https://api.mercadopago.com/v1/payments/'); var response = await http.post(url, headers: { "Authorization": "Bearer ...", }, body: json.encode(bodyMap)); if (response.statusCode == 201) { Map<String, dynamic> resultado = json.decode(response.body); Navigator.of(context).push( MaterialPageRoute(builder: ((context) => TelaDadosPix(resultado)))); } else { throw Exception(response.body); } } @override void initState() { super.initState(); criaChavePix(); } @override Widget build(BuildContext context) { return Container( color: Colors.white, child: Center( child: CircularProgressIndicator( color: Colors.blueAccent, ), ), ); } }
TelaDadosPix类
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; class TelaDadosPix extends StatefulWidget { Map<String, dynamic> dadosPix; TelaDadosPix(this.dadosPix); _TelaDadosPix createState() => _TelaDadosPix(); } class _TelaDadosPix extends State<TelaDadosPix> { String statusPix = " "; Future<void> pesquisaStatusPix() async { var url = Uri.parse('https://api.mercadopago.com/v1/payments/'); var response = await http.post(url, body: {"idPix": widget.dadosPix["idPIX"]}); statusPix = response.body; } @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return Scaffold( body: ListView( children: <Widget>[ Column( children: <Widget>[ SizedBox( width: double.infinity, height: 200.0, child: Image.asset("assets/images/img_pix.jpeg", fit: BoxFit.cover)), Padding( padding: const EdgeInsets.all(12.0), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text("Status do Pagamento Pix:", style: TextStyle( fontSize: 25.0, fontWeight: FontWeight.bold)), Text(widget.dadosPix["status"], style: TextStyle(fontSize: 18.0, color: Colors.green)), SizedBox( height: 16.0, ), Text(widget.dadosPix["total_a_pagar"].toString(), style: TextStyle(fontSize: 25.0, color: Colors.green)) ], ), ), Padding( padding: EdgeInsets.all(12.0), child: Row( children: <Widget>[ SizedBox( height: 36, width: 36, child: Image.asset( "assets/images/um.png", )), SizedBox( width: 8.0, ), Expanded( child: Text( "Abra o app do seu banco ou seu app de pagamentos.", style: TextStyle(fontSize: 18.0, color: Colors.black))) ], ), ), SizedBox( height: 16.0, ), Padding( padding: EdgeInsets.all(12.0), child: Row( children: <Widget>[ SizedBox( height: 36, width: 36, child: Image.asset( "assets/images/dois.png", )), SizedBox( width: 8.0, ), Expanded( child: Text("Busque á opção de pagar com pix.", style: TextStyle(fontSize: 18.0, color: Colors.black))) ], ), ), SizedBox( height: 16.0, ), Padding( padding: EdgeInsets.all(12.0), child: Row( children: <Widget>[ SizedBox( height: 36, width: 36, child: Image.asset( "assets/images/tres.png", )), SizedBox( width: 8.0, ), Expanded( child: Text("Copie e cole o seguinte código.", style: TextStyle(fontSize: 18.0, color: Colors.black))) ], ), ), Padding( padding: const EdgeInsets.all(12.0), child: SizedBox( width: double.infinity, child: Container( decoration: BoxDecoration( border: Border.all( color: Colors.blue, ), borderRadius: BorderRadius.circular(10.0), ), child: Padding( padding: EdgeInsets.all(8.0), child: Text( widget.dadosPix["qr_code"], style: TextStyle(fontSize: 14.0), )), ), ), ), SizedBox( height: 16.0, ), Padding( padding: const EdgeInsets.all(12.0), child: SizedBox( height: 44.0, width: double.infinity, child: TextButton( onPressed: () { final data = ClipboardData(text: widget.dadosPix["qr_code"]); Clipboard.setData(data); }, child: Text( "COPIAR CHAVE", style: TextStyle(fontSize: 18.0), ), ), ), ), SizedBox( height: 12.0, ), ], ) ], ), ); } }
解决方法
1. 字段空值处理
在TelaDadosPix的build方法中,对可能为Null的字段添加默认值或空值判断,确保传递给Text组件的是String类型:
// 状态字段添加默认值 Text(widget.dadosPix["status"] ?? "待处理", style: TextStyle(fontSize: 18.0, color: Colors.green)), // 金额字段先判断Null再转字符串 Text((widget.dadosPix["total_a_pagar"] ?? 0).toString(), style: TextStyle(fontSize: 25.0, color: Colors.green)), // 二维码字段添加空字符串默认值 Text(widget.dadosPix["qr_code"] ?? "", style: TextStyle(fontSize: 14.0)), // 复制功能同样处理空值 final data = ClipboardData(text: widget.dadosPix["qr_code"] ?? "");
2. 验证API返回字段
检查Mercado Pago API返回的resultado结构,确认字段名是否正确:
- 在
criaChavePix方法中打印返回数据,确认实际字段路径:
if (response.statusCode == 201) { Map<String, dynamic> resultado = json.decode(response.body); print(resultado); // 打印API返回数据,核对字段名 Navigator.of(context).push( MaterialPageRoute(builder: ((context) => TelaDadosPix(resultado)))); }
Mercado Pago Pix支付的二维码通常嵌套在point_of_interaction.transaction_data.qr_code路径下,而非直接的qr_code字段;金额字段是transaction_amount而非total_a_pagar。
3. 类型安全优化
使用模型类解析API返回数据,避免直接操作Map导致的字段名错误和空值问题:
class PixPaymentResponse { final String status; final double totalAmount; final String qrCode; PixPaymentResponse({ required this.status, required this.totalAmount, required this.qrCode, }); factory PixPaymentResponse.fromJson(Map<String, dynamic> json) { return PixPaymentResponse( status: json["status"] ?? "pending", totalAmount: json["transaction_amount"]?.toDouble() ?? 0.0, qrCode: json["point_of_interaction"]?["transaction_data"]?["qr_code"] ?? "", ); } }
修改criaChavePix和TelaDadosPix的参数类型:
// CriaChavePix中解析数据 PixPaymentResponse paymentResponse = PixPaymentResponse.fromJson(resultado); Navigator.of(context).push( MaterialPageRoute(builder: ((context) => TelaDadosPix(paymentResponse)))); // TelaDadosPix改为接收模型类 class TelaDadosPix extends StatefulWidget { final PixPaymentResponse paymentData; TelaDadosPix(this.paymentData); _TelaDadosPix createState() => _TelaDadosPix(); }
后续直接通过widget.paymentData.status等方式访问字段,彻底避免空值和字段名错误问题。
内容的提问来源于stack exchange,提问作者Pedro Rodrigues
相关产品推荐
相关产品推荐

