问题描述
我正在制作一个待办事项列表应用程序。我编写了代码并使用 dart migrate feature 来保证空值安全。我不知道如何解决这个问题,有人可以帮助我。
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),],)
],);
}
}
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<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(),// );
// }