You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 14:24:54