Skip to content

使用 flet-geolocator 扩展在你的 Flet 应用中访问设备定位服务。 该控件封装了 Flutter 的 geolocator 包,并提供了用于权限检查和位置流的异步辅助方法。

平台支持 ​

平台WindowsmacOSLinuxiOSAndroidWeb
支持✅✅✅✅✅✅

用法 ​

将 flet-geolocator 添加到项目依赖中:

uv

bash
uv add flet-geolocator

pip

bash
pip install flet-geolocator  [^1]

[^1]: 完成上述操作后,你还需要手动将此包添加到你的 requirements.txt 或 pyproject.toml 中。

环境要求 ​

以下各节展示了各平台所需的相关配置。

Android ​

要访问设备定位,需要进行以下配置:

注意

  • 要获取位置更新,ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION 权限中至少有一项是 必需的,如果两项都设置,则优先使用前者。
  • 指定 ACCESS_COARSE_LOCATION 权限会导致位置更新的精度大约相当于一个城市街区。由于 ACCESS_COARSE_LOCATION 只使用网络服务来计算设备位置, 在你获得第一个定位结果之前可能需要很长时间(数分钟)。更多信息见 此处。
bash
flet build apk \
  --android-permissions android.permission.ACCESS_FINE_LOCATION=true \
  --android-permissions android.permission.ACCESS_COARSE_LOCATION=true \
  --android-permissions android.permission.ACCESS_BACKGROUND_LOCATION=true \
  --android-permissions android.permission.FOREGROUND_SERVICE_LOCATION=true
toml
[tool.flet.android.permission]
"android.permission.ACCESS_FINE_LOCATION" = true
"android.permission.ACCESS_COARSE_LOCATION" = true
"android.permission.ACCESS_BACKGROUND_LOCATION" = true
"android.permission.FOREGROUND_SERVICE_LOCATION" = true

另请参阅:

iOS ​

要访问设备定位,需要进行以下配置:

bash
flet build ipa \
  --info-plist NSLocationWhenInUseUsageDescription="Some message to describe why you need this permission..."
toml
[tool.flet.ios.info]
NSLocationWhenInUseUsageDescription = "Some message to describe why you need this permission..."

另请参阅:

macOS ​

要访问设备定位,需要进行以下配置:

bash
flet build macos \
  --info-plist NSLocationUsageDescription="Some message to describe why you need this permission..." \
  --macos-entitlements com.apple.security.personal-information.location=true
toml
[tool.flet.macos.info]
NSLocationUsageDescription = "Some message to describe why you need this permission..."

[tool.flet.macos.entitlement]
"com.apple.security.personal-information.location" = true

另请参阅:

跨平台 ​

此外(或者作为替代方案),你还可以使用我们预定义的跨平台 location权限包:

bash
flet build <target_platform> --permissions location
toml
[tool.flet]
permissions = ["location"]

Web:缓存位置与实时位置 ​

在 Web 上,Geolocator.get_current_position 默认会接受浏览器缓存中最多 5 分钟前的位置, 否则它会向浏览器请求一次实时定位,如果 30 秒内没有结果就会超时。 这个默认行为的存在,是因为桌面浏览器的按需定位流程较慢,而且在静止设备上经常失败。

如果你需要更严格的时效性(或更长的缓存窗口),请传入一个带有显式 maximum_age 和/或 time_limit 的 GeolocatorWebConfiguration:

python
geo = ftg.Geolocator(
    configuration=ftg.GeolocatorWebConfiguration(
        accuracy=ftg.GeolocatorPositionAccuracy.HIGH,
        maximum_age=ft.Duration(seconds=10),
        time_limit=ft.Duration(seconds=15),
    ),
)

疑难解答 ​

macOS:Web 浏览器返回 POSITION_UNAVAILABLE 或永远无法解析 ​

在 macOS 上以 --web 模式运行 Flet 应用时,即使「定位服务」已开启、且浏览器已在 系统设置 → 隐私与安全性 → 定位服务 中被允许,浏览器仍可能无法获取位置。症状包括:

  • getCurrentPosition 挂起,或返回 Location request timed out. 错误。
  • 位置流发出 Failed to obtain position: Position update is unavailable (这是浏览器返回的 W3C POSITION_UNAVAILABLE / 错误码 2)。

这是 macOS 的 locationd 守护进程陷入卡死状态的已知问题:它会报告浏览器已被授权, 却永远不返回定位结果。重启它即可:

bash
sudo killall locationd

该守护进程会自动重新启动。你可以通过访问 browserleaks.com/geo 来独立验证浏览器一侧 —— 如果那个页面也无法获取位置,那么问题出在操作系统,而不是你的 Flet 应用。

原生(非 Web)的 macOS Flet 应用不受影响,因为它通过平台插件直接使用 CoreLocation,而不是走浏览器的 Geolocation API。

示例 ​

python
from typing import Callable

import flet as ft
import flet_geolocator as ftg


async def main(page: ft.Page):
    page.scroll = ft.ScrollMode.ADAPTIVE
    page.appbar = ft.AppBar(title=ft.Text("Geolocator Tests"))

    def handle_position_change(e: ftg.GeolocatorPositionChangeEvent):
        page.add(ft.Text(f"New position: {e.position.latitude} {e.position.longitude}"))

    def get_dialog(handler: Callable):
        return ft.AlertDialog(
            adaptive=True,
            title="Opening Location Settings...",
            content=ft.Text(
                "You are about to be redirected to the location/app settings. "
                "Please locate this app and grant it location permissions."
            ),
            actions=[ft.TextButton("Take me there", on_click=handler)],
            actions_alignment=ft.MainAxisAlignment.CENTER,
        )

    def show_snackbar(message):
        page.show_dialog(ft.SnackBar(ft.Text(message)))

    async def handle_permission_request(e: ft.Event[ft.OutlinedButton]):
        try:
            p = await geo.request_permission()
            page.add(ft.Text(f"request_permission: {p}"))
            show_snackbar(f"Permission request sent: {p}")
        except RuntimeError as ex:
            show_snackbar(f"Could not request permission: {ex}")

    async def handle_get_permission_status(e: ft.Event[ft.OutlinedButton]):
        try:
            p = await geo.get_permission_status()
            show_snackbar(f"Permission status: {p}")
        except RuntimeError as ex:
            show_snackbar(f"Could not read permission status: {ex}")

    async def handle_get_current_position(e: ft.Event[ft.OutlinedButton]):
        try:
            p = await geo.get_current_position()
            show_snackbar(f"Current position: ({p.latitude}, {p.longitude})")
            print(f"Current position: ({p.latitude}, {p.longitude})")
        except RuntimeError as ex:
            show_snackbar(f"Could not get current position: {ex}")
            print(f"Could not get current position: {ex}")

    async def handle_get_last_known_position(e):
        try:
            p = await geo.get_last_known_position()
            if p is None:
                show_snackbar("No last known position available.")
            else:
                show_snackbar(f"Last known position: ({p.latitude}, {p.longitude})")
                print(f"Last known position: ({p.latitude}, {p.longitude})")
        except RuntimeError as ex:
            show_snackbar(f"Could not get last known position: {ex}")

    async def handle_location_service_enabled(e):
        try:
            p = await geo.is_location_service_enabled()
            show_snackbar(f"Location service enabled: {p}")
        except RuntimeError as ex:
            show_snackbar(f"Could not check location service: {ex}")

    async def handle_open_location_settings(e: ft.Event[ft.OutlinedButton]):
        try:
            p = await geo.open_location_settings()
            page.pop_dialog()
            if p is True:
                show_snackbar("Location settings opened successfully.")
            else:
                show_snackbar("Location settings could not be opened.")
        except RuntimeError as ex:
            page.pop_dialog()
            show_snackbar(f"Could not open location settings: {ex}")

    async def handle_open_app_settings(e: ft.Event[ft.OutlinedButton]):
        try:
            p = await geo.open_app_settings()
            page.pop_dialog()
            if p:
                show_snackbar("App settings opened successfully.")
            else:
                show_snackbar("App settings could not be opened.")
        except RuntimeError as ex:
            page.pop_dialog()
            show_snackbar(f"Could not open app settings: {ex}")

    location_settings_dlg = get_dialog(handle_open_location_settings)
    app_settings_dlg = get_dialog(handle_open_app_settings)

    geo = ftg.Geolocator(
        configuration=ftg.GeolocatorConfiguration(
            accuracy=ftg.GeolocatorPositionAccuracy.LOW
        ),
        on_position_change=handle_position_change,
        on_error=lambda e: page.add(ft.Text(f"Error: {e.data}")),
    )

    page.add(
        ft.SafeArea(
            content=ft.Column(
                controls=[
                    ft.Row(
                        wrap=True,
                        controls=[
                            ft.OutlinedButton(
                                content="Request Permission",
                                on_click=handle_permission_request,
                            ),
                            ft.OutlinedButton(
                                content="Get Permission Status",
                                on_click=handle_get_permission_status,
                            ),
                            ft.OutlinedButton(
                                content="Get Current Position",
                                on_click=handle_get_current_position,
                            ),
                            ft.OutlinedButton(
                                content="Get Last Known Position",
                                visible=not page.web,
                                on_click=handle_get_last_known_position,
                            ),
                            ft.OutlinedButton(
                                content="Is Location Service Enabled",
                                on_click=handle_location_service_enabled,
                            ),
                            ft.OutlinedButton(
                                content="Open Location Settings",
                                visible=not page.web,  [^1]
                                on_click=lambda e: page.show_dialog(
                                    location_settings_dlg
                                ),
                            ),
                            ft.OutlinedButton(
                                content="Open App Settings",
                                visible=not page.web,  [^1]
                                on_click=lambda e: page.show_dialog(app_settings_dlg),
                            ),
                        ],
                    )
                ],
            ),
        )
    )


if __name__ == "__main__":
    ft.run(main)

描述 ​

Geolocator 继承自:ft.Service, BaseControl

一个允许你从设备获取 GPS 数据的控件。

属性 ​

名称类型默认值说明
configurationOptional[GeolocatorConfiguration]None一些额外的配置。
positionOptional[GeolocatorPosition]None设备的当前位置。(只读) 初始为 None,当位置变化时会被更新。
dataAnyskip_任意类型的自定义数据。
keyOptional['KeyValue']None用于在更新之间保持控件标识稳定的键。

只读属性 ​

名称类型默认值说明
parentOptional['BaseControl']—此控件的直接祖先(父级)。 默认为 None,只有在此控件被挂载(加入页面树)后才有值。 Page 控件(页面树的根)是个例外 —— 它的 parent 始终为 None。
page'Page'—此控件所属的页面。

事件 ​

名称类型默认值说明
on_position_changeOptional[ft.EventHandler[GeolocatorPositionChangeEvent]]None当设备位置发生变化时触发。
on_errorOptional[ft.ControlEventHandler['Geolocator']]None发生错误时触发。 事件处理器参数的 data 属性包含错误的相关信息。

方法 ​

名称类型默认值说明
get_current_positionGeolocatorPosition—使用所需的精度和设置获取设备的当前位置。 注意: 根据各种定位服务的可用情况,这可能需要数秒。建议先调用 get_last_known_position 方法获取一个已知/缓存的位置,再用 get_current_position 方法的结果更新它。 参数: configuration:位置请求的额外配置。如果未指定,则使用 configuration 属性。 返回: 设备的当前位置,类型为 GeolocatorPosition。
get_last_known_positionOptional[GeolocatorPosition]—获取存储在用户设备上的最后已知位置。精度可通过 configuration 属性定义。 返回: 设备的最后已知位置,类型为 GeolocatorPosition;如果没有可用的最后已知位置则返回 None。 抛出异常: FletUnsupportedPlatformException:如果在 Web 平台上调用。
get_permission_statusGeolocatorPermissionStatus—获取应用已被授予访问设备位置的权限。 返回: 权限的状态。
request_permissionGeolocatorPermissionStatus—向设备请求访问设备位置的权限。 返回: 权限请求的状态。
is_location_service_enabledbool—检查定位服务是否已启用。 返回: 如果定位服务已启用则返回 True,否则返回 False。
open_app_settingsbool—尝试打开应用的设置。 返回: 如果应用设置成功打开则返回 True,否则返回 False。 抛出异常: FletUnsupportedPlatformException:如果在 Web 平台上调用。
open_location_settingsbool—尝试打开设备的定位设置。 返回: 如果设备设置成功打开则返回 True,否则返回 False。 抛出异常: FletUnsupportedPlatformException:如果在 Web 平台上调用。
distance_betweenft.Number—计算所给坐标之间的距离,单位为米。 坐标之间的距离使用 Haversine 公式计算(参见 https://en.wikipedia.org/wiki/Haversine_formula)。 参数: start_latitude:起点的纬度,单位为度。 start_longitude:起点的经度,单位为度。 end_latitude:终点的纬度,单位为度。 end_longitude:终点的经度,单位为度。 返回: 坐标之间的距离,单位为米。
init———
is_isolated——返回此控件是否被标记为隔离。 隔离控件会被排除在父级驱动的更新遍历之外,并需要自行管理更新边界。
build——在控件初始化期间调用一次,用于定义其子控件。在此方法中 page 属性可用。
before_update——每当此控件被更新时都会调用此方法。 注意: 务必不要在此处调用或请求 update()。
before_event——在事件处理器执行之前拦截事件。 返回 False 可取消分发。返回 True 或 None 则继续正常的事件处理。
did_mount——在控件挂载到页面树之后调用。 重写此方法以启动需要已挂载页面的资源,例如订阅、定时器或服务监听器。
will_unmount——在控件从页面树移除之前调用。 重写此方法以释放 did_mount() 中创建的资源,例如订阅、定时器或外部句柄。
get_data_channel——解析为此 widget 在 Dart 侧分配的 [DataChannel]。模式: on_data_channel_open: Optional[ft.EventHandler[DataChannelOpenEvent]] = None def init(self): self.on_data_channel_open = self._on_open def _on_open(self, e): self._channel = self.get_data_channel(e.channel_id) 幂等 —— 底层的 Connection 会按 id 缓存 DataChannel,因此重复调用会返回同一实例。没有错误路径:该 id 始终来自框架触发的事件,所以当此方法运行时,通道在两端都已存在。
updateNone—为此控件请求一次 UI 更新。 在更改控件状态或属性后调用。控件必须已挂载到页面且未被标记为冻结。

关注我,学习大模型、大数据、Python等热门技术。