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

Flutter GetX迁移:将StatefulWidget的setState转为无状态组件

问题:Flutter GetX迁移地图页面时的状态更新错误及控制器修改方案

问题背景

我正尝试将地图页面迁移至Flutter GetX状态管理,需将原StatefulWidget中的setState逻辑转换为StatelessWidget实现,已拆分视图与控制器,要在GoogleMap加载完成前显示LoadScreen。

当前MapsPage代码:

class MapsPage2 extends StatelessWidget {
  final GoogleMapController _googleMapController =
      Get.put(GoogleMapController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Obx(
      () => locationController.currentLatLng == null
          ? LoadScreen()
          : Stack(
              children: [
                Container(
                  child: GoogleMap(
                    initialCameraPosition: CameraPosition(
                        //get user location
                        target: locationController.currentLatLng!,
                        zoom: 16),
                    minMaxZoomPreference: MinMaxZoomPreference(15.5, 19),
                    zoomGesturesEnabled: true,
                    cameraTargetBounds: CameraTargetBounds(
                      LatLngBounds(
                        northeast: LatLng(43.7970928, -79.3067414),
                        southwest: LatLng(43.592580, -79.483674),
                      ),
                    ),
                  ),
                ),
              ],
            ),
    ));
  }
}

当前LocationController代码:

class LocationController extends GetxController {
  static LocationController instance = Get.find();
  Position? myLocation;
  Rx<Geolocator>? geolocator = Geolocator().obs;
  LatLng? currentLatLng;
  RxBool isLoading = false.obs;

  @override
  void onInit() async {
    super.onInit();
    getpermission();
  }

  getpermission() async {
    bool serviceEnabled;
    LocationPermission permission;
    serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      await Geolocator.openLocationSettings();
      return Future.error("location service is not enabled");
    }
    permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      //do stuff here
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        //stuff
        return Future.error("location permissions denied");
      }
    }
    if (permission == LocationPermission.deniedForever) {
      return Future.error("location permissions permanently denied");
    }

    myLocation = await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high);
  }

  //next func here
}

终端报错信息:

The following message was thrown building Obx(has builder, dirty, state:
_ObxState#66e23):
[Get] the improper use of a GetX has been detected.
You should only use GetX or Obx for the specific widget that will be updated.
If you are seeing this error, you probably did not insert any observable variables
into
GetX/Obx
or insert them outside the scope that GetX considers suitable for an update
(example: GetX => HeavyWidget => variableObservable).
If you need to update a parent widget and a child widget, wrap each one in an
Obx/GetX.

The relevant error-causing widget was:
Obx
Obx:file:///Users/juliapak/Documents/my%20programs/coffeesoc/lib/pages/mapview_page.da
rt:16:15

错误原因及修复方案

错误核心原因

报错是因为Obx中依赖的currentLatLng不是GetX的可观测变量(Rx类型),Obx无法监听它的变化,触发了GetX的使用警告。同时控制器的初始化和位置获取逻辑也未正确更新可观测状态。

LocationController修改步骤

  1. 将currentLatLng改为可观测的Rx<LatLng?>类型,替换普通LatLng?;
  2. 移除无用的Rx<Geolocator>?(Geolocator无需作为可观测对象);
  3. 获取位置后更新可观测的currentLatLng,并正确管理加载状态;
  4. 修正onInit的异步写法(不在onInit直接加async,内部调用异步方法);
  5. 规范控制器实例获取方式,避免未初始化就调用Get.find()。

修改后的LocationController代码:

class LocationController extends GetxController {
  static LocationController get instance => Get.find<LocationController>();
  
  Position? myLocation;
  final Rx<LatLng?> currentLatLng = Rx<LatLng?>(null);
  final RxBool isLoading = true.obs; // 初始设为加载中

  @override
  void onInit() {
    super.onInit();
    _getPermissionAndLocation(); // 内部调用异步方法
  }

  void _getPermissionAndLocation() async {
    isLoading.value = true;
    try {
      bool serviceEnabled;
      LocationPermission permission;

      // 检查位置服务是否开启
      serviceEnabled = await Geolocator.isLocationServiceEnabled();
      if (!serviceEnabled) {
        await Geolocator.openLocationSettings();
        throw Exception("位置服务未开启");
      }

      // 检查权限
      permission = await Geolocator.checkPermission();
      if (permission == LocationPermission.denied) {
        permission = await Geolocator.requestPermission();
        if (permission == LocationPermission.denied) {
          throw Exception("位置权限被拒绝");
        }
      }
      if (permission == LocationPermission.deniedForever) {
        throw Exception("位置权限被永久拒绝");
      }

      // 获取当前位置并更新可观测变量
      myLocation = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
      currentLatLng.value = LatLng(myLocation!.latitude, myLocation!.longitude);
    } catch (e) {
      // 添加错误处理逻辑,比如弹窗提示用户
      print("获取位置失败: $e");
    } finally {
      isLoading.value = false;
    }
  }
}

MapsPage2代码修正

  1. 正确获取LocationController实例,移除无用的GoogleMapController初始化(如果不需要的话);
  2. 在Obx中监听currentLatLng和isLoading的可观测状态,精准控制加载界面与地图的切换。

修改后的MapsPage2代码:

class MapsPage2 extends StatelessWidget {
  final LocationController locationController = Get.put(LocationController()); // 初始化控制器

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Obx(
        () {
          // 加载中或位置未获取到,显示LoadScreen
          if (locationController.isLoading.value || locationController.currentLatLng.value == null) {
            return LoadScreen();
          }
          // 位置获取成功,显示地图
          return Stack(
            children: [
              GoogleMap(
                initialCameraPosition: CameraPosition(
                  target: locationController.currentLatLng.value!,
                  zoom: 16,
                ),
                minMaxZoomPreference: const MinMaxZoomPreference(15.5, 19),
                zoomGesturesEnabled: true,
                cameraTargetBounds: CameraTargetBounds(
                  LatLngBounds(
                    northeast: const LatLng(43.7970928, -79.3067414),
                    southwest: const LatLng(43.592580, -79.483674),
                  ),
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}

关键说明

  • 驱动UI更新的变量必须是GetX的Rx类型(如Rx<LatLng?>、RxBool),通过.value修改或访问;
  • Obx只会监听其builder函数内部直接引用的Rx变量,确保变量在builder范围内被使用;
  • 异步操作中要正确管理加载状态,给用户明确的反馈;
  • 控制器初始化尽量在页面入口用Get.put(),避免未初始化时调用Get.find()导致崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 17:10:24