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

如何用Dart调用GraphQL API获取挪威特隆海姆公交站点数据

Hey there! Let's break down how to solve your problem—first addressing the query filter issue, then walking through the Dart/GraphQL implementation step by step.

First: Fixing the Trondheim Filter Issue

Your original query uses countyReference: "Trondheim" which returns no data, and that's likely because the API expects an official NSR administrative zone identifier instead of the county name. Here's how to find the right value:

  1. Open the API IDE you linked earlier
  2. Run this broad query to grab sample stop data and their administrative zone references:
{
  stopPlaces(stopPlaceType: onstreetBus, first: 20) {
    name { value }
    administrativeZoneRef
  }
}
  1. Look for entries that correspond to Trondheim stops, copy their administrativeZoneRef value, and use that in your query instead of the county name. If this still doesn't work, reaching out to the Entur team is a good next step—their docs are indeed sparse on this detail.

Step 1: Set Up the GraphQL Client with Required Headers

First, you need to configure the graphql package to include the mandatory ET-Client-Name header (critical to avoid being blocked/rate-limited). Here's how to set that up:

import 'package:graphql/graphql.dart';

// Create the base HTTP link to the API
final HttpLink httpLink = HttpLink(
  'https://api.entur.io/stop-places/v1/graphql',
);

// Create a custom link to inject the ET-Client-Name header
final Link headerLink = Link.function((request, [forward]) {
  final updatedRequest = request.updateContextEntry<HttpLinkHeaders>(
    (headers) => HttpLinkHeaders(
      headers: {
        ...headers?.headers ?? {},
        'ET-Client-Name': 'your-university - your-flutter-app-name', // Replace with your own identifier (e.g., "ntnu - trondheim-bus-tracker")
      },
    ),
  );
  return forward!(updatedRequest);
});

// Combine the header link with the HTTP link
final Link combinedLink = headerLink.concat(httpLink);

// Initialize the GraphQL client
final GraphQLClient graphQLClient = GraphQLClient(
  cache: GraphQLCache(),
  link: combinedLink,
);

Step 2: Define Your GraphQL Query

Use the plural stopPlaces (instead of singular stopPlace) to fetch all matching stops, and use a variable for the zone reference to make it flexible:

query GetTrondheimBusStops($zoneRef: String!) {
  stopPlaces(stopPlaceType: onstreetBus, administrativeZoneRef: $zoneRef) {
    id
    name { value }
    quays {
      geometry {
        coordinates
      }
    }
  }
}

Step 3: Execute the Query and Parse Results into BusStop Objects

Now, write a function to run the query, handle the response, and map the data to your BusStop model. Note that GraphQL returns coordinates as [longitude, latitude], so we need to reverse them for LatLng:

import 'package:google_maps_flutter/google_maps_flutter.dart';

// Your BusStop model (I removed ChangeNotifier since it's not needed for data parsing—add it back if you need it for state management)
class BusStop {
  final String id;
  final String name;
  final LatLng location;

  BusStop({
    required this.id,
    required this.name,
    required this.location,
  });
}

Future<List<BusStop>> fetchTrondheimBusStops(String zoneRef) async {
  const String query = '''
    query GetTrondheimBusStops(\$zoneRef: String!) {
      stopPlaces(stopPlaceType: onstreetBus, administrativeZoneRef: \$zoneRef) {
        id
        name { value }
        quays {
          geometry {
            coordinates
          }
        }
      }
    }
  ''';

  final QueryOptions options = QueryOptions(
    document: gql(query),
    variables: {'zoneRef': zoneRef},
  );

  final QueryResult result = await graphQLClient.query(options);

  if (result.hasException) {
    throw Exception('Failed to fetch stops: ${result.exception.toString()}');
  }

  final List<dynamic> stopData = result.data?['stopPlaces'] ?? [];
  final List<BusStop> busStops = [];

  for (final stop in stopData) {
    final String id = stop['id'];
    final String name = stop['name']['value'];
    // Grab the first quay's coordinates (most stops have at least one)
    final List<dynamic>? coordinates = stop['quays']?[0]?['geometry']?['coordinates'];
    
    if (coordinates != null && coordinates.length == 2) {
      // Reverse coordinates: GraphQL returns [lon, lat], LatLng expects [lat, lon]
      final LatLng location = LatLng(coordinates[1], coordinates[0]);
      busStops.add(BusStop(id: id, name: name, location: location));
    }
  }

  return busStops;
}

Quick Notes

  • If you do need BusStop to be a ChangeNotifier, add back with ChangeNotifier and call notifyListeners() whenever properties change (though for initial data parsing, this isn't necessary).
  • Double-check your ET-Client-Name format—follow the "organization - app name" pattern to avoid rate limits or blocks.

内容的提问来源于stack exchange,提问作者Nikolai Henriksen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 08:27:34