切换Firebase数据库后Google Map页面Phenotype.API错误求助
切换Firebase数据库后,Google Map的homepage.dart页面出现错误。已完成Firebase和Maps API的全部配置,包括使用flutterfire configure初始化项目及Android应用、配置SHA1密钥、重新生成Maps API密钥,但问题仍未解决。
错误信息:
W/MobStoreFlagStore(10088): java.util.concurrent.ExecutionException: m.few: 17: 17: API: Phenotype.API is not available on this device. Connection failed with: cmc{statusCode=DEVELOPER_ERROR, resolution=null, message=null}
相关代码:
import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:geolocator/geolocator.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:firebase_database/firebase_database.dart'; import 'package:geocoding/geocoding.dart'; class HomePage extends StatefulWidget { const HomePage({Key? key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { final Completer<GoogleMapController> googleMapCompleterController = Completer<GoogleMapController>(); GoogleMapController? controllerGoogleMap; Position? currentPositionOfUser; late List<dynamic> _fletesList = []; Set<Marker> _markers = {}; double calculateDistance(LatLng pos1, LatLng pos2) { return Geolocator.distanceBetween( pos1.latitude, pos1.longitude, pos2.latitude, pos2.longitude); } void updateMapTheme(GoogleMapController controller) { getJsonFileFromThemes("themes/night_style.json") .then((value) => setGoogleMapStyle(value, controller)); } Future<String> getJsonFileFromThemes(String mapStylePath) async { ByteData byteData = await rootBundle.load(mapStylePath); var list = byteData.buffer .asUint8List(byteData.offsetInBytes, byteData.lengthInBytes); return utf8.decode(list); } setGoogleMapStyle(String setGoogleMapStyle, GoogleMapController controller) { controller.setMapStyle(setGoogleMapStyle); } Future<LatLng?> getLatLngFromAddress(String address) async { try { List<Location> locations = await locationFromAddress(address); if (locations.isNotEmpty) { double latitude = locations.first.latitude; double longitude = locations.first.longitude; return LatLng(latitude, longitude); } } catch (e) { print("Error obtaining coordinates: $e"); } return null; } Future<void> _getFletesFromFirebase() async { DatabaseReference databaseReference = FirebaseDatabase.instance.ref().child('fletes'); DatabaseEvent event = await databaseReference.once(); DataSnapshot dataSnapshot = event.snapshot; if (dataSnapshot.value != null) { if (dataSnapshot.value is Map) { Map<dynamic, dynamic> mapData = dataSnapshot.value as Map<dynamic, dynamic>; if (mounted) { setState(() { mapData.forEach((key, value) async { String startAddress = value['startAddress']; String endAddress = value['endAddress']; LatLng? startCoords = await getLatLngFromAddress(startAddress); LatLng? endCoords = await getLatLngFromAddress(endAddress); if (startCoords != null) { _markers.add( Marker( markerId: MarkerId(key.toString()), position: startCoords, infoWindow: InfoWindow( title: 'Inicio del Viaje', ), onTap: () { _showFleteInfoModal(value); }, ), ); } if (endCoords != null) { _markers.add( Marker( markerId: MarkerId(key.toString() + "_end"), position: endCoords, infoWindow: InfoWindow( title: 'Entrega del Viaje', ), ), ); } }); _addMarkersToMap(); }); } } } } void _showFleteInfoModal(Map<dynamic, dynamic> fleteInfo) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text('Detalles del Flete'), content: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildInfoRow('Descripción', fleteInfo['description'] ?? ""), _buildInfoRow('Fecha', fleteInfo['date'] ?? ""), _buildInfoRow( 'Dirección de inicio', fleteInfo['startAddress'] ?? ""), _buildInfoRow( 'Dirección de entrega', fleteInfo['endAddress'] ?? ""), _buildInfoRow('Hora', fleteInfo['time'] ?? ""), _buildInfoRow( 'Tipo de vehículo', fleteInfo['vehicleType'] ?? ""), _buildInfoRow('Pago', fleteInfo['offerRate'] ?? ""), ], ), ), actions: [ ElevatedButton( onPressed: () { // Lógica para aceptar el flete si es necesario Navigator.of(context).pop(); }, child: Text('Aceptar Flete'), ), TextButton( onPressed: () { Navigator.of(context).pop(); }, child: Text('Cerrar'), ), ], ); }, ); } Widget _buildInfoRow(String title, String value) { return Padding( padding: EdgeInsets.symmetric(vertical: 8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontWeight: FontWeight.bold, ), ), SizedBox(height: 4), Text(value), Divider( color: Colors.grey, thickness: 0.5, ), ], ), ); } void _addMarkersToMap() { _fletesList.forEach((flete) { double latitude = double.parse(flete['latitud'].toString()); double longitude = double.parse(flete['longitud'].toString()); LatLng fleteLocation = LatLng(latitude, longitude); _markers.add( Marker( markerId: MarkerId( flete['idflete'].toString()), // ID único del marcador position: fleteLocation, infoWindow: InfoWindow( title: flete['description'], // Muestra la descripción del flete ), ), ); }); } @override void initState() { super.initState(); _getFletesFromFirebase(); } @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ GoogleMap( padding: EdgeInsets.only(top: 100), mapType: MapType.normal, myLocationEnabled: true, initialCameraPosition: CameraPosition( target: LatLng(0, 0), // Coordenadas iniciales del mapa zoom: 12.0, ), onMapCreated: (GoogleMapController mapController) { controllerGoogleMap = mapController; updateMapTheme(controllerGoogleMap!); googleMapCompleterController.complete(controllerGoogleMap); // Llama a la función para obtener los marcadores después de que el mapa se haya creado _getCurrentLiveLocationOfDriver(); }, markers: _markers, ), Positioned( top: 16.0, right: 16.0, child: ElevatedButton( onPressed: () { // Llama a la función para obtener los marcadores al presionar el botón _getFletesFromFirebase(); }, child: Text('Mostrar viajes disponibles'), ), ), ], ), ); } void _getCurrentLiveLocationOfDriver() async { Position positionOfUser = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.bestForNavigation); currentPositionOfUser = positionOfUser; LatLng positionOfUserInLatLng = LatLng( currentPositionOfUser!.latitude, currentPositionOfUser!.longitude); CameraPosition cameraPosition = CameraPosition(target: positionOfUserInLatLng, zoom: 15); controllerGoogleMap! .animateCamera(CameraUpdate.newCameraPosition(cameraPosition)); } }
解决步骤
验证Firebase配置文件
确认新Firebase项目的google-services.json已完全替换旧文件,文件中的包名、SHA1/SHA256密钥与新项目控制台注册的信息完全匹配。检查Maps API密钥限制
- 确保API密钥绑定了正确的包名和SHA1值;
- 确认已启用
Maps SDK for Android和Geocoding API(代码中用到了地址转坐标功能)。
清理项目缓存
执行flutter clean命令,删除旧构建缓存后重新编译项目,避免旧配置残留。检查数据库权限
临时将新Firebase数据库规则设置为公开读写(仅开发阶段验证),确认是否是权限导致的访问失败:{ "rules": { ".read": true, ".write": true } }修复代码异步逻辑问题
_getFletesFromFirebase中mapData.forEach的异步操作无法在setState内正确等待完成,导致标记添加不及时,修改后的方法如下:Future<void> _getFletesFromFirebase() async { DatabaseReference databaseReference = FirebaseDatabase.instance.ref().child('fletes'); DatabaseEvent event = await databaseReference.once(); DataSnapshot dataSnapshot = event.snapshot; if (dataSnapshot.value != null && dataSnapshot.value is Map) { Map<dynamic, dynamic> mapData = dataSnapshot.value as Map<dynamic, dynamic>; Set<Marker> newMarkers = {}; await Future.wait(mapData.entries.map((entry) async { String key = entry.key.toString(); Map<dynamic, dynamic> value = entry.value; String startAddress = value['startAddress']; String endAddress = value['endAddress']; LatLng? startCoords = await getLatLngFromAddress(startAddress); LatLng? endCoords = await getLatLngFromAddress(endAddress); if (startCoords != null) { newMarkers.add( Marker( markerId: MarkerId(key), position: startCoords, infoWindow: const InfoWindow(title: 'Inicio del Viaje'), onTap: () => _showFleteInfoModal(value), ), ); } if (endCoords != null) { newMarkers.add( Marker( markerId: MarkerId('${key}_end'), position: endCoords, infoWindow: const InfoWindow(title: 'Entrega del Viaje'), ), ); } })); if (mounted) { setState(() { _markers = newMarkers; _addMarkersToMap(); }); } } }
内容的提问来源于stack exchange,提问作者Sergio Sanchez

