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

Flutter中Firestore数据加载引发rating空值异常问题求助

问题:Firestore数据加载时RatingBarIndicator空值与格式异常

我对空值检查的理解不算深入,但基本原理还是懂的😂。这次遇到空值检查异常实在搞不懂。我把数据存在textController里,用Text组件展示值,没用到TextFormField。用户通过对话框提交新数据修改,之后调用setState更新组件,本质是让用户查看资料修改,而非用表单。所以我没选FutureBuilder,因为setState会重新从Firebase拉数据覆盖修改内容。

核心问题:我有个String?类型的rating变量,在initState里通过Firestore的异步函数赋值。starting_price、description这些值都能正常显示,唯独rating出问题。一开始它是int类型,Firestore里存的是数字,后来改成String类型试了试。把rating传给组件时,因为是可空String,用空值检查运算符会触发“空值检查运算符用于空值”异常;在Text组件里传rating.toString()能正常运行,但转成double传给RatingBarIndicator(flutter_rating_bar包)时就报错。

我怀疑组件在rating赋值前就完成了构建,但明明已经在initState里初始化了。不管用不用FutureBuilder,该怎么解决?


profile_screen.dart

class ProfileScreen extends StatefulWidget {
  const ProfileScreen({super.key, required this.userId});
  final String userId;

  @override
  State<ProfileScreen> createState() => _ProfileScreenViewState();
}

class _ProfileScreenViewState extends State<ProfileScreen> {
String? rating;

@override
  void initState() {
    super.initState();
    fetchProviderData(widget.userId);
  }

Future<void> fetchProviderData(String userId) async {
    final DocumentSnapshot snapshot = await FirebaseFirestore.instance
        .collection('providers')
        .doc(userId)
        .get();

    if (snapshot.exists) {
      final data = snapshot.data() as Map<String, dynamic>?;
      if (data != null) {
        setState(() {
          startPriceController.text = data['starting_price'];
          descriptionController.text = data['description'];
          profilePicUrl = data['profile_picture'] as String;
          rating = data['rating'] as String;
          services = data['services'] as List<dynamic>;

          print(rating);
          print(startPriceController.text);
        });
      }
    }
  }

@override
  Widget build(BuildContext context) {
    print(startPriceController.text); // IT SHOWS THE CORRECT VALUES
    print(descriptionController.text);
    print(rating);
    return Scaffold(
        body: Padding(
            padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 32),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
                  Flexible(
                      child: Padding(
                          padding: const EdgeInsets.symmetric(
                              horizontal: 12, vertical: 5),
                          child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(rating.toString()),
                                 RatingBarIndicator(
                                   rating: double.parse(rating.toString()),
                                   itemCount: 5,
                                   itemSize: 15,
                                   itemBuilder: (context, _) => const Icon(
                                       Icons.star,
                                       color: Colors.amber),
                                 ),
                              ])))
                ]),
              ],
            )));
  }
}

控制台日志

I/flutter ( 4651): LANDING: PROFILE CLICKED
I/flutter ( 4651): START PRICE:
I/flutter ( 4651): DESC:
I/flutter ( 4651): RATING:
I/flutter ( 4651): START PRICE: 20.1
I/flutter ( 4651): DESC: description here
I/flutter ( 4651): RATING: 3

格式异常栈追踪

════════ Exception caught by widgets library ═══════════════════════════════════
The following FormatException was thrown building ProfileScreen(dirty, state: _ProfileScreenViewState#060dc):
Invalid double

The relevant error-causing widget was
ProfileScreen
landing_page.dart:197
When the exception was thrown, this was the stack
#0      double.parse (dart:core-patch/double_patch.dart:111:28)
#1      _ProfileScreenViewState.build
profile_screen.dart:219
#2      StatefulElement.build
framework.dart:4992
#3      ComponentElement.performRebuild
framework.dart:4878
#4      StatefulElement.performRebuild
framework.dart:5050
#5      Element.rebuild
framework.dart:4604
#6      ComponentElement._firstBuild
framework.dart:4859
#7      StatefulElement._firstBuild
framework.dart:5041
#8      ComponentElement.mount
framework.dart:4853
...     Normal element mounting (275 frames)
#283    Element.inflateWidget
framework.dart:3863
#284    MultiChildRenderObjectElement.inflateWidget
framework.dart:6435
#285    Element.updateChild
framework.dart:3592
#286    RenderObjectElement.updateChildren
framework.dart:5964
#287    MultiChildRenderObjectElement.update
framework.dart:6460
#288    Element.updateChild
framework.dart:3570
#289    ComponentElement.performRebuild
framework.dart:4904
#290    StatefulElement.performRebuild
framework.dart:5050
#291    Element.rebuild
framework.dart:4604
#292    StatefulElement.update
framework.dart:5082
#293    Element.updateChild
framework.dart:3570
#294    ComponentElement.performRebuild
framework.dart:4904
#295    Element.rebuild
framework.dart:4604
#296    ProxyElement.update
framework.dart:5228
#297    Element.updateChild
framework.dart:3570
#298    ComponentElement.performRebuild
framework.dart:4904
#299    Element.rebuild
framework.dart:4604
#300    ProxyElement.update
framework.dart:5228
#301    _InheritedNotifierElement.update
inherited_notifier.dart:107
#302    Element.updateChild
framework.dart:3570
#303    SingleChildRenderObjectElement.update
framework.dart:6307
#304    Element.updateChild
framework.dart:3570
#305    ComponentElement.performRebuild
framework.dart:4904
#306    StatefulElement.performRebuild
framework.dart:5050
#307    Element.rebuild
framework.dart:4604
#308    StatefulElement.update
framework.dart:5082
#309    Element.updateChild
framework.dart:3570
#310    SingleChildRenderObjectElement.update
framework.dart:6307
#311    Element.updateChild
framework.dart:3570
#312    SingleChildRenderObjectElement.update
framework.dart:6307
#313    Element.updateChild
framework.dart:3570
#314    ComponentElement.performRebuild
framework.dart:4904
#315    Element.rebuild
framework.dart:4604
#316    ProxyElement.update
framework.dart:5228
#317    Element.updateChild
framework.dart:3570
#318    ComponentElement.performRebuild
framework.dart:4904
#319    StatefulElement.performRebuild
framework.dart:5050
#320    Element.rebuild
framework.dart:4604
#321    BuildOwner.buildScope
framework.dart:2667
#322    WidgetsBinding.drawFrame
binding.dart:882
#323    RendererBinding._handlePersistentFrameCallback
binding.dart:378
#324    SchedulerBinding._invokeFrameCallback
binding.dart:1175
#325    SchedulerBinding.handleDrawFrame
binding.dart:1104
#326    SchedulerBinding._handleDrawFrame
binding.dart:1015
#327    _invoke (dart:ui/hooks.dart:148:13)
#328    PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:318:5)
#329    _drawFrame (dart:ui/hooks.dart:115:31)
════════════════════════════════════════════════════════════════════════════════

空值异常栈追踪

════════ Exception caught by widgets library ═══════════════════════════════════
The following _CastError was thrown building ProfileScreen(dirty, state: _ProfileScreenViewState#30924):
Null check operator used on a null value

The relevant error-causing widget was
ProfileScreen
landing_page.dart:197
When the exception was thrown, this was the stack
#0      _ProfileScreenViewState.build
profile_screen.dart:219
#1      StatefulElement.build
framework.dart:4992
#2      ComponentElement.performRebuild
framework.dart:4878
#3      StatefulElement.performRebuild
framework.dart:5050
#4      Element.rebuild
framework.dart:4604
#5      ComponentElement._firstBuild
framework.dart:4859
#6      StatefulElement._firstBuild
framework.dart:5041
#7      ComponentElement.mount
framework.dart:4853
...     Normal element mounting (275 frames)
#282    Element.inflateWidget
framework.dart:3863
#283    MultiChildRenderObjectElement.inflateWidget
framework.dart:6435
#284    Element.updateChild
framework.dart:3592
#285    RenderObjectElement.updateChildren
framework.dart:5964
#286    MultiChildRenderObjectElement.update
framework.dart:6460
#287    Element.updateChild
framework.dart:3570
#288    ComponentElement.performRebuild
framework.dart:4904
#289    StatefulElement.performRebuild
framework.dart:5050
#290    Element.rebuild
framework.dart:4604
#291    StatefulElement.update
framework.dart:5082
#292    Element.updateChild
framework.dart:3570
#293    ComponentElement.performRebuild
framework.dart:4904
#294    Element.rebuild
framework.dart:4604
#295    ProxyElement.update
framework.dart:5228
#296    Element.updateChild
framework.dart:3570
#297    ComponentElement.performRebuild
framework.dart:4904
#298    Element.rebuild
framework.dart:4604
#299    ProxyElement.update
framework.dart:5228
#300    _InheritedNotifierElement.update
inherited_notifier.dart:107
#301    Element.updateChild
framework.dart:3570
#302    SingleChildRenderObjectElement.update
framework.dart:6307
#303    Element.updateChild
framework.dart:3570
#304    ComponentElement.performRebuild
framework.dart:4904
#305    StatefulElement.performRebuild
framework.dart:5050
#306    Element.rebuild
framework.dart:4604
#307    StatefulElement.update
framework.dart:5082
#308    Element.updateChild
framework.dart:3570
#309    SingleChildRenderObjectElement.update
framework.dart:6307
#310    Element.updateChild
framework.dart:3570
#311    SingleChildRenderObjectElement.update
framework.dart:6307
#312    Element.updateChild
framework.dart:3570
#313    ComponentElement.performRebuild
framework.dart:4904
#314    Element.rebuild
framework.dart:4604
#315    ProxyElement.update
framework.dart:5228
#316    Element.updateChild
framework.dart:3570
#317    ComponentElement.performRebuild
framework.dart:4904
#318    StatefulElement.performRebuild
framework.dart:5050
#319    Element.rebuild
framework.dart:4604
#320    BuildOwner.buildScope
framework.dart:2667
#321    WidgetsBinding.drawFrame
binding.dart:882
#322    RendererBinding._handlePersistentFrameCallback
binding.dart:378
#323    SchedulerBinding._invokeFrameCallback
binding.dart:1175
#324    SchedulerBinding.handleDrawFrame
binding.dart:1104
#325    SchedulerBinding._handleDrawFrame
binding.dart:1015
#326    _invoke (dart:ui/hooks.dart:148:13)
#327    PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:318:5)
#328    _drawFrame (dart:ui/hooks.dart:115:31)
════════════════════════════════════════════════════════════════════════════════

解决方案

1. 先处理空状态再渲染RatingBar

组件第一次构建时rating是null,直接调用double.parse(rating.toString())会把null转成字符串"null",导致格式异常。先判断rating是否为空,为空时显示加载占位符:

// 替换原代码中的RatingBarIndicator
rating != null 
    ? RatingBarIndicator(
        rating: double.parse(rating!),
        itemCount: 5,
        itemSize: 15,
        itemBuilder: (context, _) => const Icon(
            Icons.star,
            color: Colors.amber),
      )
    : const SizedBox(
        height: 15,
        child: Center(child: Text("加载中...")),
      )

2. 安全转换Firestore数据类型

Firestore里的rating是数字类型,直接用as String强制转换会报错,改成.toString()安全转换:

// 修改fetchProviderData里的rating赋值
rating = data['rating'].toString();
// 如果后续不需要String类型,直接存为double更省心
// double? rating;
// rating = (data['rating'] as num).toDouble();

3. 用FutureBuilder优化(避免覆盖本地修改)

如果担心FutureBuilder重新拉数据覆盖修改,把本地修改状态和远程数据分开,优先用本地修改后的值:

// 添加本地修改的状态变量
String? localRating;

// 用户修改时更新本地状态
void updateRating(String newRating) {
  setState(() {
    localRating = newRating;
  });
}

// build方法中替换为FutureBuilder
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Padding(
      padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 32),
      child: FutureBuilder<DocumentSnapshot>(
        future: FirebaseFirestore.instance.collection('providers').doc(widget.userId).get(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          }
          final data = snapshot.data?.data() as Map<String, dynamic>?;
          final remoteRating = data?['rating'].toString();
          // 优先用本地修改的值,没有则用远程数据
          final currentRating = localRating ?? remoteRating;

          return Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Flexible(
                    child: Padding(
                      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(currentRating ?? "暂无评分"),
                          if (currentRating != null)
                            RatingBarIndicator(
                              rating: double.parse(currentRating),
                              itemCount: 5,
                              itemSize: 15,
                              itemBuilder: (context, _) => const Icon(Icons.star, color: Colors.amber),
                            )
                          else
                            const Text("暂无评分"),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            ],
          );
        },
      ),
    ),
  );
}

4. 避免强制类型转换风险

所有Firestore数据转换都用安全方式,比如`data

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 12:55:57