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

Flutter中geocoding的locationFromAddress抛出空值异常排查

问题排查建议

在Chrome中通过flutter run运行Flutter Web应用时,调用geocoding包的locationFromAddress方法抛出"Unexpected null value"错误,但直接HTTP调用谷歌地理编码API正常,已启用Geocoding API并在index.html配置了API密钥,需检查以下内容:

需要检查的项

  • geocoding包Web端配置完整性
    确认index.html中谷歌Maps JavaScript API的加载脚本包含geocoding库,格式如下:

    <script src="https://maps.googleapis.com/maps/api/js?key=你的API密钥&libraries=places,geocoding"></script>
    

    同时检查API密钥无拼写错误、多余空格,确保注入位置正确(在Flutter的main.dart.js加载之前)。

  • 版本兼容性问题
    当前使用geocoding 3.0.0 + Flutter 3.24.4,可能存在版本适配问题。尝试升级geocoding到最新稳定版,或降级至与Flutter 3.x匹配的版本(如geocoding 2.1.1),再测试是否复现错误。

  • 空值解析逻辑问题
    geocoding包在Web端解析谷歌API响应时,可能对某些字段使用了强制非空断言(!),若API返回的字段缺失就会触发空值错误。对比直接HTTP调用的返回数据,检查是否存在字段在包的解析逻辑中被假设为非空,但实际返回为null;也可查看geocoding包的Web端源码,确认解析逻辑是否存在潜在空值风险。

  • API密钥的域名限制
    尽管直接HTTP调用正常,但需确认Google Cloud控制台中API密钥的HTTP引用者限制是否包含本地开发域名(如http://localhost:XXXX,XXXX为你的Flutter Web端口),避免因域名白名单限制导致geocoding包调用失败。


相关代码参考

问题代码片段

/// Searches for an address and moves the map
Future<void> _searchAddress() async {
    String query = _searchController.text.trim();
    if (query.isEmpty) {
      debugPrint("Search query is empty.");
      return;
    }

    try {
      debugPrint("Calling locationFromAddress for: $query");
      List<Location> locations = await locationFromAddress(query);
      debugPrint("locationFromAddress response: $locations");
      if (locations.isEmpty) {
        throw Exception("No locations found for the query.");
      }
    } catch (e) {
      debugPrint("locationFromAddress error: $e"); // Outputs "locationFromAddress error: Unexpected null value."
    }
  }
}

完整页面代码

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:geocoding/geocoding.dart';

class FindAddressScreen extends StatefulWidget {
  const FindAddressScreen({super.key});

  @override
  State<FindAddressScreen> createState() => _FindAddressScreenState();
}

class _FindAddressScreenState extends State<FindAddressScreen> {
  LatLng _selectedLocation =
      LatLng(37.7749, -122.4194);
  final TextEditingController _searchController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Select Address'),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.pop(context, _selectedLocation);
            },
            child: Text("Conferma"),
          )
        ],
      ),
      body: Stack(
        children: [
          GoogleMap(
            initialCameraPosition: CameraPosition(
              target: _selectedLocation,
              zoom: 14,
            ),
            onMapCreated: (controller) {

            },
            onLongPress: _updateLocation,
            markers: {
              Marker(
                markerId: MarkerId("selected-location"),
                position: _selectedLocation,
              )
            },
          ),
          Positioned(
            top: 10,
            left: 10,
            right: 10,
            child: Card(
              child: Row(
                children: [
                  Expanded(
                    child: TextField(
                      controller: _searchController,
                      decoration: InputDecoration(
                        hintText: "Search address",
                        contentPadding: EdgeInsets.symmetric(
                          vertical: 10,
                          horizontal: 15,
                        ),
                        border: InputBorder.none,
                      ),
                    ),
                  ),
                  IconButton(
                    icon: Icon(Icons.search),
                    onPressed: _searchAddress,
                  ),
                ],
              ),
            ),
          )
        ],
      ),
    );
  }

  /// Updates the selected location
  Future<void> _updateLocation(LatLng position) async {
    setState(() {
      _selectedLocation = position;
    });
  }

  /// Searches for an address and moves the map
  Future<void> _searchAddress() async {
    String query = _searchController.text.trim();
    if (query.isEmpty) {
      debugPrint("Search query is empty.");
      return;
    }

    try {
      debugPrint("Calling locationFromAddress for: $query");
      List<Location> locations = await locationFromAddress(query);
      debugPrint("locationFromAddress response: $locations");
      if (locations.isEmpty) {
        throw Exception("No locations found for the query.");
      }
    } catch (e) {
      debugPrint("locationFromAddress error: $e");
    }
  }
}

可用的HTTP调用替代实现

import 'package:http/http.dart' as http;
import 'dart:convert';

// ...

  Future<void> _searchAddress() async {
    String query = _searchController.text.trim();
    if (query.isEmpty) {
      debugPrint("Search query is empty.");
      return;
    }

    final apiKey = "YOUR_API_KEY"; // 替换为你的API密钥
    final url = Uri.parse(
      "https://maps.googleapis.com/maps/api/geocode/json?address=${Uri.encodeComponent(query)}&key=$apiKey");

    try {
      final response = await http.get(url);
      if (response.statusCode == 200) {
        final data = json.decode(response.body);
        if (data['results'] != null && data['results'].isNotEmpty) {
          final location = data['results'][0]['geometry']['location'];
          LatLng target = LatLng(location['lat'], location['lng']);
          _moveCamera(target);
          _updateLocation(target);
          debugPrint("Address found: $location");
        } else {
          throw Exception("No results found.");
        }
      } else {
        throw Exception("Failed to fetch geocoding data.");
      }
    } catch (e) {
      debugPrint("Google Maps search error: $e");
    }
  }
}

环境信息

  • 依赖版本:google_maps_flutter 2.10.0、geocoding 3.0.0、http 1.2.2
  • 运行环境:Chrome 131.0.6778.86、Flutter 3.24.4、Dart 3.5.4、VS Code 1.95.3、macOS 15.0(MacBook Air M1 8GB)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 20:40:01