前言
在实际的项目中,如果碰到一个数据库操作,就使用一条SQL语句对数据库操作,那么不仅仅代码非常杂乱,而且弄不好可能造成内存溢出,所以我们常常需要封装SQL操作,封装成SQLHelpers来进行操作。
有过后端开发经验的程序员来说,都或多或少使用过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的形式显示在界面上,如果数据库没有数据,则显示圆形进度条。显示的效果图,如博文首图所示。