如何在Flutter中修复“null check operator used on a null value”错误?

15 浏览
0 Comments

如何在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 get db async {

if (_db == null) {

_db = await _initDb();

}

return _db;

}

Future _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>> getTaskMapList() async {

Database db = await (this.db as FutureOr);

final List> result = await db.query(tasksTable);

return result;

}

Future> getTaskList() async {

final List> taskMapList = await getTaskMapList();

final List taskList = [];

taskMapList.forEach((taskMap) {

taskList.add(Task.fromMap(taskMap));

});

taskList.sort((taskA, taskB) => taskA.date!.compareTo(taskB.date!));

return taskList;

}

Future insertTask(Task task) async {

Database db = await (this.db as FutureOr);

final int result = await db.insert(tasksTable, task.toMap());

return result;

}

Future updateTask(Task task) async {

Database db = await (this.db as FutureOr);

final int result = await db.update(

tasksTable,

task.toMap(),

where: '$colId=',

whereArgs: [task.id],

);

return result;

}

Future deleteTask(int? id) async {

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 toMap() {

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 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 _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),

);

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]);

},

);

},

),

);

}

}

0
0 Comments

在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

0