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

Flutter空安全迁移报错:Null不是String类型子类型

问题定位

你遇到的两个问题根源如下:

  • 第一个type 'Null' is not a subtype of type 'String'报错,和你加的空安全运算符无关,是测试代码构造User对象时传入的JSON字段和User.fromJson的读取逻辑不匹配:
    • 缺少username、photo_url两个非空必填字段
    • 最后上线时间的key写为lastSeen,和fromJson里读取的last_seen不匹配
      三个非空字段从JSON里取到的都是null,自然会触发类型转换错误。
  • 硬编码id后测试挂起无响应,有三个原因:
    1. 你在MessageService的messages方法里对可空参数activeUser直接做!强制解包是错误写法,入参为空时会直接抛运行时异常
    2. 测试逻辑没有正确初始化、释放RethinkDB连接和流订阅,后台常驻的流监听会阻止测试进程退出
    3. 没有给异步操作加超时,数据库连接异常时会无限等待
  • 你之前做的空安全适配里,只有给_changefeed加late修饰是正确的,另外两处!属于不考虑边界情况的暴力解包,会埋下运行时崩溃隐患。
修复方案

1. 修正User类与测试JSON的字段匹配问题

首先补全测试中构造User的JSON字段,修正key名:

final user = User.fromJson({
  'id': '1234',
  'username': 'test_user',
  'photo_url': 'https://test.com/avatar.png',
  'active': true,
  'last_seen': DateTime.now().toIso8601String(),
});

同时优化User类的空安全适配,增加空值兜底,修正toJson方法缺失id字段、DateTime直接序列化的问题:

import 'package:flutter/foundation.dart';

class User {
  String? id;
  final String username;
  final String photoUrl;
  final bool active;
  final DateTime lastseen;

  User({
    required this.username,
    required this.photoUrl,
    required this.active,
    required this.lastseen,
    this.id,
  });

  Map<String, dynamic> toJson() => {
        'id': id,
        'username': username,
        'photo_url': photoUrl,
        'active': active,
        'last_seen': lastseen.toIso8601String(),
      };

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as String?,
      username: json['username'] as String? ?? '',
      photoUrl: json['photo_url'] as String? ?? '',
      active: json['active'] as bool? ?? false,
      lastseen: json['last_seen'] is DateTime
          ? json['last_seen']
          : DateTime.tryParse(json['last_seen'].toString()) ?? DateTime.now(),
    );
  }
}

2. 修正MessageService的错误空安全写法

去掉messages方法里对activeUser的暴力强制解包,增加空判断,同时在启动消息监听前校验用户id是否为空:

@override
Stream<Message> messages({User? activeUser}) {
  if (activeUser != null) {
    _startReceivingMessages(activeUser);
  }
  return _controller.stream;
}

// 对应修改_startReceivingMessages的入口判断
_startReceivingMessages(User user) {
  if (user.id == null) return;
  _changefeed = r
      .table('messages')
      .filter({'to': user.id})
      .changes({'include_initial': true})
      .run(_connection)
      .asStream()
      .cast<Feed>()
      .listen((event) {
        event
            .forEach((feedData) {
              if (feedData['new_val'] == null) return;
              final message = _messageFromFeed(feedData);
              _controller.sink.add(message);
              _removeDeliveredMessage(message);
            })
            .catchError((err) => print(err))
            .onError((error, stackTrace) => print(error));
      });
}

3. 修正测试用例的资源初始化与释放逻辑

测试必须在启动前初始化数据库连接、建表,结束后释放连接和服务资源,同时给异步操作加超时避免无限挂起,参考结构如下:

import 'package:rethink_db_ns/rethink_db_ns.dart';
import 'package:test/test.dart';
// 引入对应model、service文件

void main() {
  final r = RethinkDb();
  late Connection connection;
  late MessageService sut;

  setUp(() async {
    // 确保本地RethinkDB服务已启动,默认端口28015
    connection = await r.connect(host: 'localhost', port: 28015);
    // 测试前自动建表,已存在则捕获异常跳过
    await r.db('test').tableCreate('messages').run(connection).catchError((e) {});
    sut = MessageService(r, connection);
  });

  tearDown(() async {
    await sut.dispose();
    await connection.close();
  });

  test('sent message successfully', () async {
    final user = User.fromJson({
      'id': '1234',
      'username': 'test_user',
      'photo_url': 'https://test.com/avatar.png',
      'active': true,
      'last_seen': DateTime.now().toIso8601String(),
    });

    Message message = Message(
      from: user.id!,
      to: '3456',
      timestamp: DateTime.now(),
      contents: 'this is a message',
    );

    // 5秒超时,避免连接异常时测试无限挂起
    final res = await sut.send(message).timeout(const Duration(seconds: 5));
    expect(res, true);
  });
}

注意:跑测试前必须确认本地RethinkDB服务处于正常运行状态,否则数据库连接会一直等待,表现为测试无响应。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 00:57:25