尝试在 Flutter 中获取地点的自动完成位置并在点击的地点上显示详细信息时出现空安全错误

Posted

技术标签:

【中文标题】尝试在 Flutter 中获取地点的自动完成位置并在点击的地点上显示详细信息时出现空安全错误【英文标题】:Getting null safety errors when tried to get autocomplete location of places in Flutter and display details on tapped place 【发布时间】:2021-10-07 04:24:46 【问题描述】:

我尝试将 no null 安全代码迁移到 null 安全,但最终出现错误。我想在 Flutter 中自动完成地点的位置,并在点击的地方显示详细信息。

错误截图:

代码:

main.dart

import 'package:flutter/material.dart';
import 'package:google_places_flutter/address_search.dart';
import 'package:google_places_flutter/place_service.dart';
import 'package:uuid/uuid.dart';

void main() 
  runApp(MyApp());


class MyApp extends StatelessWidget 
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Google Places Demo',

      home: MyHomePage(title: 'Places Autocomplete Demo'),
    );
  


class MyHomePage extends StatefulWidget 
  MyHomePage(Key? key, this.title) : super(key: key);

  final String? title;

  @override
  _MyHomePageState createState() => _MyHomePageState();


class _MyHomePageState extends State<MyHomePage> 
  final _controller = TextEditingController();
  String? _streetNumber = '';
  String? _street = '';
  String? _city = '';
  String? _zipCode = '';

  @override
  void dispose() 
    _controller.dispose();
    super.dispose();
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title!),
      ),
      body: Container(
        margin: EdgeInsets.only(left: 20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            TextField(
              controller: _controller,
              readOnly: true,
              onTap: () async 
                // generate a new token here
                final sessionToken = Uuid().v4();
                final Suggestion? result = await showSearch(
                  context: context,
                  delegate:AddressSearch(sessionToken),
                );
                // This will change the text displayed in the TextField
                if (result != null) 
                  final placeDetails = await PlaceApiProvider(sessionToken)
                      .getPlaceDetailFromId(result.placeId);
                  setState(() 
                    _controller.text = result.description!;
                    _streetNumber = placeDetails.streetNumber;
                    _street = placeDetails.street;
                    _city = placeDetails.city;
                    _zipCode = placeDetails.zipCode;
                  );
                
              ,
              decoration: InputDecoration(
                icon: Container(
                  width: 10,
                  height: 10,
                  child: Icon(
                    Icons.home,
                    color: Colors.black,
                  ),
                ),
                hintText: "Enter address",
                border: InputBorder.none,
                contentPadding: EdgeInsets.only(left: 8.0, top: 16.0),
              ),
            ),
            SizedBox(height: 20.0),
            Text('Street Number: $_streetNumber'),
            Text('Street: $_street'),
            Text('City: $_city'),
            Text('ZIP Code: $_zipCode'),
          ],
        ),
      ),
    );
  

address_search.dart

import 'package:flutter/material.dart';
import 'package:google_places_flutter/place_service.dart';

class AddressSearch extends SearchDelegate<Suggestion?> 
  AddressSearch(this.sessionToken) 
    apiClient = PlaceApiProvider(sessionToken);
  

  final sessionToken;
  late PlaceApiProvider apiClient;

  @override
  List<Widget> buildActions(BuildContext context) 
    return [
      IconButton(
        tooltip: 'Clear',
        icon: Icon(Icons.clear),
        onPressed: () 
          query = '';
        ,
      )
    ];
  

  @override
  Widget buildLeading(BuildContext context) 
    return IconButton(
      tooltip: 'Back',
      icon: Icon(Icons.arrow_back),
      onPressed: () 
        close(context, null);
      ,
    );
  

  @override
  Widget buildResults(BuildContext context) 
    return Container();
  

  @override
  Widget buildSuggestions(BuildContext context) 
    return FutureBuilder(
      future: query == ""
          ? null
          : apiClient.fetchSuggestions(
              query, Localizations.localeOf(context).languageCode),
      builder: (context, snapshot) => query == ''
          ? Container(
              padding: EdgeInsets.all(16.0),
              child: Text('Enter address'),
            )
          : snapshot.hasData
              ? ListView.builder(
        itemBuilder: (context, index) =>
            ListTile(
              title:
              Text((snapshot.data[index] as Suggestion).description!),
              onTap: () 
                close(context, snapshot.data[index] as Suggestion?);
              ,
            ),
        itemCount: snapshot.data.length,
      )
              : Container(child: Text('Loading...')),
    );
  

place_service.dart

import 'dart:convert';
import 'dart:io';

import 'package:http/http.dart';

class Place 
  String? streetNumber;
  String? street;
  String? city;
  String? zipCode;

  Place(
    this.streetNumber,
    this.street,
    this.city,
    this.zipCode,
  );

  @override
  String toString() 
    return 'Place(streetNumber: $streetNumber, street: $street, city: $city, zipCode: $zipCode)';
  


class Suggestion 
  final String? placeId;
  final String? description;

  Suggestion(this.placeId, this.description);

  @override
  String toString() 
    return 'Suggestion(description: $description, placeId: $placeId)';
  


class PlaceApiProvider 
  final client = Client();

  PlaceApiProvider(this.sessionToken);

  final sessionToken;

  static final String androidKey = 'YOUR_API_KEY_HERE';
  static final String iosKey = 'YOUR_API_KEY_HERE';
  final apiKey = Platform.isAndroid ? androidKey : iosKey;

  Future<List<Suggestion>?> fetchSuggestions(String input, String lang) async 
    final request =
        'https://maps.googleapis.com/maps/api/place/autocomplete/json?input=$input&key=$apiKey&sessiontoken=$sessionToken';
    final response = await client.get(Uri.parse(request));

    if (response.statusCode == 200) 
      final result = json.decode(response.body);
      if (result['status'] == 'OK') 
        // compose suggestions in a list
        return result['predictions']
            .map<Suggestion>((p) => Suggestion(p['place_id'], p['description']))
            .toList();
      
      if (result['status'] == 'ZERO_RESULTS') 
        return [];
      
      throw Exception(result['error_message']);
     else 
      throw Exception('Failed to fetch suggestion');
    
  

  Future<Place> getPlaceDetailFromId(String? placeId) async 
    final request =
        'https://maps.googleapis.com/maps/api/place/details/json?place_id=$placeId&fields=address_component&key=$apiKey&sessiontoken=$sessionToken';
    final response = await client.get(Uri.parse(request));

    if (response.statusCode == 200) 
      final result = json.decode(response.body);
      if (result['status'] == 'OK') 
        final components =
            result['result']['address_components'] as List<dynamic>;
        // build result
        final place = Place();
        components.forEach((c) 
          final List type = c['types'];
          if (type.contains('street_number')) 
            place.streetNumber = c['long_name'];
          
          if (type.contains('route')) 
            place.street = c['long_name'];
          
          if (type.contains('locality')) 
            place.city = c['long_name'];
          
          if (type.contains('postal_code')) 
            place.zipCode = c['long_name'];
          
        );
        return place;
      
      throw Exception(result['error_message']);
     else 
      throw Exception('Failed to fetch suggestion');
    
  

【问题讨论】:

尝试添加 bang(!) 运算符,例如 snapshot.data!.... @YeasinSheikh 我尝试使用 bang(!) 运算符,但又出现了另一个错误 1) 运算符 '[]' 没有为类型 'Object' 定义:58 2) 运算符 '[]'没有为“对象”类型定义:60 3)没有为“对象”类型定义吸气剂“长度”:63 【参考方案1】:

解决办法大概是这样的:

builder: (context, AsyncSnapshot&lt;List&lt;Suggestion&gt;&gt; snapshot)

而不是这个:

builder: (context, snapshot)

然后您可以执行以下操作:

List<Suggestion>? suggestions = snapshot.data;

if ( suggestions != null && suggestions.length > 0) 

【讨论】:

以上是关于尝试在 Flutter 中获取地点的自动完成位置并在点击的地点上显示详细信息时出现空安全错误的主要内容,如果未能解决你的问题,请参考以下文章

对区域和提供商进行地理编码

在 Flutter 测试中获取小部件的位置?

Flutter 自动完成功能在 VSCode 中无法用于包

位置 API。获取城市的区域

将地点自动完成限制为某种语言

iOS 如何定位获取周围地点信息