居天下之广居,立天下之正位,行天下之大道;得志,与民由之;不得志,独行其道。富贵不能淫,贫贱不能移,威武不能屈,此之谓大丈夫。
在实际的项目中,如果碰到一个数据库操作,就使用一条SQL语句对数据库操作,那么不仅仅代码非常杂乱,而且弄不好可能造成内存溢出,所以我们常常需要封装SQL操作,封装成SQLHelpers来进行操作。
有过后端开发经验的程序员来说,都或多或少使用过SQLHelpers的帮助类,下面,博主通过一个例子详解SQLHelpers如何封装。
假如现在博主这里有一个需求,需要做一个新闻类的App,新闻有标题和详细信息,那么如何创建一个SQLHelpers来操作新闻数据呢?不妨直接上代码把:
import 'package:sqflite/sqflite.dart';
final String tableNew='news';
final String columnId='_Id';
final String columnTitle='title';
final String columnDet='detailed';
class News{
int id;
String title;
String detailed;
/***
* 将数据转换为键值对
*/
Map<String,dynamic> toMap() {
var map = <String, dynamic>{
columnTitle: title,
columnDet: detailed,
};
if (id != null) {
map[columnId] = id;
}
return map;
}
News();
/***
* 将数据转换为类
*/
News.fromMap(Map<String,dynamic> map){
id=map[columnId];
title=map[columnTitle];
detailed=map[detailed];
}
}
class NewsProvider{
Database db;
/***
* 打开数据库并创建表格
*/
Future open(String path) async{
db=await openDatabase(path,version: 1,onCreate: (Database db,int version) async{
await db.execute('''
create table $tableNew(
$columnId integer primary key autoincrement,
$columnTitle text not null,
$columnDet text not null)
''');
});
}
/***
* 插入数据
*/
Future<News> insert(News news) async{
news.id=await db.insert(tableNew, news.toMap());
return news;
}
/***
* 根据ID获取数据
*/
Future<News> getNews(int id)async{
List<Map> maps=await db.query(
tableNew,
columns: [columnId,columnTitle,columnDet],
where: '$columnId=?',
whereArgs: [id],
);
if(maps.length>0){
return News.fromMap(maps.first);
}
return null;
}
/***
* 根据ID删除数据
*/
Future<int> delete(int id)async{
return await db.delete(tableNew,where: '$columnId=?',whereArgs: [id]);
}
/***
* 根据ID更新数据
*/
Future<int> update(News news)async{
return await db.update(tableNew, news.toMap(),where: '$columnId=?',whereArgs: [news.id]);
}
Future close() async=>db.close();//关闭数据库
}
这里我们将新闻的数据库封装成了SQLHelpers:NewsProvider,这样我们就可以针对数据进行反复的操作,不需要额外的其他的代码,这样程序执行起来就比较高效了。
知道怎么封装SQLHelpers类后,我们接着通过实战员工打卡系统,熟练掌握操作。大致的需求是用户在登录应用时有一个输入框,可以输入员工的姓名,然后点击保存,保存完之后可以在列表记录并显示所有已打卡的用户,打卡完成之后可以进入打卡浏览界面浏览所有打过卡的员工,也可以在保存界面清楚打卡数据。
这一次,我们直接封装成一个DBProvider类,通过直接调用这个类就可以操作数据库,首先,我们需要定义一个用户类,代码如下所示:
final String tableUser='user';
final String columnId='_id';
final String columnName='name';
class User{
int id;
String name;
/***
* 将类转换为键值对
*/
Map<String,dynamic> toMap(){
var map=<String,dynamic>{
columnName:name,
};
if(id!=null){
map[columnId]=id;
}
return map;
}
User();
/***
* 将键值对转换为类
*/
User.fromMap(Map<String,dynamic> map){
id=map[columnId];
name=map[columnName];
}
}
定义了一个Model类后,接着就是我们的SQLHelpers的实现,代码如下:
class DBProvider{
DBProvider._();
static final DBProvider db=DBProvider._();
Database _database;
Future<Database> get database async{
if(_database!=null){
return _database;
}
_database=await initDB();
return _database;
}
initDB() async{
Directory documentsDirectory=await getApplicationDocumentsDirectory();
String path=join(documentsDirectory.path,'UserDB.db');
return await openDatabase(path,version: 1,onOpen: (db){},onCreate: (Database db,int version)async{
await db.execute('''
create table $tableUser(
$columnId integer primary key autoincrement,
$columnName text not null)
''');
});
}
Future<User> insert(User user) async{
final db=await database;
user.id=await db.insert(tableUser, user.toMap());
return user;
}
Future<User> getUser(int id) async{
final db=await database;
List<Map> maps=await db.query(tableNew,columns: [columnId,columnName],
where: '$columnId=?',
whereArgs: [id]);
if(maps.length>0){
return User.fromMap(maps.first);
}
return null;
}
Future<List<User>> getAllUser() async{
final db=await database;
var res=await db.query("User");
List<User> list=res.isNotEmpty?res.map((c)=>User.fromMap(c)).toList():[];
return list;
}
Future<int> delete(int id) async{
final db=await database;
return await db.delete(tableUser,where: '$columnId=?',whereArgs: [id]);
}
Future<int> update(User user)async{
final db=await database;
return await db.update(tableUser, user.toMap(),where: '$columnId=?',whereArgs: [user.id]);
}
removeAll() async{
final db=await database;
db.delete(tableUser);
}
Future close() async{
final db=await database;
db.close();
}
}
这段代码与前面基本类似,唯一不同的是这里我们使用了单例模式,接着,我们要定义界面,首先是打卡界面,代码如下:
class _MyHomePageState extends State<MyHomePage> {
final TextEditingController textEditingController=new TextEditingController();
_saveData(){
User user=User();
user.name=textEditingController.text;
DBProvider.db.insert(user);
Navigator.of(context).push(
new MaterialPageRoute(builder: (context){
return new PageResult();
})
);
}
_removeData(){
DBProvider.db.removeAll();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
children: <Widget>[
TextField(
controller: textEditingController,
autofocus: true,
decoration: new InputDecoration(hintText: '请输入打卡姓名'),
),
RaisedButton(
child: Text('保存'),
onPressed: _saveData,
),
RaisedButton(
child: Text('清除数据'),
onPressed: _removeData,
),
],
),
),
);
}
}
上面代码界面控件有三个,一个输入文本框,两个按钮,一个按钮记录打卡数据,所以点击后saveData,一个清楚数据库所有数据removeData,基本都是常用的代码,这里就不赘述了,接着就是打卡后,显示的详细打卡详情列表的界面,代码如下:
class PageResult extends StatelessWidget{
PageResult({Key key}):super(key:key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('已打卡人员'),
),
body: FutureBuilder<List<User>>(
future: DBProvider.db.getAllUser(),
builder: (BuildContext context,AsyncSnapshot<List<User>> snapshot){
if(snapshot.hasData){
return ListView.builder(
itemBuilder: (BuildContext context,int index){
User item=snapshot.data[index];
return ListTile(
title: Text(item.name),
);
},
itemCount: snapshot.data.length,
);
}else{
return Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}
这段代码就是获取数据库中的数据以ListView的形式显示在界面上,如果数据库没有数据,则显示圆形进度条。显示的效果图,如博文首图所示。