紧接上篇,对于一个代办事项的新增,修改,删除功能都已经完成了,可是数据都是保存在内存中的,从新启动应用数据就重置了,为了存储数据能够将数据存到手机的存储里面或者存到远程服务器上,本文就实现如何使用 dio 将数据存到服务器git
源码地址github
在 pubspec.yaml
添加 dio 依赖;一个存储数据的服务,我用的是 jsonboxjson
dependencies:
dio: ^3.0.1
复制代码
因为这个应用只有一个服务地址,因此建立一个 dio 的单例来进行请求就很好了,新建一个 request.dart 文件配置 dio,使用一个函数返回建立的 dio 实例服务器
基本设置下就能够用了,其它设置能够查看 dio 的文档app
import 'package:dio/dio.dart';
const DB_URL = 'https://jsonbox.io/box_7ea9df49e805cf99509b';
Dio craeteDio() {
BaseOptions options = BaseOptions(
baseUrl: DB_URL,
connectTimeout: 5000,
receiveTimeout: 3000,
);
Dio dio = Dio(options);
dio.interceptors.add(LogInterceptor(
error: true,
request: false,
responseBody: true,
responseHeader: false,
requestHeader: false,
));
return dio;
}
复制代码
因为须要从服务器上获取 todo 数据,服务返回的数据是 json 格式,因此须要在拿到数据的时候将单个 todo 的 json 数据转成 Todo 实例,新建一个 model/todo.dart 文件,比以前多的是两个方法而已,fromJson
这个工厂函数做用是使用 json 数据实例化一个 Todo,toJson
方法用来将一个 Todo 转成一个 Map 结构的数据less
若是一个模型的字段较少能够手写,可是当字段较多比较复杂的时候就须要使用工具来帮助生成代码了,我使用的是 quicktype 这个工具async
class Todo {
String id;
bool finish;
String thing;
Todo({
this.id,
this.thing,
this.finish,
});
factory Todo.fromJson(Map<String, dynamic> json) => Todo(
id: json["_id"].toString(),
thing: json["thing"],
finish: json["finish"],
);
Map<String, dynamic> toJson() => {
"id": id,
"thing": thing,
"finish": finish,
};
}
复制代码
配置好 dio 就能够在 todos.dart 向服务器发送请求了,修改 store/todos.dart,给 Todos 类添加了一个 _dio 属性用来发送请求,一个 getTodos 方法用来获取所有 todo 的列表数据,而后修改 addTodo,removeTodo,editTodo 方法使用 _dio 向服务器发送 post,delelte,put 请求。ide
须要注意的一点是将 json 转换成实例的问题,很容易就会出现相似函数
type 'List<dynamic>' is not a subtype of type 'List<Todo>'
复制代码
这种错误,这种都是类型转换的问题,我看了一篇文章后才算弄懂了一点 parsing-complex-json-in-flutter工具
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import '../request.dart';
import '../model/todo.dart';
class Todos extends ChangeNotifier {
List<Todo> _items = [];
Dio _dio = craeteDio();
get items {
return [..._items];
}
void refresh() {
notifyListeners();
}
Future<List> getTodos() async {
try {
Response response = await _dio.get('/todos');
final list = response.data as List;
_items = List<Todo>.from(list.map((i) => Todo.fromJson(i)).toList());
return items;
} on DioError catch (err) {
throw err;
}
}
Future addTodo(String thing) async {
try {
Response response = await _dio.post('/todos', data: {
"thing": thing,
"finish": false,
});
Todo todo = Todo(
thing: thing,
id: response.data["_id"],
finish: response.data["finish"],
);
_items.insert(0, todo);
refresh();
} on DioError catch (err) {
throw err;
}
}
Future removeTodo(int index) async {
try {
String todoId = _items[index].id;
await _dio.delete("/todos/$todoId");
_items.removeAt(index);
refresh();
} catch (err) {
throw err;
}
}
Future editTodo(int index, String thing, bool finish) async {
String todoId = _items[index].id;
try {
await _dio.put("/todos/$todoId", data: {
"thing": thing,
"finish": finish,
});
Todo todo = _items[index];
todo.thing = thing;
todo.finish = finish;
refresh();
} catch (e) {
throw e;
}
}
void toggleFinish(int index) {
final todo = _items[index];
todo.finish = !todo.finish;
refresh();
}
bool isTodoExist(String thing) {
bool isExist = false;
for (var i = 0; i < _items.length; i++) {
final todo = _items[i];
if (todo.thing == thing) {
isExist = true;
}
}
return isExist;
}
}
复制代码
有了数据后就能够在列表页使用了,因为如今数据是从服务器返回的,会有请求耗时,因此须要使用 FutureBuilder
这个部件渲染列表,FutureBuilder
须要一个设置一个 future 来判断状态,这里天然是 Todos 类的 getTodos
方法返回的 Future 对象,而后 builder 就是一个函数,有两个参数,一个是 context 上下文对象,一个是 snapshot 对象,表示的是这个 future 的状态。
在 builder 方法里面用一个 switch 语句判断这个 future 的状态,根据状态返回须要渲染的部件,有如下几种状态 none(状态不存在),active(运行中),waiting(等待中),done(完成),若是都不匹配就,返回一个 null 值。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'store/todos.dart';
import 'widget/add_todo_button.dart';
import 'widget/edit_todo_button.dart';
import 'widget/remove_todo_button.dart';
class TodosPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Flutter Provider Todos')),
body: FutureBuilder(
future: Provider.of<Todos>(context).getTodos(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('Press button to start.');
case ConnectionState.active:
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
case ConnectionState.done:
if (snapshot.hasError) {
print(snapshot.error);
return Center(
child: Text(
'出错了,请重试',
style: TextStyle(fontSize: 18.0, color: Colors.red),
),
);
}
List items = snapshot.data;
if (items == null) {
return Center(
child: Text(
'尚未代办事项,快去添加吧',
style: TextStyle(fontSize: 18.0),
),
);
}
return ListView.builder(
itemCount: items.length,
itemBuilder: (_, index) {
return Column(
children: <Widget>[
ListTile(
title: Text(
items[index].thing,
style: TextStyle(
color: items[index].finish
? Colors.green
: Colors.grey,
),
),
trailing: Container(
width: 150,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
EditTodoButton(todoIndex: index),
RemoveTodoButton(todoIndex: index),
],
),
),
),
Divider(),
],
);
});
}
return null;
},
),
floatingActionButton: Consumer<Todos>(
builder: (_, todos, child) {
return AddTodoButton();
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
}
复制代码
接下来就是须要修改新增,编辑,删除代办的按钮了,同理因为如今须要跟服务端进行通讯,因此须要根据请求状态来处理逻辑,主要的修改就是使用 async/await
语法等到一个请求完成后,根据返回值进行处理。
添加 Todo 按钮
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../store/todos.dart';
class AddTodoButton extends StatefulWidget {
@override
_AddTodoButtonState createState() => _AddTodoButtonState();
}
class _AddTodoButtonState extends State<AddTodoButton> {
final _formKey = GlobalKey<FormState>();
final _controller = TextEditingController();
@override
void dispose() {
_formKey.currentState.dispose();
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Consumer<Todos>(
builder: (_, todos, child) {
_addTodo() async {
final isValid = _formKey.currentState.validate();
if (!isValid) {
return;
}
final thing = _controller.value.text;
try {
await todos.addTodo(thing);
Navigator.pop(context);
_controller.clear();
} catch (e) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text('新增代办失败了,请重试。')),
);
}
}
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
print('add todo');
return showDialog(
context: context,
builder: (BuildContext _) {
return SimpleDialog(
title: Text('添加 Todo'),
contentPadding: const EdgeInsets.all(24.0),
children: <Widget>[
Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
autofocus: true,
autovalidate: false,
controller: _controller,
keyboardType: TextInputType.text,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: '输入你想作的事',
),
validator: (val) {
if (val.isEmpty) {
return '想作的事不能为空';
}
bool isExist = todos.isTodoExist(val);
if (isExist) {
return '这件事情已经存在了';
}
return null;
},
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FlatButton(
child: Text('取消'),
onPressed: () {
Navigator.pop(context);
},
),
RaisedButton(
child: Text(
'肯定',
style: TextStyle(color: Colors.white),
),
color: Theme.of(context).primaryColor,
onPressed: _addTodo,
),
],
),
],
),
),
],
);
},
);
},
);
},
);
}
}
复制代码
编辑 Todo 按钮
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../model/todo.dart';
import '../store/todos.dart';
class EditTodoButton extends StatefulWidget {
final todoIndex;
const EditTodoButton({Key key, this.todoIndex}) : super(key: key);
@override
_EditTodoButtonState createState() => _EditTodoButtonState();
}
class _EditTodoButtonState extends State<EditTodoButton> {
final _formKey = GlobalKey<FormState>();
@override
void dispose() {
_formKey?.currentState?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Consumer<Todos>(
builder: (context, todos, child) {
final todoIndex = widget.todoIndex;
final Todo todo = todos.items[todoIndex];
return IconButton(
color: Colors.blue,
icon: Icon(Icons.edit),
onPressed: () {
return showDialog(
context: context,
builder: (_) {
return SimpleDialog(
title: Text('编辑 Todo'),
contentPadding: const EdgeInsets.all(24.0),
children: <Widget>[
Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
autofocus: false,
autovalidate: false,
initialValue: todo.thing,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: '输入你想作的事',
),
onChanged: (val) {
todo.thing = val;
},
validator: (val) {
if (val.isEmpty) {
return '想作的事不能为空';
}
return null;
},
),
SizedBox(height: 20),
SwitchListTile(
title: const Text('是否完成'),
value: todo.finish,
onChanged: (bool value) {
todo.finish = value;
},
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FlatButton(
child: Text('取消'),
onPressed: () => Navigator.pop(context),
),
RaisedButton(
child: Text(
'肯定',
style: TextStyle(color: Colors.white),
),
color: Theme.of(context).primaryColor,
onPressed: () async {
final isValid =
_formKey.currentState.validate();
if (!isValid) {
return;
}
try {
await todos.editTodo(
todoIndex,
todo.thing,
todo.finish,
);
Navigator.pop(context);
} catch (e) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text('修改代办失败了,请重试。')),
);
}
},
)
],
),
],
),
),
],
);
},
);
},
);
},
);
}
}
复制代码
删除 Todo 按钮
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../model/todo.dart';
import '../store/todos.dart';
class RemoveTodoButton extends StatelessWidget {
final int todoIndex;
const RemoveTodoButton({Key key, this.todoIndex}) : super(key: key);
@override
Widget build(BuildContext context) {
return Consumer<Todos>(builder: (_, todos, child) {
final Todo todo = todos.items[todoIndex];
return IconButton(
color: Colors.red,
icon: Icon(Icons.delete),
onPressed: () {
print('delete todo');
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('确认删除 ${todo.thing}?'),
actions: <Widget>[
FlatButton(
child: Text(
'取消',
style: TextStyle(color: Colors.grey),
),
onPressed: () => Navigator.pop(context),
),
FlatButton(
child: Text('确认'),
onPressed: () async {
await todos.removeTodo(todoIndex);
Navigator.pop(context);
},
),
],
);
},
);
},
);
});
}
}
复制代码
至此全部的数据都存储在服务器上了,重启应用数据也会从服务器上获取了。