写flutter程序 Android Studio 版本
打开Android Studio 新建 Flutter 项目
项目放在 WorkSpace/flutterWorkSpace/项目名
选择 Open iOS Simulator
debug模式运行 , 修改后代码后点击闪电图标热加载
编写第一个flutter程序
打开Android Studio 新建 Flutter 项目
pubspec文件管理Flutter应用程序的assets(资源,如图片、package等)。 在pubspec.yaml中,将english_words(3.1.0或更高版本)添加到依赖项列表,如下面高亮显示的行:
dependencies: flutter: sdk: flutter cupertino_icons: ^0.1.0 english_words: ^3.1.0
单击右上角的 Pub get ,这会将依赖包安装到您的项目。您可以在控制台中看到以下内容:
/Users/jiangangkong/flutter/bin/flutter --no-color pub get Running "flutter pub get" in startup_01... 8.0s Process finished with exit code 0
如遇到Error: Cannot run with sound null safety, because the following dependencies don’t support null safety: 则在该项目根目录终端上输入:flutter run --no-sound-null-safety
或者 我们可以在运行的时候添加--no-sound-null-safety
。打开Android Studio,然后依次选择【Run】 –>【 Edit Configurations】 –> 【Add Additional Run args 】–> 【–no-sound-null-safety】,如下图。
设置lcon和启动画面 ios:
在ios/Runner/Assets.xcassets/AppIcon.appiconset
中设置lcon图片, 并修改Contents.json
在ios/Runner/Assets.xcassets/LaunchImage.imageset
中设置启动图片,并在同级别的Contents.json
文件中配置。
路由管理 fluro
import 'package:flutter/material.dart' ;import 'routers/routes.dart' ;import 'routers/application.dart' ;import 'package:fluro/fluro.dart' ;import 'pages/first_page.dart' ;void main() { FluroRouter router = FluroRouter(); Application.router = router; Routes.configureRoutes(router); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super (key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo' , theme: ThemeData( primarySwatch: Colors.blue, ), home: FirstPage(), ); } }
import 'package:fluro/fluro.dart' ;class Application { static late final FluroRouter router; }
routers/router_handler.dart
import 'package:fluro/fluro.dart' ;import '../pages/second_page.dart' ;import '../pages/first_page.dart' ;import '../pages/third_page.dart' ;import '../pages/fourth_page.dart' ;import '../pages/fifth_page.dart' ;import '../pages/error_page.dart' ;import 'package:flutter/material.dart' ;var firstPageHandler = new Handler( handlerFunc: (BuildContext? context, Map <String , List <String >> params) { return FirstPage(); }); var secondPageHandler = new Handler( handlerFunc: (BuildContext? context, Map <String , List <String >> params) { return SecondPage(); }); var thirdPageHandler = new Handler( handlerFunc: (BuildContext? context, Map <String , List <String >> params) { return ThirdPage(); }); var fourthPageHandler = new Handler( handlerFunc: (BuildContext? context, Map <String , List <String >> params) { return FourthPage(); }); var fifthPageHandler = new Handler( handlerFunc: (BuildContext? context, Map <String , List <String >> params) { return FifthPage(); });
/ import 'package:fluro/fluro.dart' ;import 'package:flutter/material.dart' ;import 'router_handler.dart' ;import '../pages/error_page.dart' ;class Routes { static String page1 = "/1" ; static String page2 = "/2" ; static String page3 = "/3" ; static String page4 = "/4" ; static String page5 = "/5" ; static void configureRoutes(FluroRouter router) { router.notFoundHandler = Handler( handlerFunc: (BuildContext? context, Map <String , List <String >> params) { return ErrorPage(); }); router.define(page1, handler: firstPageHandler); router.define(page2, handler: secondPageHandler); router.define(page3, handler: thirdPageHandler); router.define(page4, handler: fourthPageHandler); router.define(page5, handler: fifthPageHandler); } }
import 'package:flutter/material.dart' ;import 'package:fluro/fluro.dart' ;import '../routers/application.dart' ;class FirstPage extends StatefulWidget { const FirstPage({Key? key}) : super (key: key); @override State<FirstPage> createState() => _FirstPageState(); } class _FirstPageState extends State <FirstPage > { int _selectedIndex = 0 ; @override Widget build(BuildContext context) { onGenerateRoute: Application.router.generator ; return Scaffold( appBar: AppBar( title: const Text('第一页' ), ), bottomNavigationBar: BottomNavigationBar( items: [ BottomNavigationBarItem(icon: Icon(Icons.account_tree_rounded), label:'UPCOMING' ), BottomNavigationBarItem(icon: Icon(Icons.add_chart_outlined), label:'RESULTS' ), BottomNavigationBarItem(icon: Icon(Icons.add_box_sharp), label:'FIGHT PASS' ), BottomNavigationBarItem(icon: Icon(Icons.abc_sharp), label:'NEWS' ), BottomNavigationBarItem(icon: Icon(Icons.accessibility_outlined), label:'ATHLETES' ), ], type: BottomNavigationBarType.fixed, unselectedFontSize: 12 , selectedFontSize: 12 , iconSize: 28 , currentIndex: _selectedIndex, fixedColor: Color.fromRGBO(221 , 0 , 0 , 1 ), onTap: _onItemTapped, unselectedItemColor: Colors.white, backgroundColor: Colors.black87 ), ); } void _onItemTapped(int index){ Application.router.navigateTo( context, "/${index+1 } " , transition: TransitionType.fadeIn ); } }
状态管理 provider 短时状态 与 应用状态 短时状态(有时也称 用户界面 (UI) 状态 或者 局部状态 )是你可以完全包含在一个独立 widget 中的状态。
这是一个有点儿模糊的定义,这里有几个例子。
一个 PageView
组件中的当前页面
一个复杂动画中当前进度
一个 BottomNavigationBar
中当前被选中的 tab
widget 树中其他部分不需要访问这种状态。不需要去序列化这种状态,这种状态也不会以复杂的方式改变。换句话说,不需要使用状态管理架构(例如 ScopedModel, Redux)去管理这种状态。你需要用的只是一个 StatefulWidget
。
如果你想在你的应用中的多个部分之间共享一个非短时的状态,并且在用户会话期间保留这个状态,我们称之为应用状态(有时也称共享状态)。
应用状态的一些例子:
用户选项
登录信息
一个社交应用中的通知
一个电商应用中的购物车
一个新闻应用中的文章已读/未读状态
provider使用 dependencies: flutter: sdk: flutter provider: ^6.0 .0
import 'package:provider/provider.dart' ;
三个概念
ChangeNotifier
是 Flutter SDK 中的一个简单的类。它用于向监听器发送通知。换言之,如果被定义为 ChangeNotifier
,你可以订阅它的状态变化。(这和大家所熟悉的观察者模式相类似)。
class ShowAthletes extends ChangeNotifier { void showAthletesService() async { var options = BaseOptions( baseUrl: "http://127.0.0.1:8000/api" , connectTimeout: 5000 , receiveTimeout: 3000 , ); Dio dio = Dio(options); var formData = FormData.fromMap({'level' : currentIndex}); Response response = await dio.post("/showAthletes" , data: formData); currentLen = response.data["len" ]; notifyListeners(); } }
ChangeNotifierProvider 【在mian中注册该ChangeNotifier】
ChangeNotifierProvider
widget 可以向其子孙节点暴露一个 ChangeNotifier
实例。它属于 provider
package。
我们已经知道了该把 ChangeNotifierProvider
放在什么位置:在需要访问它的 widget 之上。在 CartModel
里,也就意味着将它置于 MyCart
和 MyCatalog
之上。
你肯定不愿意把 ChangeNotifierProvider
放的级别太高(因为你不希望破坏整个结构)。但是在我们这里的例子中,MyCart
和 MyCatalog
之上只有 MyApp
。
void main() { FluroRouter router = FluroRouter(); Application.router = router; Routes.configureRoutes(router); Provider.debugCheckInvalidValueType = null ; runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => ShowAthletes()), ], child: MyApp(), ) ); }
Widget buildAthleteList(){ return Consumer<ShowAthletes>( builder: (context,sa,child){ return Expanded( child: ListView.builder( itemBuilder: (BuildContext context, int index) { return ListTile( leading: Icon(Icons.settings,color: Colors.blue,), title: Text("师傅麻烦你把我送到解放碑" ,style: TextStyle(color: Colors.white24),), subtitle: Text("和他挥手拜拜有缘再会" ,style: TextStyle(color: Colors.white24),), ); }, itemCount: currentLen, ) , ); } ); }
我们必须指定要访问的模型类型。在这个示例中,我们要访问 CartModel
那么就写上 Consumer<CartModel>
。
Consumer
widget 唯一必须的参数就是 builder。当 ChangeNotifier
发生变化的时候会调用 builder 这个函数。(换言之,当你在模型中调用 notifyListeners()
时,所有相关的 Consumer
widget 的 builder 方法都会被调用。)
builder 在被调用的时候会用到三个参数。第一个是 context
。在每个 build 方法中都能找到这个参数。
builder 函数的第二个参数是 ChangeNotifier
的实例。它是我们最开始就能得到的实例。你可以通过该实例定义 UI 的内容。
第三个参数是 child
,用于优化目的。如果 Consumer
下面有一个庞大的子树,当模型发生改变的时候,该子树 并不会 改变,那么你就可以仅仅创建它一次,然后通过 builder 获得该实例。
最好能把 Consumer
放在 widget 树尽量低的位置上。你总不希望 UI 上任何一点小变化就全盘重新构建 widget 吧。
onPressed: () async { setState(() { _swt = !_swt; }); int? selectedIndex = await _showCustomModalBottomSheet( context, _options); if (selectedIndex == null ) { setState(() { _swt = !_swt; }); } final sa = ShowAthletes(); Provider.of<ShowAthletes>(context,listen:false ).showAthletesService(); print ("自定义底部弹层:选中了第$selectedIndex 个选项" ); },
有的时候你不需要模型中的 数据 来改变 UI,但是你可能还是需要访问该数据。比如,ClearCart
按钮能够清空购物车的所有商品。它不需要显示购物车里的内容,只需要调用 clear()
方法。
我们可以使用 Consumer<CartModel>
来实现这个效果,不过这么实现有点浪费。因为我们让整体框架重构了一个无需重构的 widget。
所以这里我们可以使用 Provider.of
,并且将 listen
设置为 false
。
Provider.of<CartModel>(context, listen: false ).removeAll();
在 build 方法中使用上面的代码,当 notifyListeners
被调用的时候,并不会使 widget 被重构。
视频播放插件 种类
flutter+django 前后端分离 在前后端分离的应用模式中,我们通常将后端开发的每个视图都称为一个接口,或者API,前端通过访问接口来对数据进行增删改查。
什么是RESTful API? 要弄清楚什么是RESTful API,首先要弄清楚什么是REST。REST – REpresentational State Transfer,英语的直译就是“表现层状态转移”。如果看这个概念,估计没几个人能明白是什么意思。那下面就让我来用一句人话解释一下什么是RESTful:URL定位资源,用HTTP动词(GET,POST,PUT,DELETE)描述操作。RESTful 是典型的基于HTTP的协议。
Resource:
资源,即数据。Representational:
某种表现形式,比如用JSON,XML,JPEG等;State Transfer:
状态变化。通过HTTP动词实现。
所以RESTful API就是REST风格的API。 那么在什么场景下使用RESTful API呢?在当今的互联网应用的前端展示媒介很丰富。有手机、有平板电脑还有PC以及其他的展示媒介。那么这些前端接收到的用户请求统一由一个后台来处理并返回给不同的前端肯定是最科学和最经济的方式,RESTful API就是一套协议来规范多种形式的前端和同一个后台的交互方式。
RESTful API由后台也就是SERVER来提供前端来调用。前端调用API向后台发起HTTP请求,后台响应请求将处理结果反馈给前端。也就是说RESTful 是典型的基于HTTP的协议。那么RESTful API有哪些设计原则和规范呢?
资源。首先是弄清楚资源的概念。资源就是网络上的一个实体,一段文本,一张图片或者一首歌曲。资源总是要通过一种载体来反应它的内容。文本可以用TXT,也可以用HTML或者XML、图片可以用JPG格式或者PNG格式,JSON是现在最常用的资源表现形式。
统一接口。RESTful风格的数据元操CRUD(create,read,update,delete)分别对应HTTP方法:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源,这样就统一了数据操作的接口。
URI。可以用一个URI(统一资源定位符)指向资源,即每个URI都对应一个特定的资源。要获取这个资源访问它的URI就可以,因此URI就成了每一个资源的地址或识别符。一般的,每个资源至少有一个URI与之对应,最典型的URI就是URL。
无状态。所谓无状态即所有的资源都可以URI定位,而且这个定位与其他资源无关,也不会因为其他资源的变化而变化。有状态和无状态的区别,举个例子说明一下,例如要查询员工工资的步骤为第一步:登录系统。第二步:进入查询工资的页面。第三步:搜索该员工。第四步:点击姓名查看工资。这样的操作流程就是有状态的,查询工资的每一个步骤都依赖于前一个步骤,只要前置操作不成功,后续操作就无法执行。如果输入一个URL就可以得到指定员工的工资,则这种情况就是无状态的,因为获取工资不依赖于其他资源或状态,且这种情况下,员工工资是一个资源,由一个URL与之对应可以通过HTTP中的GET方法得到资源,这就是典型的RESTful风格。
说了这么多,到底RESTful长什么样子的呢?GET:http://www.xxx.com/source/id 获取指定ID的某一类资源。例如GET:http://www.xxx.com/friends/123表示获取ID为123的会员的好友列表。如果不加id就表示获取所有会员的好友列表。
POST:http://www.xxx.com/friends/123表示为指定ID为123的会员新增好友。其他的操作类似就不举例了。
RESTful API设计准则 应该尽量将API部署在专用域名之下 https://example.org/api/
应该将API的版本号放入URL https://example.org/app/1.0/foo
, 但这个是不强制的 路径又被称为终点,表示API的具体地址,每个地址代表一种资源。资源只能是名词不能是动词,而且名词往往和数据库的表名相对应。同时,利用HTTP方法(post, get, put, delete)可以分离网址中资源名称的操作。
GET /products POST /products GET /products/ 4 PATCH /products/ 4 PUT /products/ 4 DELETE /products/ 4 HEAD OPTIONS
API中的名词应该使用复数,无论是子资源或者所有资源
过滤信息;如果记录数量很多,服务器不可能都将它们全部返回。API需要提供参数,过滤返回结果
?l imit=10 ?o ffset=10 ?p age=2 &per_page=100 ?s hortby=name&order=asc ?a nimal_type_id=1
RESTful API最好做到Hypermedia(即返回结果中提供链接,连向其它API方法)
服务器返回的数据格式,应该尽量采用json格式,避免使用XML
后端开发(REST接口开发)的核心任务 后端只负责返回前端需要的数据,不再渲染HTML页面,不再控制前端的效果。无论哪种前端,所需的数据基本相同,所以后端只需要开发一套逻辑对外提供数据就可以了。
在前后端分离的应用模式中,我们通常将后端开发的每个视图都称为一个接口,或者API,前端通过访问接口来对数据进行增删改查。
将请求的数据(如json格式的数据)转换成模型类对象操作数据库, 将模型类对象转换成响应的数据,比如Json格式 因此:数据类型的转换就涉及到序列化和反序列化:
在开发REST API 接口时,视图中要频繁地进行序列化和反序列化的编写。
序列化和反序列化 序列化:将程序中的一个数据结构类型转换为其它格式(字典,json, xml等),比如将django 中的模型类对象转换为json字符串,这个转换过程我们成为序列化。
books_all=Books.objects.all () book_list=[] for book in books_all: book_list.append({ 'id' :book.id , 'title' :book.title, 'author' :book.author, 'publish_time' :book.publish_time }) return JsonResponse(book_list, safe=False )
反序列化:将其它格式(json,字典,XML等)转换成程序中的数据。例如将Json字符串转换成Django中的模型类对象。
json_bytes =request.bodyjson_str =json_bytes.decode()book_dict =json.loads(json_str)book = Books.objects.create( title =book_dict.get('title'), publish_time =datetime.striptime(book_dict.get('publish_time'), '%Y-%m-%d' ).date() )
前后端分离实际操作
django中安装并在settings.py中注册
pip3 install djangorestframework
pip3 install django-cors-headers
INSTALLED_APPS = [ 'django.contrib.admin' , 'django.contrib.auth' , 'django.contrib.contenttypes' , 'django.contrib.sessions' , 'django.contrib.messages' , 'django.contrib.staticfiles' , 'apps.user' , 'rest_framework' , 'corsheaders' , ]
from django.db import modelsclass User (models.Model): """ 用户表 """ username = models.CharField(verbose_name="用户" , max_length=20 , null=False ) password = models.CharField(verbose_name="密码" , max_length=16 , null=False )
在user根目录添加serializers.py文件
from rest_framework import serializersfrom apps.user.models import Userfrom apps.user import modelsclass UserSerializer (serializers.HyperlinkedModelSerializer): class Meta : model = models.User fields = "__all__"
python3 manage.py makemigrations python3 manage.py migrate
实际开发注意事项
拆分appbar进components一定要实现PreferredSizeWidget
借口
class MyAppBar extends StatefulWidget implements PreferredSizeWidget { const MyAppBar({Key? key}) : super (key: key); @override Size get preferredSize => const Size.fromHeight(100 ); @override State<MyAppBar> createState() => _MyAppBarState(); } class _MyAppBarState extends State <MyAppBar > { @override Widget build(BuildContext context) { return AppBar( title: const Text("UFC" ), ); } }
appBar中的actions组件设置高度要用UnconstrainedBox包裹去除父样式
actions: [ UnconstrainedBox( child :SizedBox( height: 27 , width: 70 , child: ElevatedButton( child: Text("live now" ), style: ElevatedButton.styleFrom( primary: Color.fromRGBO(254 , 46 , 36 , 1 ), padding: new EdgeInsets.only(bottom: 3 ), minimumSize: const Size(90 , 20 ), maximumSize: const Size(90 , 20 ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5 ))), onPressed: () { print ("aaa" ); }, ), ), ), ],
运行项目后若出现The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.
前端传”type”:2 后端要用”2”接收; 并且多个数据要用formData传
var formData = FormData.fromMap({ "username" : usernameController.text, 'password' : passwordController.text, 'type' : 2 , }); Response response = await dio.post("/login" , data: formData);
loginOrRegister = request.POST.get("type" ) ... if loginOrRegister == "2" :
Scaffold.of() called with a context that does not contain a Scaffold. 比如:Drawer点不出
leading: Builder(builder: (BuildContext context){ return IconButton( icon: Icon(Icons.menu), tooltip: "Search" , onPressed: () { Scaffold.of(context).openDrawer(); } ); }),
Scaffold下body中有多个组件,其中之一为列表时,要用Expanded包裹
level = request.POST.get("level" ) athletes = models.Athlete.objects.filter (level=level) num = models.Athlete.objects.filter (level=level).count() result = serializers.serialize('json' , athletes) res = { 'len' : num, 'data' : result, } return HttpResponse(json.dumps(res), content_type="application/json" )
class ShowAthletes extends ChangeNotifier { void showAthletesService() async { var options = BaseOptions( baseUrl: "http://127.0.0.1:8000/api" , connectTimeout: 5000 , receiveTimeout: 3000 , ); Dio dio = Dio(options); var formData = FormData.fromMap({'level' : currentIndex}); Response response = await dio.post("/showAthletes" , data: formData); currentLen = response.data["len" ]; notifyListeners(); list = jsonDecode(response.data["data" ]); if (list.isNotEmpty){ for (int i = 0 ;i< list.length;i++){ print (list[i]["fields" ]["chineseName" ]); } } }
*flutter持久化 [实际开发一般都是前后端分离] sqlite和mysql
和MySQL相比,SQLite支持的数据类型较少。
SQLite的可移植性较好,而MySQL较差。 SQLite库大小约为250 KB,而MySQL服务器大约为600 MB。SQLite直接将信息存储在单个文件中,使其易于复制。不需要任何配置,并且可以使用最少的支持来完成该过程。
MySQL有构造良好的用户管理系统,而SQLite没有。 SQLite没有任何特定的用户管理功能,因此不适合多用户访问。MySQL有一个构造良好的用户管理系统,可以处理多个用户并授予不同级别的权限。SQLite适用于较小的数据库,随着数据库的增长,使用SQLite时内存需求也会变大。使用SQLite时,性能优化更加困难。相反,MySQL易于扩展,可以轻松处理更大的数据库。
SQLite没有内置的身份验证机制,而MySQL有,其安全性较高。
优点和缺点 – sqlite与mysql sqlite的优点 :
基于文件,易于设置和使用
适合基础开发和测试
轻松携带
使用标准SQL语法进行微小更改
使用方便
SQLite的缺点 :
缺乏用户管理和安全功能
不容易扩展
不适合大数据库
无法定制
MySQL的优点 :
使用方便
提供了许多与数据库相关的功能
良好的安全功能
易于扩展,适用于大型数据库
提供良好的速度和性能
提供良好的用户管理和多种访问控制
MySQL的缺点 :
需要一些技术专业知识来设置
与传统SQL相比,语法略有不同
sqflite sqflite是一款轻量级的关系型数据库,类似SQLite。在Flutter平台我们使用sqflite库来同时支持Android 和iOS。数据种类:
INTEGER : Dart type: int
REAL : Dart type: num
TEXT : Dart type: String
BLOB : Dart type: Uint8List
flutter使用sqflite
dependencies: flutter: sdk: flutter fluro: ^2.0 .3 dio: ^4.0 .6 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons . cupertino_icons : ^1.0.2 sqflite : ^2.0.3+1
import 'package:sqflite/sqflite.dart' ;
var db = await openDatabase('my_db.db' );
sqflite原生操作 // 使用getDatabasesPath获取位置 var databasesPath = await getDatabasesPath(); String path = join(databasesPath, 'demo.db'); // 删除数据库 await deleteDatabase(path); // 打开数据库 Database database = await openDatabase(path, version: 1, onCreate: (Database db, int version) async { // When creating the db, create the table await db.execute( 'CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER, num REAL)'); }); // 在事务中插入一些记录 await database.transaction((txn) async { int id1 = await txn.rawInsert( 'INSERT INTO Test(name, value, num) VALUES("some name", 1234, 456.789)'); print('inserted1: $id1'); int id2 = await txn.rawInsert( 'INSERT INTO Test(name, value, num) VALUES(?, ?, ?)', ['another name', 12345678, 3.1416]); print('inserted2: $id2'); }); // 更新记录 int count = await database.rawUpdate( 'UPDATE Test SET name = ?, value = ? WHERE name = ?', ['updated name', '9876', 'some name']); print('updated: $count'); // 查找全部 List<Map> list = await database.rawQuery('SELECT * FROM Test'); List<Map> expectedList = [ {'name': 'updated name', 'id': 1, 'value': 9876, 'num': 456.789}, {'name': 'another name', 'id': 2, 'value': 12345678, 'num': 3.1416} ]; print(list); print(expectedList); assert(const DeepCollectionEquality().equals(list, expectedList)); // 记录条数 count = Sqflite .firstIntValue(await database.rawQuery('SELECT COUNT(*) FROM Test')); assert(count == 2); // 删除记录 count = await database .rawDelete('DELETE FROM Test WHERE name = ?', ['another name']); assert(count == 1); // 关闭数据库 await database.close();
SQL helpers操作 final String tableTodo = 'todo'; final String columnId = '_id'; final String columnTitle = 'title'; final String columnDone = 'done'; // 实体类 class Todo { int id; String title; bool done; Map<String, Object?> toMap() { var map = <String, Object?>{ columnTitle: title, columnDone: done == true ? 1 : 0 }; if (id != null) { map[columnId] = id; } return map; } Todo(); Todo.fromMap(Map<String, Object?> map) { id = map[columnId]; title = map[columnTitle]; done = map[columnDone] == 1; } } class TodoProvider { Database db; // 打开数据库并创建表 Future open(String path) async { db = await openDatabase(path, version: 1, onCreate: (Database db, int version) async { await db.execute(''' create table $tableTodo ( $columnId integer primary key autoincrement, $columnTitle text not null, $columnDone integer not null) '''); }); } // 插入记录 Future<Todo> insert(Todo todo) async { todo.id = await db.insert(tableTodo, todo.toMap()); return todo; } // 根据id查询记录 Future<Todo> getTodo(int id) async { List<Map> maps = await db.query(tableTodo, columns: [columnId, columnDone, columnTitle], where: '$columnId = ?', whereArgs: [id]); if (maps.length > 0) { return Todo.fromMap(maps.first); } return null; } // 根据id删除记录 Future<int> delete(int id) async { return await db.delete(tableTodo, where: '$columnId = ?', whereArgs: [id]); } // 给一个实体类,根据它的id更新记录 Future<int> update(Todo todo) async { return await db.update(tableTodo, todo.toMap(), where: '$columnId = ?', whereArgs: [todo.id]); } // 关闭数据库 Future close() async => db.close(); }
事务 不要使用数据库,而只使用事务中的Transaction对象来访问数据库
await database.transaction((txn) async { await txn.execute('CREATE TABLE Test1 (id INTEGER PRIMARY KEY)' ); await database.execute('CREATE TABLE Test2 (id INTEGER PRIMARY KEY)' ); });
批量操作 batch = db.batch(); batch.insert('Test' , {'name' : 'item' }); batch.update('Test' , {'name' : 'new_item' }, where: 'name = ?' , whereArgs: ['item' ]); batch.delete('Test' , where: 'name = ?' , whereArgs: ['item' ]); results = await batch.commit();
警告,在事务期间,批处理在事务提交之前不会被提交
await database.transaction((txn) async { var batch = txn.batch(); await batch.commit(); });
默认情况下,批处理一旦遇到错误就会停止(通常会恢复未提交的更改)。你可以忽略错误,这样即使有一个操作失败,每个成功的操作都会运行并提交:
await batch.commit (continueOnError: true ) ;