Skip to content

Commit 07d82ce

Browse files
committed
Shopping list local DB
1 parent fab0b2e commit 07d82ce

File tree

7 files changed

+331
-53
lines changed

7 files changed

+331
-53
lines changed

lib/add_item.dart

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_sqlite/sqlite_helper.dart';
3+
4+
class AddItemForm extends StatefulWidget {
5+
AddItemForm({
6+
Key? key,
7+
required this.onChange
8+
}) : super(key: key);
9+
final Function onChange;
10+
11+
@override
12+
State<AddItemForm> createState() => _AddItemFormState();
13+
}
14+
15+
class _AddItemFormState extends State<AddItemForm> {
16+
bool showLoader = false;
17+
TextEditingController controller = TextEditingController();
18+
19+
@override
20+
Widget build(BuildContext context) {
21+
return showLoader
22+
? Center(child: CircularProgressIndicator())
23+
: Column(
24+
mainAxisSize: MainAxisSize.min,
25+
children: [
26+
Text('Add to local DB'),
27+
TextFormField(
28+
controller: controller,
29+
),
30+
ElevatedButton(
31+
onPressed: () async {
32+
setState(() {
33+
showLoader = true;
34+
});
35+
String text = controller.text;
36+
Map<String, dynamic> dataToAdd = {
37+
'title': text,
38+
'status': 0
39+
};
40+
bool added = await SqliteHelper().insert(dataToAdd);
41+
setState(() {
42+
showLoader = false;
43+
});
44+
45+
if (added) {
46+
widget.onChange();
47+
Navigator.of(context).pop();
48+
}
49+
},
50+
child: Text('Save'))
51+
],
52+
);
53+
}
54+
}

lib/each_item.dart

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_sqlite/item_details.dart';
3+
import 'package:flutter_sqlite/sqlite_helper.dart';
4+
5+
class EachItem extends StatefulWidget {
6+
const EachItem({Key? key, required this.item, required this.onChange})
7+
: super(key: key);
8+
9+
final Map item;
10+
final Function onChange;
11+
12+
@override
13+
State<EachItem> createState() => _EachItemState();
14+
}
15+
16+
class _EachItemState extends State<EachItem> {
17+
late bool status;
18+
bool deleting = false;
19+
SqliteHelper sqliteHelper = SqliteHelper();
20+
21+
initState() {
22+
super.initState();
23+
status = widget.item['status'] == 1 ? true : false;
24+
}
25+
26+
@override
27+
Widget build(BuildContext context) {
28+
return ListTile(
29+
onTap: () {
30+
Navigator.of(context).push(MaterialPageRoute(
31+
builder: (context) => ItemDetails(widget.item['id'])));
32+
},
33+
title: Text(widget.item['title']),
34+
leading: Checkbox(
35+
value: status,
36+
onChanged: (bool? value) {
37+
Map<String, dynamic> dataToUpdate = {'status': value! ? 1 : 0};
38+
sqliteHelper.update(widget.item['id'], dataToUpdate);
39+
40+
setState(() {
41+
status = value;
42+
});
43+
},
44+
),
45+
trailing: deleting
46+
? Container(height: 40, width: 40, child: CircularProgressIndicator())
47+
: IconButton(
48+
icon: Icon(Icons.delete),
49+
onPressed: () async {
50+
setState(() {
51+
deleting = true;
52+
});
53+
bool deleted = await sqliteHelper.delete(widget.item['id']);
54+
if (deleted) {
55+
widget.onChange();
56+
}
57+
setState(() {
58+
deleting = false;
59+
});
60+
},
61+
),
62+
);
63+
}
64+
}

lib/item_details.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_sqlite/sqlite_helper.dart';
3+
4+
class ItemDetails extends StatelessWidget {
5+
ItemDetails(this.id,{Key? key}) : super(key: key){
6+
_futureData=SqliteHelper().fetchOne(id);
7+
}
8+
late Future<Map> _futureData;
9+
int id;
10+
@override
11+
Widget build(BuildContext context) {
12+
return Scaffold(
13+
appBar: AppBar(title: Text('Details'),),
14+
body: Padding(
15+
padding: const EdgeInsets.all(8.0),
16+
child: FutureBuilder<Map>(
17+
future: _futureData,
18+
builder: (context,snapshot){
19+
if(snapshot.hasError)
20+
{
21+
return Center(child: Text(snapshot.error.toString()));
22+
}
23+
24+
if(snapshot.hasData)
25+
{
26+
Map item=snapshot.data!;
27+
return Column(
28+
crossAxisAlignment: CrossAxisAlignment.stretch,
29+
children: [
30+
Text(item['title']),
31+
Text(item['status']==1?'Purchased':'To purchase'),
32+
],
33+
);
34+
}
35+
36+
return Center(child: CircularProgressIndicator());
37+
38+
}),
39+
),
40+
);
41+
}
42+
}

lib/main.dart

Lines changed: 70 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import 'package:flutter/material.dart';
2+
import 'package:flutter_sqlite/add_item.dart';
3+
import 'package:flutter_sqlite/each_item.dart';
4+
import 'package:flutter_sqlite/sqlite_helper.dart';
25

36
void main() {
47
runApp(const MyApp());
@@ -49,67 +52,85 @@ class MyHomePage extends StatefulWidget {
4952

5053
class _MyHomePageState extends State<MyHomePage> {
5154
int _counter = 0;
55+
bool _deletingAll=false;
56+
bool _orderDesc=false;
57+
Future<List<Map>> _futureData=SqliteHelper().fetchAll();
5258

53-
void _incrementCounter() {
54-
setState(() {
55-
// This call to setState tells the Flutter framework that something has
56-
// changed in this State, which causes it to rerun the build method below
57-
// so that the display can reflect the updated values. If we changed
58-
// _counter without calling setState(), then the build method would not be
59-
// called again, and so nothing would appear to happen.
60-
_counter++;
61-
});
59+
void _addItem() {
60+
/*todo 10: Add an item*/
61+
showModalBottomSheet(
62+
context: context,
63+
builder: (BuildContext context) {
64+
TextEditingController controller = TextEditingController();
65+
return AddItemForm(onChange: refreshItems,);
66+
});
6267
}
6368

6469
@override
6570
Widget build(BuildContext context) {
66-
// This method is rerun every time setState is called, for instance as done
67-
// by the _incrementCounter method above.
68-
//
69-
// The Flutter framework has been optimized to make rerunning build methods
70-
// fast, so that you can just rebuild anything that needs updating rather
71-
// than having to individually change instances of widgets.
7271
return Scaffold(
7372
appBar: AppBar(
74-
// Here we take the value from the MyHomePage object that was created by
75-
// the App.build method, and use it to set our appbar title.
76-
title: Text(widget.title),
73+
title: Text('Shopping SQLite'),
74+
actions: [
75+
IconButton(onPressed: (){
76+
_orderDesc=!_orderDesc;
77+
orderByID(_orderDesc);
78+
}, icon: Icon(Icons.short_text)),
79+
_deletingAll?CircularProgressIndicator():IconButton(onPressed: () async {
80+
setState((){
81+
_deletingAll=true;
82+
});
83+
bool deleted=await SqliteHelper().deleteAll();
84+
if(deleted)
85+
{
86+
refreshItems();
87+
}
88+
setState((){
89+
_deletingAll=false;
90+
});
91+
}, icon: Icon(Icons.delete))
92+
],
7793
),
78-
body: Center(
79-
// Center is a layout widget. It takes a single child and positions it
80-
// in the middle of the parent.
81-
child: Column(
82-
// Column is also a layout widget. It takes a list of children and
83-
// arranges them vertically. By default, it sizes itself to fit its
84-
// children horizontally, and tries to be as tall as its parent.
85-
//
86-
// Invoke "debug painting" (press "p" in the console, choose the
87-
// "Toggle Debug Paint" action from the Flutter Inspector in Android
88-
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
89-
// to see the wireframe for each widget.
90-
//
91-
// Column has various properties to control how it sizes itself and
92-
// how it positions its children. Here we use mainAxisAlignment to
93-
// center the children vertically; the main axis here is the vertical
94-
// axis because Columns are vertical (the cross axis would be
95-
// horizontal).
96-
mainAxisAlignment: MainAxisAlignment.center,
97-
children: <Widget>[
98-
const Text(
99-
'You have pushed the button this many times:',
100-
),
101-
Text(
102-
'$_counter',
103-
style: Theme.of(context).textTheme.headline4,
104-
),
105-
],
106-
),
94+
body: FutureBuilder<List<Map>>(
95+
future: _futureData,
96+
builder: (context, snapshot) {
97+
if (snapshot.hasError) {
98+
return Center(
99+
child: Text('${snapshot.error}'),
100+
);
101+
}
102+
103+
if (snapshot.hasData) {
104+
List<Map> items = snapshot.data!;
105+
return ListView.builder(
106+
itemCount: items.length,
107+
itemBuilder: (context, index) {
108+
Map item = items[index];
109+
return EachItem(item: item,onChange:refreshItems);
110+
});
111+
}
112+
return CircularProgressIndicator();
113+
},
107114
),
108115
floatingActionButton: FloatingActionButton(
109-
onPressed: _incrementCounter,
110-
tooltip: 'Increment',
116+
onPressed: _addItem,
117+
tooltip: 'Add item',
111118
child: const Icon(Icons.add),
112119
), // This trailing comma makes auto-formatting nicer for build methods.
113120
);
114121
}
122+
123+
refreshItems(){
124+
setState((){
125+
_futureData=SqliteHelper().fetchAll();
126+
});
127+
}
128+
129+
orderByID(bool desc){
130+
setState((){
131+
_futureData=SqliteHelper().fetchAllOrderedByID(desc:desc);
132+
});
133+
}
115134
}
135+
136+

lib/sqlite_helper.dart

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*todo 2: Create a helper class to perform all operations related to local DB*/
2+
import 'package:sqflite/sqflite.dart';
3+
4+
final String tableName='items';
5+
final String colId='id';
6+
final String colTitle='title';
7+
final String colStatus='status';
8+
9+
class SqliteHelper{
10+
late Database _database;
11+
12+
open() async {
13+
/*todo 3: Open database connection*/
14+
_database=await openDatabase(
15+
'local_db.db',
16+
version: 1,
17+
onCreate: (Database db,int version) async {
18+
await db.execute('Create table $tableName ('
19+
'$colId integer primary key autoincrement,'
20+
'$colTitle text not null,'
21+
'$colStatus integer not null'
22+
')');
23+
}
24+
);
25+
}
26+
Future<bool> insert(Map<String,dynamic> dataToInsert) async {
27+
/*todo 4: Insert data
28+
* -4.1: Call open() to open a database connection,
29+
* -4.2: Call the function insert() on the instance of the database,
30+
* pass the name of the table, and a Map containing the data to insert
31+
*
32+
* */
33+
await open();
34+
int rowId=await _database.insert(tableName, dataToInsert);
35+
return rowId>0;
36+
}
37+
Future<List<Map>> fetchAll() async {
38+
/*todo 5: Fetch all items from the db*/
39+
await open();
40+
List<Map> items=await _database.query(tableName);
41+
// List<Map> items=await _database.query('tasks',orderBy: 'id desc');
42+
return items;
43+
}
44+
Future<Map> fetchOne(int id) async {
45+
/*todo 6: Fetch one item from the db*/
46+
await open();
47+
List<Map> items=await _database.query(tableName,where: '$colId=?',whereArgs: [id]);
48+
return items.first;
49+
}
50+
Future<bool> update(int id,Map<String,dynamic> dataToUpdate) async {
51+
/*todo 7: Update one item by the id*/
52+
await open();
53+
int numOfChanges=await _database.update(tableName, dataToUpdate,where: 'id=?',whereArgs: [id]);
54+
return numOfChanges>0;
55+
}
56+
delete(int id) async {
57+
/*todo 8: Delete one item by the id*/
58+
await open();
59+
int numOfRows=await _database.delete(tableName,where: 'id=?',whereArgs: [id]);
60+
return numOfRows==1;
61+
}
62+
63+
deleteAll() async {
64+
/*todo 9: Delete all items*/
65+
await open();
66+
int numOfRows=await _database.delete(tableName);
67+
return numOfRows>0;
68+
}
69+
70+
Future<List<Map>> fetchAllOrderedByID({bool desc=false}) async {
71+
/*todo 5: Fetch all items from the db*/
72+
await open();
73+
// List<Map> items=await _database.query(tableName);
74+
List<Map> items=await _database.query(tableName,orderBy: 'id ${desc?'desc':'asc'}');
75+
return items;
76+
}
77+
}

0 commit comments

Comments
 (0)