如何修复颤振中的“用于空值的空检查运算符”?

问题描述

我正在制作一个待办事项列表应用程序。我编写了代码并使用 dart migrate feature 来保证空值安全。我不知道如何解决这个问题,有人可以帮助我。

Error logs

在设备上运行应用时显示错误

This is the output screen

main.dart

import 'package:Flutter/material.dart';
import 'package:todo_list/screens/todo_list_screen.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,title: 'Todo List',theme: ThemeData(
        primarySwatch: Colors.red,),home: TodoListScreen(),);
  }
}

database_helpers.dart

import 'dart:io';

import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'package:todo_list/models/task_model.dart';

class DatabaseHelper {
  static final DatabaseHelper instance = DatabaseHelper._instance();
  static Database? _db;

  DatabaseHelper._instance();

  String tasksTable = 'task_table';
  String colId = 'id';
  String colTitle = 'title';
  String colDate = 'date';
  String colPriority = 'priority';
  String colStatus = 'status';

  //Task Table
  //id | Title | Date | Priority | Status

  Future<Database?> get db async {
    if (_db == null) {
      _db = await _initDb();
    }
    return _db;
  }

  Future<Database> _initDb() async {
    Directory dir = await getApplicationDocumentsDirectory();
    String path = dir.path + 'todo_list.db';
    final todoListDb =
        await openDatabase(path,version: 1,onCreate: _createDb);
    return todoListDb;
  }

  void _createDb(Database db,int version) async {
    await db.execute(
        'CREATE TABLE $tasksTable($colId INTEGER PRIMARY KEY AUTOINCREMENT,$colTitle TEXT,$colDate TEXT,$colPriority TEXT,$colStatus INTEGER');
  }

  Future<List<Map<String,dynamic>>> getTaskMapList() async {
    Database db = await (this.db as FutureOr<Database>);
    final List<Map<String,dynamic>> result = await db.query(tasksTable);
    return result;
  }

  Future<List<Task>> getTaskList() async {
    final List<Map<String,dynamic>> taskMapList = await getTaskMapList();
    final List<Task> taskList = [];
    taskMapList.forEach((taskMap) {
      taskList.add(Task.fromMap(taskMap));
    });
    taskList.sort((taskA,taskB) => taskA.date!.compareto(taskB.date!));
    return taskList;
  }

  Future<int> insertTask(Task task) async {
    Database db = await (this.db as FutureOr<Database>);
    final int result = await db.insert(tasksTable,task.toMap());
    return result;
  }

  Future<int> updateTask(Task task) async {
    Database db = await (this.db as FutureOr<Database>);
    final int result = await db.update(
      tasksTable,task.toMap(),where: '$colId=',whereArgs: [task.id],);
    return result;
  }

  Future<int> deleteTask(int? id) async {
    Database db = await (this.db as FutureOr<Database>);
    final int result = await db.delete(
      tasksTable,where: '$colId = ',whereArgs: [id],);
    return result;
  }
}

task_model.dart

class Task {
  int? id;
  String? title;
  DateTime? date;
  String? priority;
  int? status;

  Task({this.date,this.priority,this.status,this.title});
  Task.withId({this.id,this.date,this.title});

  Map<String,dynamic> toMap() {
    final map = Map<String,dynamic>();
    // if (id = null) {
    //   map['id'] = id;
    // }
    map['id'] = id;
    map['title'] = title;
    map['date'] = date!.toIso8601String();
    map['priority'] = priority;
    map['status'] = status;
    return map;
  }

  factory Task.fromMap(Map<String,dynamic> map) {
    return Task.withId(
      id: map['id'],date: DateTime.parse(map['date']),priority: map['priority'],status: map['status'],title: map['title'],);
  }
}

add_task_screen.dart

import 'package:Flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:todo_list/helpers/database_helpers.dart';
import 'package:todo_list/models/task_model.dart';

class AddTaskScreen extends StatefulWidget {
  final Function? updateTaskList;
  final Task? task;
  AddTaskScreen({this.task,this.updateTaskList});

  @override
  _AddTaskScreenState createState() => _AddTaskScreenState();
}

class _AddTaskScreenState extends State<AddTaskScreen> {
  final _formKey = GlobalKey<FormState>();
  String? _title = '';
  String? _priority = '';
  DateTime? _date = DateTime.Now();
  int? _status = 0;
  TextEditingController _dateController = TextEditingController();

  final DateFormat _dateFormatter = DateFormat('MMM dd,yyyy');
  final List<String> _priorities = ['Low','Medium','High'];

  @override
  void initState() {
    super.initState();

    _title = widget.task!.title;
    _date = widget.task!.date;
    _priority = widget.task!.priority;
    _status = widget.task!.status;

    _dateController.text = _dateFormatter.format(_date!);
  }

  @override
  void dispose() {
    _dateController.dispose();
    super.dispose();
  }

  _handleDatePicker() async {
    final DateTime? date = await showDatePicker(
      context: context,initialDate: _date!,firstDate: DateTime.Now(),lastDate: DateTime(2100),// currentDate: DateTime.Now(),);
    if (date != null && date != _date) {
      setState(() {
        _date = date;
      });
      _dateController.text = _dateFormatter.format(date);
    }
  }

  _delete() {
    DatabaseHelper.instance.deleteTask(widget.task!.id);
    widget.updateTaskList!();
    Navigator.pop(context);
  }

  _submit() {
    if (_formKey.currentState!.validate()) {
      _formKey.currentState!.save();

      Task task = Task(
          date: _date,title: _title,priority: _priority,status: _status);

      if (task.status == 0)
        DatabaseHelper.instance.insertTask(task);
      else {
        task.id = widget.task!.id;
        task.status = widget.task!.status;
        DatabaseHelper.instance.updateTask(task);
      }
      widget.updateTaskList!();
      Navigator.pop(context);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onTap: () => FocusScope.of(context).unfocus(),child: SingleChildScrollView(
          child: Padding(
            padding: EdgeInsets.symmetric(horizontal: 40,vertical: 80),child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,children: [
                GestureDetector(
                  onTap: () => Navigator.pop(context),child: Icon(
                    Icons.arrow_back_ios_new,size: 30,color: Theme.of(context).primaryColor,SizedBox(height: 20),Text(
                  widget.task == null ? 'Add Task' : 'Update Task',style: TextStyle(
                    fontSize: 40,fontWeight: FontWeight.bold,color: Colors.black,SizedBox(height: 10),Form(
                  key: _formKey,child: Column(
                    children: [
                      Padding(
                        padding: EdgeInsets.symmetric(vertical: 20),child: TextFormField(
                          style: TextStyle(fontSize: 18),decoration: Inputdecoration(
                            labelText: 'Title',labelStyle: TextStyle(fontSize: 18),border: OutlineInputBorder(
                              borderRadius: BorderRadius.circular(10),validator: (input) =>
                              input != null && input.trim().isEmpty
                                  ? 'Please enter a task title!'
                                  : null,onSaved: (input) {
                            if (input != null) _title = input;
                          },initialValue: _title,Padding(
                        padding: EdgeInsets.symmetric(vertical: 20),controller: _dateController,onTap: _handleDatePicker,readOnly: true,decoration: Inputdecoration(
                            labelText: 'Date',child: DropdownButtonFormField(
                          isDense: true,icon: Icon(Icons.arrow_drop_down_circle),iconSize: 22,iconEnabledColor: Theme.of(context).primaryColor,items: _priorities.map((String priority) {
                            return DropdownMenuItem(
                              value: priority,child: Text(
                                priority,style: TextStyle(
                                  color: Colors.black,fontSize: 18,);
                          }).toList(),style: TextStyle(fontSize: 18),decoration: Inputdecoration(
                            labelText: 'Priority',validator: (dynamic input) => _priority == null
                              ? 'Please select a priority level!'
                              : null,onChanged: (dynamic value) {
                            setState(() {
                              _priority = value.toString();
                            });
                          },Container(
                        margin: EdgeInsets.symmetric(vertical: 20),height: 60,width: double.infinity,decoration: Boxdecoration(
                          color: Theme.of(context).primaryColor,borderRadius: BorderRadius.circular(30),child: TextButton(
                          onpressed: _submit,child: Text(
                            widget.task == null ? 'Add' : 'Update',style: TextStyle(
                              color: Colors.white,fontSize: 20,widget.task != null
                          ? Container(
                              margin: EdgeInsets.symmetric(vertical: 20),decoration: Boxdecoration(
                                color: Theme.of(context).primaryColor,child: TextButton(
                                onpressed: _delete,child: Text(
                                  'Delete',style: TextStyle(
                                    color: Colors.white,)
                          : SizedBox(height: 0),],)
              ],);
  }
}

tod​​o_list_screen.dart

import 'package:Flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:todo_list/helpers/database_helpers.dart';
import 'package:todo_list/models/task_model.dart';
import 'package:todo_list/screens/add_task_sreen.dart';

class TodoListScreen extends StatefulWidget {
  @override
  _TodoListScreenState createState() => _TodoListScreenState();
}

class _TodoListScreenState extends State<TodoListScreen> {
  Future<List<Task>>? _taskList;
  final DateFormat _dateFormatter = DateFormat('MMM dd,yyyy');

  @override
  void initState() {
    super.initState();
    _updateTaskList();
  }

  _updateTaskList() {
    setState(() {
      _taskList = DatabaseHelper.instance.getTaskList();
    });
  }

  Widget _buildTask(Task task) {
    return Padding(
      padding: EdgeInsets.symmetric(horizontal: 25),child: Column(
        children: [
          ListTile(
            title: Text(
              task.title!,style: TextStyle(
                  fontSize: 18,decoration: task.status == 0
                      ? Textdecoration.none
                      : Textdecoration.lineThrough),subtitle: Text(
              '${_dateFormatter.format(task.date!)}·${task.priority}',style: TextStyle(
                  fontSize: 15,trailing: CheckBox(
              value: task.status == 1 ? true : false,onChanged: (value) {
                // task.status = value ? 1 : 0;
                if (value == false)
                  task.status = 0;
                else
                  task.status = 1;
                DatabaseHelper.instance.updateTask(task);
                _updateTaskList();
              },activeColor: Theme.of(context).primaryColor,onTap: () => Navigator.push(
              context,MaterialPageRoute(
                builder: (_) => AddTaskScreen(
                  updateTaskList: _updateTaskList(),task: task,Divider(),);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        backgroundColor: Theme.of(context).primaryColor,child: Icon(Icons.add),onpressed: () => Navigator.push(
          context,MaterialPageRoute(
            builder: (_) => AddTaskScreen(
              updateTaskList: _updateTaskList(),body: FutureBuilder(
        future: _taskList,builder: (context,AsyncSnapshot<List<Task>> snapshot) {
          // if (!snapshot.hasData) {
          //   return Center(
          //     child: CircularProgressIndicator(),//   );
          // }
          final int completedTaskCount = snapshot.data!
              .where((Task task) => task.status == 1)
              .toList()
              .length;

          return ListView.builder(
            padding: EdgeInsets.symmetric(
              vertical: 80,itemCount: 1 + snapshot.data!.length,itemBuilder: (BuildContext context,int index) {
              if (index == 0) {
                return Padding(
                  padding: EdgeInsets.symmetric(horizontal: 40,vertical: 20),child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,children: [
                      Text(
                        'My Tasks',style: TextStyle(
                          color: Colors.black,fontSize: 40,SizedBox(
                        height: 10,Text(
                        '$completedTaskCount of ${snapshot.data!.length}',style: TextStyle(
                          color: Colors.grey,fontWeight: FontWeight.w600,);
              }
              return _buildTask(snapshot.data![index - 1]);
            },);
        },);
  }
}

解决方法

FutureBuilder 的设计方式是从您那里获取 Future 对象并在该 Widget 的状态发生时使用 builder 函数构建您的 Future变化。

由于 Future 是异步的,您的数据不会立即可用。这就是它可能崩溃的地方。

您使用 snapshot.data!.where 时没有检查数据是否实际存在,并且由于 snapshot.data 在 Future 完成之前将为空,因此您将遇到此错误。

取消注释此代码,因为这是执行检查数据是否实际存在的代码。

// if (!snapshot.hasData) {
//   return Center(
//     child: CircularProgressIndicator(),//   );
// }