首页 > 解决方案 > 在等待来自 moor 数据库的 Future 之后,“未处理的错误 Null 检查运算符用于空值”

问题描述

我正在尝试学习如何在颤振中实现一个 moor 数据库,但我遇到了这个错误:

I/flutter ( 5303): Moor: Sent SELECT * FROM tasks; with args []
E/flutter ( 5303): [ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: Unhandled error Null check operator used on a null value occurred in Instance of 'TodoBloc'.
E/flutter ( 5303): #0      $TasksTable.map
package:todo_app_orm/…/local/database.g.dart:153
E/flutter ( 5303): #1      MappedListIterable.elementAt (dart:_internal/iterable.dart:412:31)
E/flutter ( 5303): #2      ListIterator.moveNext (dart:_internal/iterable.dart:341:26)
E/flutter ( 5303): #3      new _GrowableList._ofEfficientLengthIterable (dart:core-patch/growable_array.dart:188:27)
E/flutter ( 5303): #4      new _GrowableList.of (dart:core-patch/growable_array.dart:150:28)
E/flutter ( 5303): #5      new List.of (dart:core-patch/array_patch.dart:50:28)
E/flutter ( 5303): #6      ListIterable.toList (dart:_internal/iterable.dart:212:44)
E/flutter ( 5303): #7      SimpleSelectStatement._mapResponse
package:moor/…/select/select.dart:70

在等待我从 moor 数据库获得的未来之后,我收到了这个错误。如果我只得到 Future 对象但只有在等待对象之后才会出现List<Task>。它发生在等待todo_bloc.dart_database.getAllTasks()中的此处之后:

class TodoBloc extends Bloc<TodoEvent, TodoState> {
  TodoBloc(this._database) : super(TodoInitial());

  final Database _database;

  @override
  Stream<TodoState> mapEventToState(
    TodoEvent event,
  ) async* {
    if (event is LoadTasks) {
      List<Task> tasks = await _database.getAllTasks();
      yield TasksUpdated(tasks);
    }
    if (event is AddTask) {
      _database.insertTask(event.task);
      final tasks = state.tasks;
      yield TasksUpdated([...tasks, event.task]);
    }
    if (event is RemoveTask) {
      await _database.deleteTask(event.task);
    }
  }
}

数据库.dart:

@UseRowClass(Task)
class Tasks extends Table {
  TextColumn get id => text()();
  TextColumn get title => text()();
  BoolColumn get isHighPriority => boolean()();

  Set<Column> get primaryKey => {id};
}

@UseMoor(tables: [Tasks])
class Database extends _$Database {
  Database()
      : super(FlutterQueryExecutor.inDatabaseFolder(
            path: 'db.sqlite', logStatements: true));

  @override
  int get schemaVersion => 1;

  Future<List<Task>> getAllTasks() => select(tasks).get();
  Future insertTask(Task task) => into(tasks).insert(task);
  Future deleteTask(Task task) => delete(tasks).delete(task);
}

任务飞镖:

class Task implements Insertable<Task> {
  Task({
    required this.id,
    required this.title,
    this.isHighPriority = false,
  });

  final String id;
  final String title;
  final bool isHighPriority;

  @override
  Map<String, Expression> toColumns(bool nullToAbsent) {
    return TasksCompanion(
      id: Value(id),
      title: Value(title),
      isHighPriority: Value(isHighPriority),
    ).toColumns(nullToAbsent);
  }
}

数据库.g.dart:

class TasksCompanion extends UpdateCompanion<Task> {
  final Value<String> id;
  final Value<String> title;
  final Value<bool> isHighPriority;
  const TasksCompanion({
    this.id = const Value.absent(),
    this.title = const Value.absent(),
    this.isHighPriority = const Value.absent(),
  });
  TasksCompanion.insert({
    required String id,
    required String title,
    required bool isHighPriority,
  })  : id = Value(id),
        title = Value(title),
        isHighPriority = Value(isHighPriority);
  static Insertable<Task> custom({
    Expression<String>? id,
    Expression<String>? title,
    Expression<bool>? isHighPriority,
  }) {
    return RawValuesInsertable({
      if (id != null) 'id': id,
      if (title != null) 'title': title,
      if (isHighPriority != null) 'is_high_priority': isHighPriority,
    });
  }

  TasksCompanion copyWith(
      {Value<String>? id, Value<String>? title, Value<bool>? isHighPriority}) {
    return TasksCompanion(
      id: id ?? this.id,
      title: title ?? this.title,
      isHighPriority: isHighPriority ?? this.isHighPriority,
    );
  }
  @override
  Map<String, Expression> toColumns(bool nullToAbsent) {
    final map = <String, Expression>{};
    if (id.present) {
      map['id'] = Variable<String>(id.value);
    }
    if (title.present) {
      map['title'] = Variable<String>(title.value);
    }
    if (isHighPriority.present) {
      map['is_high_priority'] = Variable<bool>(isHighPriority.value);
    }
    return map;
  }
  @override
  String toString() {
    return (StringBuffer('TasksCompanion(')
          ..write('id: $id, ')
          ..write('title: $title, ')
          ..write('isHighPriority: $isHighPriority')
          ..write(')'))
        .toString();
  }
}
class $TasksTable extends Tasks with TableInfo<$TasksTable, Task> {
  final GeneratedDatabase _db;
  final String? _alias;
  $TasksTable(this._db, [this._alias]);
  final VerificationMeta _idMeta = const VerificationMeta('id');
  @override
  late final GeneratedTextColumn id = _constructId();
  GeneratedTextColumn _constructId() {
    return GeneratedTextColumn(
      'id',
      $tableName,
      false,
    );
  }
  final VerificationMeta _titleMeta = const VerificationMeta('title');
  @override
  late final GeneratedTextColumn title = _constructTitle();
  GeneratedTextColumn _constructTitle() {
    return GeneratedTextColumn(
      'title',
      $tableName,
      false,
    );
  }

  final VerificationMeta _isHighPriorityMeta =
      const VerificationMeta('isHighPriority');
  @override
  late final GeneratedBoolColumn isHighPriority = _constructIsHighPriority();
  GeneratedBoolColumn _constructIsHighPriority() {
    return GeneratedBoolColumn(
      'is_high_priority',
      $tableName,
      false,
    );
  }

  @override
  List<GeneratedColumn> get $columns => [id, title, isHighPriority];
  @override
  $TasksTable get asDslTable => this;
  @override
  String get $tableName => _alias ?? 'tasks';
  @override
  final String actualTableName = 'tasks';
  @override
  VerificationContext validateIntegrity(Insertable<Task> instance,
      {bool isInserting = false}) {
    final context = VerificationContext();
    final data = instance.toColumns(true);
    if (data.containsKey('id')) {
      context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
    } else if (isInserting) {
      context.missing(_idMeta);
    }
    if (data.containsKey('title')) {
      context.handle(
          _titleMeta, title.isAcceptableOrUnknown(data['title']!, _titleMeta));
    } else if (isInserting) {
      context.missing(_titleMeta);
    }
    if (data.containsKey('is_high_priority')) {
      context.handle(
          _isHighPriorityMeta,
          isHighPriority.isAcceptableOrUnknown(
              data['is_high_priority']!, _isHighPriorityMeta));
    } else if (isInserting) {
      context.missing(_isHighPriorityMeta);
    }
    return context;
  }
  @override
  Set<GeneratedColumn> get $primaryKey => {id};
  @override
  Task map(Map<String, dynamic> data, {String? tablePrefix}) {
    final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null;
    return Task(
      id: const StringType()
          .mapFromDatabaseResponse(data['${effectivePrefix}id'])!,
      title: const StringType()
          .mapFromDatabaseResponse(data['${effectivePrefix}title'])!,
      isHighPriority: const BoolType()
          .mapFromDatabaseResponse(data['${effectivePrefix}is_high_priority'])!,
    );
  }
  @override
  $TasksTable createAlias(String alias) {
    return $TasksTable(_db, alias);
  }
}
abstract class _$Database extends GeneratedDatabase {
  _$Database(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e);
  late final $TasksTable tasks = $TasksTable(this);
  @override
  Iterable<TableInfo> get allTables => allSchemaEntities.whereType<TableInfo>();
  @override
  List<DatabaseSchemaEntity> get allSchemaEntities => [tasks];
}

谁能看到问题出在哪里?如果需要,我可以提供更多信息。

标签: flutterdartblocdart-null-safetyflutter-moor

解决方案


问题

这是由 moor_generator 4.3.0 中的错误引起的。它现在从版本 4.3.1 开始修复。仅当自定义类用于代码生成时才会发生。

问题出在 moor_generator 生成的database.g.dart文件中。变量effectivePrefix有一个值,null因为data['${effectivePrefix}id']调用.toString()方法 onnull并返回一个String值为“null”的值。所以基本上data['${effectivePrefix}id']是试图访问data['nullid']which 不存在并返回另一个null. 然后StringType().mapFromDatabaseResponse(data['${effectivePrefix}title'])!尝试使用引发错误的bang运算符(!)。 导致此问题的原始代码如下:null

  @override
  Set<GeneratedColumn> get $primaryKey => {id};
  @override
  Task map(Map<String, dynamic> data, {String? tablePrefix}) {
    final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null;
    return Task(
      id: const StringType()
          .mapFromDatabaseResponse(data['${effectivePrefix}id'])!,
      title: const StringType()
          .mapFromDatabaseResponse(data['${effectivePrefix}title'])!,
      isHighPriority: const BoolType()
          .mapFromDatabaseResponse(data['${effectivePrefix}is_high_priority'])!,
    );
  }

解决方案

这个问题可以通过${effectivePrefix}从数据中使用的键中删除来解决Map


推荐阅读