Flutter解析Valorant API数据时类型不匹配错误排查
Flutter请求Valorant API类型不匹配错误排查与修复
错误信息
Error: TypeError: "players": type 'String' is not a subtype of type 'int'
Error fetching match history: Exception: Error fetching player match history: TypeError: "players": type 'String' is not a subtype of type 'int'
需求
在ListView中展示Valorant API返回的玩家名称、标签(name tag)以及当前段位(current tier patched),API请求地址:https://api.henrikdev.xyz/valorant/v3/matches/ap/Gunner/GG69?size=1&api_key=HDEV-990890cf-68cf-4263-afce-446dadda0b13
现有代码
模型类代码
class Player { final String name; final String tag; final String rank; Player({required this.name, required this.tag, required this.rank}); factory Player.fromJson(Map<String, dynamic> json) { return Player( name: json['name'], tag: json['tag'], rank: json['currenttier_patched'], // Use 'rank_patched' instead of 'rank' ); } } class PlayerMatchHistory { final List<Player> players; PlayerMatchHistory({required this.players}); factory PlayerMatchHistory.fromJson(Map<String, dynamic> json) { var data = json['data']; // Get the first element of the 'data' list var playersJson = data['players']; // Get the 'all_players' field from the 'players' object if (playersJson is List) { List<Player> playersList = playersJson.map((playerJson) => Player.fromJson(playerJson)).toList(); return PlayerMatchHistory(players: playersList); } else { throw Exception('Expected all_players to be a List but got a ${playersJson.runtimeType}'); } } }
UI页面代码
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:valorant_app/viewmodel/match_vm.dart'; class PlayerMatchHistoryScreen extends StatefulWidget { final String playerName; final String tag; const PlayerMatchHistoryScreen({ Key? key, required this.playerName, required this.tag, }) : super(key: key); @override _PlayerMatchHistoryScreenState createState() => _PlayerMatchHistoryScreenState(); } class _PlayerMatchHistoryScreenState extends State<PlayerMatchHistoryScreen> { bool _isListViewVisible = false; @override Widget build(BuildContext context) { final viewModel = Provider.of<PlayerMatchHistoryViewModel>(context); return Scaffold( appBar: AppBar( title: const Text('Player Match History'), ), body: FutureBuilder( future: viewModel.fetchMatchHistory(widget.playerName, widget.tag), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (viewModel.isLoading) { return const Center(child: Text("Loading...")); } else if (snapshot.hasError) { return Center(child: Text('Error: ${snapshot.error}')); } else if (viewModel.matchHistory == null || viewModel.matchHistory!.players.isEmpty) { return const Center(child: Text('No match history available.')); } else { WidgetsBinding.instance.addPostFrameCallback((_) { setState(() { _isListViewVisible = true; }); }); return _isListViewVisible ? ListView.builder( itemCount: viewModel.matchHistory!.players.length, itemBuilder: (context, index) { final player = viewModel.matchHistory!.players[index]; return ListTile( title: Text('${player.name}#${player.tag}'), subtitle: Text('Current Rank: ${player.rank}'), ); }, ) : Container(); } } else { return const Center(child: CircularProgressIndicator()); } }, ), ); } }
Main代码
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:valorant_app/pages/player_screen.dart'; import 'package:valorant_app/service/api.dart'; import 'package:valorant_app/repo/repo.dart'; import 'package:valorant_app/viewmodel/match_vm.dart'; import 'package:valorant_app/viewmodel/valo_vm.dart'; //import 'package:valorant_app/pages/player_screen.dart'; import 'package:valorant_app/pages/player_history.dart'; // Import the new screen void main() { // Define base URLs and API key const baseUrl = 'https://api.henrikdev.xyz/valorant/v2/mmr'; const baseUrl2 = 'https://api.henrikdev.xyz/valorant/v3/matches'; // Correct Base URL const apiKey = 'HDEV-990890cf-68cf-4263-afce-446dadda0b13'; // Replace with your actual API Key // Initialize the API service and repository final apiService = ValorantApiService(baseUrl: baseUrl, baseUrl2: baseUrl2, apiKey: apiKey); final repository = ValorantRepository(apiService: apiService); runApp( MultiProvider( providers: [ ChangeNotifierProvider( create: (_) => PlayerViewModel(repository: repository), ), ChangeNotifierProvider( create: (_) => PlayerMatchHistoryViewModel(repository: repository), ), ], child: const MyApp(), ), ); }
class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Valorant Player Info', theme: ThemeData( primarySwatch: Colors.blue, ), home: const PlayerMatchHistoryScreen(playerName: 'Gunner', tag: 'GG69', ) // Initial screen can be changed to match history if needed ); } }
API返回JSON片段
"players": { "all_players": [ { "puuid": "3c4b8e9c-8a7b-5dfc-b68d-4fba11b39291", "name": "GUNNER", "tag": "GG69", "team": "Blue", "level": 111, "character": "Breach", "currenttier": 9, "currenttier_patched": "Silver 1", "player_card": "72f9b137-41f1-ef95-853a-798212f064ee", "player_title": "129190d4-42fa-4e79-75e3-ffb5679d1dc2", "party_id": "5052b7eb-098e-4943-ac25-08fe747f17cf", "session_playtime": { "minutes": 46, "seconds": 2760, "milliseconds": 2760000 },
解决方案
1. 修复模型类的JSON解析逻辑
错误根源是对API返回结构的误解:
data字段是比赛数组(即使size=1,返回的仍是数组),直接取json['data']会得到数组而非单场比赛对象;- 单场比赛中的
players是对象,真正的玩家数组在all_players字段下。
修改后的PlayerMatchHistory.fromJson方法:
factory PlayerMatchHistory.fromJson(Map<String, dynamic> json) { // 取数组中的第一场比赛 var firstMatch = json['data'][0]; // 从比赛对象的players中获取all_players数组 var playersJson = firstMatch['players']['all_players']; if (playersJson is List) { List<Player> playersList = playersJson.map((playerJson) => Player.fromJson(playerJson)).toList(); return PlayerMatchHistory(players: playersList); } else { throw Exception('Expected all_players to be a List but got a ${playersJson.runtimeType}'); } }
2. 简化UI的FutureBuilder逻辑
移除不必要的_isListViewVisible状态,优化代码结构:
@override Widget build(BuildContext context) { final viewModel = Provider.of<PlayerMatchHistoryViewModel>(context); return Scaffold( appBar: AppBar( title: const Text('Player Match History'), ), body: FutureBuilder( future: viewModel.fetchMatchHistory(widget.playerName, widget.tag), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { return Center(child: Text('Error: ${snapshot.error}')); } else if (viewModel.matchHistory == null || viewModel.matchHistory!.players.isEmpty) { return const Center(child: Text('No match history available.')); } else { return ListView.builder( itemCount: viewModel.matchHistory!.players.length, itemBuilder: (context, index) { final player = viewModel.matchHistory!.players[index]; return ListTile( title: Text('${player.name}#${player.tag}'), subtitle: Text('Current Rank: ${player.rank}'), ); }, ); } }, ), ); }
3. 增加空值安全处理
在Player.fromJson中添加空值判断,避免API返回字段为null时崩溃:
factory Player.fromJson(Map<String, dynamic> json) { return Player( name: json['name'] ?? 'Unknown', tag: json['tag'] ?? 'Unknown', rank: json['currenttier_patched'] ?? 'Unranked', ); }
验证
修改后重新运行应用,即可正确解析API返回的玩家数据,并在ListView中展示名称、标签和段位信息。
内容的提问来源于stack exchange,提问作者Harshit Gyanchandani
相关产品推荐
相关产品推荐

