如何在Flutter中修复“null check operator used on a null value”错误?
如何在Flutter中修复“null check operator used on a null value”错误?
我正在制作一个待办事项列表应用。我编写了代码并使用了dart的空安全特性。我不知道如何解决这个问题,可以有人帮我吗?
运行应用程序时显示的错误日志
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';
Future
if (_db == null) {
_db = await _initDb();
}
return _db;
}
Future
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>> getTaskMapList() async {
Database db = await (this.db as FutureOr
final List
return result;
}
Future> getTaskList() async {
final List
final List
taskMapList.forEach((taskMap) {
taskList.add(Task.fromMap(taskMap));
});
taskList.sort((taskA, taskB) => taskA.date!.compareTo(taskB.date!));
return taskList;
}
Future
Database db = await (this.db as FutureOr
final int result = await db.insert(tasksTable, task.toMap());
return result;
}
Future
Database db = await (this.db as FutureOr
final int result = await db.update(
tasksTable,
task.toMap(),
where: '$colId=',
whereArgs: [task.id],
);
return result;
}
Future
Database db = await (this.db as FutureOr
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.priority, this.status, this.title});
Map
final map = Map
map['id'] = id;
map['title'] = title;
map['date'] = date!.toIso8601String();
map['priority'] = priority;
map['status'] = status;
return map;
}
factory Task.fromMap(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
final _formKey = GlobalKey
String? _title = '';
String? _priority = '';
DateTime? _date = DateTime.now();
int? _status = 0;
TextEditingController _dateController = TextEditingController();
final DateFormat _dateFormatter = DateFormat('MMM dd, yyyy');
final List
@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),
);
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),
child: TextFormField(
style: TextStyle(fontSize: 18),
controller: _dateController,
onTap: _handleDatePicker,
readOnly: true,
decoration: InputDecoration(
labelText: 'Date',
labelStyle: TextStyle(fontSize: 18),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 20),
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',
labelStyle: TextStyle(fontSize: 18),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
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),
height: 60,
width: double.infinity,
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(30),
),
child: TextButton(
onPressed: _delete,
child: Text(
'Delete',
style: TextStyle(
color: Colors.white,
fontSize: 20,
),
),
),
)
: SizedBox(height: 0),
],
),
)
],
),
),
),
),
);
}
}
todo_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
Future>? _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,
decoration: task.status == 0
? TextDecoration.none
: TextDecoration.lineThrough),
),
trailing: Checkbox(
value: task.status == 1 ? true : false,
onChanged: (value) {
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> snapshot) {
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,
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: 10,
),
Text(
'$completedTaskCount of ${snapshot.data!.length}',
style: TextStyle(
color: Colors.grey,
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
],
),
);
}
return _buildTask(snapshot.data![index - 1]);
},
);
},
),
);
}
}
在Flutter中,当我们使用FutureBuilder
构建Widget时,它会接收一个Future对象,并使用builder函数根据该Future的状态来构建Widget。由于Future是异步的,数据不会立即可用,这可能会导致问题的出现。
在代码中,你使用了snapshot.data!.where
,但没有检查数据是否实际存在。由于snapshot.data
在Future完成之前将为null,所以会出现这个错误。
你需要取消注释下面的代码,因为这段代码会检查数据是否实际存在:
// if (!snapshot.hasData) {
// return Center(
// child: CircularProgressIndicator(),
// );
// }
你可能会发现,取消注释后,CircularProgressIndicator会一直运行,并且当点击按钮添加任务时,会显示不同的错误。
如果进度条不停止运行,说明你的Future尚未完成。可能需要检查它花费了多少时间以及为什么花费了这么长时间。
调试控制台
Unhandled Exception: type 'Future<Database?>' is not a subtype of type 'FutureOr<Database>' in type cast