Flutter定位功能插件fl_location的使用

发布于 1周前 作者 sinazl 来自 Flutter

Flutter定位功能插件fl_location的使用

插件简介

fl_location 是一个能够访问各平台位置服务并收集设备位置数据的插件。

支持的平台

  • ✅ Android
  • ✅ iOS
  • ✅ Web

功能特性

  • 可请求位置权限。
  • 可获取设备当前的位置。
  • 可检查位置服务是否启用。
  • 可订阅 LocationStream 实时监听位置变化。
  • 可订阅 LocationServicesStatusStream 实时监听位置服务状态变化。

开始使用

要使用此插件,请在项目的 pubspec.yaml 文件中添加 fl_location 依赖。例如:

dependencies:
  fl_location: ^5.0.0

平台特定配置

🐤 Android

由于插件基于位置工作,需要在 AndroidManifest.xml 文件中添加以下权限:

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

如果需要在后台获取位置信息,还需添加以下权限(特别是支持Android 10及以上版本):

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

🐤 iOS

类似地,在 ios/Runner/Info.plist 文件中添加如下描述:

<key>NSLocationWhenInUseUsageDescription</key>
<string>用于收集位置数据。</string>

若需在后台获取位置信息,添加以下描述:

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>用于在后台收集位置数据。</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>用于在后台收集位置数据。</string>
<key>UIBackgroundModes</key>
<array>
    <string>fetch</string>
    <string>location</string>
</array>

使用方法

请求位置权限

首先检查位置权限是否已授予,如未授予则请求位置权限:

Future<bool> _requestLocationPermission({bool background = false}) async {
  if (!await FlLocation.isLocationServicesEnabled) {
    // 位置服务未启用
    return false;
  }

  LocationPermission permission = await FlLocation.checkLocationPermission();
  if (permission == LocationPermission.denied) {
    permission = await FlLocation.requestLocationPermission();
  }

  if (permission == LocationPermission.denied ||
      permission == LocationPermission.deniedForever) {
    // 位置权限已被${permission.name}
    return false;
  }

  // 处理Web端权限逻辑...
  if (kIsWeb || kIsWasm) {
    return true;
  }

  // Android需要再次请求后台位置权限...
  if (Platform.isAndroid &&
      background &&
      permission == LocationPermission.whileInUse) {
    permission = await FlLocation.requestLocationPermission();

    if (permission != LocationPermission.always) {
      // 必须始终授予位置权限才能在后台收集位置信息
      return false;
    }
  }

  return true;
}

获取当前位置

使用 getLocation 函数获取当前位置:

Future<void> _getLocation() async {
  if (await _requestLocationPermission()) {
    final Location location = await FlLocation.getLocation();
    print('Location: ${location.toJson()}');
  }
}

订阅位置流

使用 getLocationStream 函数实时监听位置变化:

StreamSubscription<Location>? _locationSubscription;

Future<void> _subscribeLocationStream() async {
  if (await _requestLocationPermission()) {
    _locationSubscription = FlLocation.getLocationStream().listen(_onLocation);
  }
}

void _onLocation(Location location) {
  print('Location: ${location.toJson()}');
}

监听位置服务状态变化

使用 getLocationServicesStatusStream 函数实时监听位置服务状态变化:

StreamSubscription<LocationServicesStatus>? _locationServicesStatusSubscription;

Future<void> _subscribeLocationServicesStatusStream() async {
  _locationServicesStatusSubscription = FlLocation.getLocationServicesStatusStream()
      .listen(_onLocationServicesStatus);
}

void _onLocationServicesStatus(LocationServicesStatus status) {
  print('LocationServicesStatus: $status');
}

示例代码

下面是一个完整的示例应用程序,演示了如何结合上述方法来创建一个简单的Flutter应用以展示位置信息:

import 'dart:async';
import 'dart:io';

import 'package:fl_location/fl_location.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() => runApp(ExampleApp());

enum ButtonState { LOADING, DONE, DISABLED }

class ExampleApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _ExampleAppState();
}

class _ExampleAppState extends State<ExampleApp> {
  StreamSubscription<Location>? _locationSubscription;

  final _resultText = ValueNotifier('');
  final _getLocationButtonState = ValueNotifier(ButtonState.DONE);
  final _subscribeLocationStreamButtonState = ValueNotifier(ButtonState.DONE);
  final _isSubscribeLocationStream = ValueNotifier(false);

  Future<bool> _requestLocationPermission({bool background = false}) async {
    if (!await FlLocation.isLocationServicesEnabled) {
      _resultText.value = '位置服务未启用。';
      return false;
    }

    LocationPermission permission = await FlLocation.checkLocationPermission();
    if (permission == LocationPermission.denied) {
      permission = await FlLocation.requestLocationPermission();
    }

    if (permission == LocationPermission.denied ||
        permission == LocationPermission.deniedForever) {
      _resultText.value = '位置权限已被${permission.name}。';
      return false;
    }

    if (kIsWeb || kIsWasm) {
      return true;
    }

    if (Platform.isAndroid &&
        background &&
        permission == LocationPermission.whileInUse) {
      permission = await FlLocation.requestLocationPermission();

      if (permission != LocationPermission.always) {
        _resultText.value =
            '必须始终授予位置权限才能在后台收集位置信息。';
        return false;
      }
    }

    return true;
  }

  Future<void> _getLocation() async {
    if (await _requestLocationPermission()) {
      _getLocationButtonState.value = ButtonState.LOADING;
      _subscribeLocationStreamButtonState.value = ButtonState.DISABLED;

      final Duration timeLimit = const Duration(seconds: 10);
      await FlLocation.getLocation(timeLimit: timeLimit).then((location) {
        _onLocation(location);
      }).onError((error, stackTrace) {
        _onError(error);
      }).whenComplete(() {
        _getLocationButtonState.value = ButtonState.DONE;
        _subscribeLocationStreamButtonState.value = ButtonState.DONE;
      });
    }
  }

  Future<void> _subscribeLocationStream() async {
    if (await _requestLocationPermission()) {
      if (_locationSubscription != null) {
        await _unsubscribeLocationStream();
      }

      _locationSubscription = FlLocation.getLocationStream()
          .handleError(_onError)
          .listen(_onLocation);

      _getLocationButtonState.value = ButtonState.DISABLED;
      _isSubscribeLocationStream.value = true;
    }
  }

  Future<void> _unsubscribeLocationStream() async {
    await _locationSubscription?.cancel();
    _locationSubscription = null;

    _getLocationButtonState.value = ButtonState.DONE;
    _isSubscribeLocationStream.value = false;
  }

  void _onLocation(Location location) {
    _resultText.value = location.toJson().toString();
  }

  void _onError(dynamic error) {
    _resultText.value = error.toString();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Flutter Location'),
          centerTitle: true,
        ),
        body: _buildContentView(),
      ),
    );
  }

  Widget _buildContentView() {
    final Widget buttonWidget = Column(
      children: [
        const SizedBox(height: 8.0),
        ValueListenableBuilder(
          valueListenable: _getLocationButtonState,
          builder: (context, state, _) {
            return _buildTestButton(
              text: 'GET Location',
              state: state,
              onPressed: _getLocation,
            );
          },
        ),
        const SizedBox(height: 8.0),
        ValueListenableBuilder(
          valueListenable: _subscribeLocationStreamButtonState,
          builder: (context, state, _) {
            return ValueListenableBuilder(
              valueListenable: _isSubscribeLocationStream,
              builder: (context, isSubscribe, __) {
                final String text;
                final VoidCallback? onPressed;
                if (isSubscribe) {
                  text = '取消订阅位置流';
                  onPressed = _unsubscribeLocationStream;
                } else {
                  text = '订阅位置流';
                  onPressed = _subscribeLocationStream;
                }

                return _buildTestButton(
                  text: text,
                  state: state,
                  onPressed: onPressed,
                );
              },
            );
          },
        ),
      ],
    );

    final Widget resultWidget = ValueListenableBuilder(
      valueListenable: _resultText,
      builder: (context, resultText, _) {
        return Text(resultText);
      },
    );

    return ListView(
      padding: const EdgeInsets.symmetric(horizontal: 10.0),
      children: [
        buttonWidget,
        const SizedBox(height: 10.0),
        resultWidget,
      ],
    );
  }

  Widget _buildTestButton({
    required String text,
    required ButtonState state,
    VoidCallback? onPressed,
  }) {
    return ElevatedButton(
      child: (state == ButtonState.LOADING)
          ? SizedBox.fromSize(
              size: const Size.square(20.0),
              child: const CircularProgressIndicator(strokeWidth: 2.0))
          : Text(text),
      onPressed: (state == ButtonState.DONE) ? onPressed : null,
    );
  }

  @override
  void dispose() {
    _locationSubscription?.cancel();
    _resultText.dispose();
    _getLocationButtonState.dispose();
    _subscribeLocationStreamButtonState.dispose();
    _isSubscribeLocationStream.dispose();
    super.dispose();
  }
}

这个示例展示了如何在Flutter应用中集成 fl_location 插件来实现基本的位置管理功能,包括请求权限、获取当前位置、订阅位置流和监听位置服务状态变化等。


更多关于Flutter定位功能插件fl_location的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter定位功能插件fl_location的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter项目中使用fl_location插件来实现定位功能的示例代码。fl_location是一个用于获取设备位置信息的Flutter插件。

首先,确保你的Flutter项目已经设置好,并且已经在pubspec.yaml文件中添加了fl_location依赖:

dependencies:
  flutter:
    sdk: flutter
  fl_location: ^4.0.0  # 请检查最新版本号

然后,运行flutter pub get来安装依赖。

接下来,我们需要在Android和iOS平台上进行一些配置,以确保定位功能能够正常工作。

Android配置

android/app/src/main/AndroidManifest.xml文件中添加以下权限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.yourapp">

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

    <!-- 其他配置 -->

</manifest>

iOS配置

ios/Runner/Info.plist文件中添加以下权限请求:

<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to location when in use.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs access to location always.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs access to location always.</string>

Flutter代码实现

接下来,我们在Flutter项目中实现定位功能。以下是一个完整的示例代码:

import 'package:flutter/material.dart';
import 'package:fl_location/fl_location.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Location Example'),
        ),
        body: LocationScreen(),
      ),
    );
  }
}

class LocationScreen extends StatefulWidget {
  @override
  _LocationScreenState createState() => _LocationScreenState();
}

class _LocationScreenState extends State<LocationScreen> {
  FlLocation _flLocation = FlLocation();
  LocationData? _locationData;
  LocationPermission _currentPermission = LocationPermission.denied;

  @override
  void initState() {
    super.initState();
    _getLocationPermission();
  }

  Future<void> _getLocationPermission() async {
    bool serviceEnabled;
    LocationPermission permission;

    // Test if location services are enabled.
    serviceEnabled = await _flLocation.serviceEnabled();
    if (!serviceEnabled) {
      return Future.error('Location services are disabled.');
    }

    permission = await _flLocation.requestPermission();
    if (permission == LocationPermission.deniedForever) {
      // Permissions are permanently denied, we cannot request permissions anymore.
      return Future.error(
          'Location permissions are permanently denied, we cannot request permissions.');
    }

    if (permission == LocationPermission.denied) {
      permission = LocationPermission.denied;
    }

    _currentPermission = permission;
    if (_currentPermission == LocationPermission.whileInUse ||
        _currentPermission == LocationPermission.always) {
      _getLocation();
    }
  }

  Future<void> _getLocation() async {
    try {
      _locationData = await _flLocation.getCurrentLocation(
        accuracy: LocationAccuracy.high,
      );
      setState(() {});
    } catch (e) {
      print(e);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(
            'Current Permission: $_currentPermission',
            style: TextStyle(fontSize: 18),
          ),
          SizedBox(height: 20),
          if (_locationData != null)
            Text(
              'Latitude: ${_locationData!.latitude}, Longitude: ${_locationData!.longitude}',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            )
          else
            Text(
              'Location data is not available.',
              style: TextStyle(fontSize: 18),
            ),
        ],
      ),
    );
  }
}

解释

  1. 依赖管理:在pubspec.yaml文件中添加fl_location依赖。
  2. 权限配置:在Android和iOS平台上配置必要的权限。
  3. Flutter代码
    • MyAppLocationScreen是两个主要的Widget。
    • _getLocationPermission方法用于请求位置权限。
    • _getLocation方法用于获取当前位置。
    • 根据权限状态和位置数据更新UI。

通过上述步骤和代码,你可以在Flutter项目中实现定位功能。确保在实际应用中处理更多的错误情况和用户交互,以提升用户体验。

回到顶部