Reorderable List View Flutter with Sqlflite Combination - flutter

I am trying to figure out to collect user's education information.
I successfully add delete update my database.
Additionally I made a list to show user what they entered so far. Like Elementary school, highchool, bachelors etc. I wanted to let user drag and drop the entries in the order they like.
I came up with the following code I am showing the user basic information such as school name, degree and field of study. Which I retrieve from database.
I am stuck at this point. I am using id to list the data id's are going like 1 2 3 4 5 once the user enters data in but when user tries to enter data in at it is correctly working. But onReoerder function and update order function is not correctly working.
ReorderableListView(
children: List.generate(_educationList.length, (index) {
final education = _educationList[index];
education.key = ValueKey(education.id!);
print(education.id);
return Card(
key: ValueKey(education.id),
elevation: 4,
margin: EdgeInsets.all(8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'School name:',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(education.schoolName ?? ''),
SizedBox(height: 8),
Text(
'Degree:',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(education.degree ?? ''),
SizedBox(height: 8),
Text(
'Field of study:',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(education.fieldOfStudy ?? ''),
],
),
),
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size(75, 40),
elevation: 2.0,
backgroundColor: Color(0xff2ec4b6),
),
onPressed: () {
// Edit the education item
editEducation(education);
_isEditing = true;
},
child: Text('Edit'),
),
SizedBox(height: 8),
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size(75, 40),
elevation: 2.0,
backgroundColor: Color(0xffff9f1c),
),
onPressed: () {
// Remove the education item from the list
setState(() {
_educationList.removeAt(index);
print(education.id!);
Education.instance.delete(education.id!);
});
},
child: Text('Delete'),
),
],
),
],
),
),
);
}),
onReorder: (oldIndex, newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final item = _educationList.removeAt(oldIndex);
_educationList.insert(newIndex, item);
// Update the order of the items in the database
Education.instance.updateOrder(_educationList[oldIndex].id!, _educationList[newIndex].id!);
// Update the key of the item
item.key = ValueKey(_educationList[newIndex].id!);
});
},
)
//And this is my basics of database:
Future updateOrder(int originalIndex, int newIndex) async {
final db = await instance.database;
//update the id of the moved row
await db.update(_tableName, {_columnId: newIndex}, where: '$_columnId = ?', whereArgs: [originalIndex]);
//loop through all the rows in the table
final List<Map<String, dynamic>> rows = await db.query(_tableName);
for(int i = 0; i < rows.length; i++) {
if(i != newIndex) {
final int currentId = rows[i][_columnId];
//update the id of the row if it's affected by the move
if(currentId == originalIndex || currentId == newIndex) {
await db.update(_tableName, {_columnId: i}, where: '$_columnId = ?', whereArgs: [currentId]);
}
}
}
}
Education({
this.id,
this.schoolName,
this.degree,
this.fieldOfStudy,
});

Related

How to keep track of creation index when creating a list of functions?

My code looks something like this:
list.add(TextButton(
onPressed: () {
int temp = index;
print(temp);
},
child: Text(myText),
));
However, when I put this list in a widget and try to click on any of the TextButtons, I get the last index of the loop.
How could I make it so that when I click on one of the TextButton, I get its right index?
Here is the full function,
List<TableRow> generateTables() {
List<TableRow> list = [];
int index = 0;
for (Map element in exerciseList) {
String exName = element["name"];
Icon? icon = correspIcon(element);
list.add(
TableRow(
children: [
TextButton(
onPressed: () {
int temp = index;
print(temp);
},
style: TextButton.styleFrom(
minimumSize: Size.zero,
padding: const EdgeInsets.all(4.0),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
alignment: Alignment.centerLeft,
),
child: Text(
exName,
style: TextStyle(
fontFamily: "ArialRounded",
fontSize: 20,
color: Colors.white.withAlpha(240)),
),
),
icon ?? const SizedBox()
],
),
);
index++;
}
return list;
}
What I want to happen is that if I click on the n-th TextButton in the list, the index returned is n-1.
Thanks a lot if you can help me
use this to get to the index value of the selected element
the above code does not work because the index value always holds the last element
List<TableRow> generateTables() {
List<TableRow> list = [];
for (var i=0; i<exerciseList.length; i++) {
final Map element= exerciseList[i];
String exName = element["name"];
Icon? icon = correspIcon(element);
list.add(
TableRow(
children: [
TextButton(
onPressed: () {
int temp = i;
print(temp);
},
style: TextButton.styleFrom(
minimumSize: Size.zero,
padding: const EdgeInsets.all(4.0),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
alignment: Alignment.centerLeft,
),
child: Text(
exName,
style: TextStyle(
fontFamily: "ArialRounded",
fontSize: 20,
color: Colors.white.withAlpha(240)),
),
),
icon ?? const SizedBox()
],
),
);
}
return list;
}

How do I add pictures to my menu items (which are Card widgets) using a StreamBuilder in flutter?

I first got the stream builder to work with a listTile but now I want to use a card instead.
I am using a firebase real-time database and have stored the pictures in the menuItem tree as follows.
menuItem
J1
description:"Tasty milk shake!"
img:"assets/images/milkshake.jpg"
itemName:"Milk Shake"
price:20
The page I am working with is rewards which stores the menut item ID as part of the record so I can retrieve the full details of the reward item.
here is the code for the stream builder I have used for the reward Card items:
body: Center(
child: StreamBuilder(
stream: _dbRef.child('Rewards').onValue,
builder: (context, snapshot) {
final tilesList =
<InkWell>[];
if (snapshot.hasData) {
final myRewards = Map<String, dynamic>.from(
(snapshot.data! as DatabaseEvent).snapshot.value
as Map<dynamic, dynamic>);
myRewards.forEach((key, value) {
getImg();
print("++++++++++++++++++++++++++++++++++ => $publicImg");
final nextReward = Map<String, dynamic>.from(
value);
publicID = nextReward['menuItemID'];
final rewardTile = InkWell(
onTap: () async {
final pointCost = nextReward[
'pointCost'];
final id = nextReward[
'menuItemID'];
final snapshot = await _dbRef.child('menuItem/$id').get();
if (snapshot.exists) {
final details = Map<String, dynamic>.from(
snapshot.value! as Map<Object?, Object?>);
pass to next page
String itemName = details['itemName'];
String desc = details['description'];
int cost = pointCost;
String itemImg = details['img'];
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => RewardItemPage(
itemName: itemName,
desc: desc,
rewardPoints: cost.toString(),
itemImg: itemImg,
)));
}
},
child: Card(
margin: const EdgeInsets.all(10),
color: Colors.white,
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ClipRect(
child: Align(
alignment: Alignment.center,
widthFactor: 0.8,
child: Image(
image: AssetImage(publicImg),
height: 100,
fit: BoxFit.cover,
),
)),
const SizedBox(width: 30),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
Text(
nextReward['rewardName'],
style: const TextStyle(
color: Colors.black, fontSize: 25),
),
Text(
'Points: ' +
nextReward['pointCost'].toString(),
style: const TextStyle(
color: Colors.black, fontSize: 18),
),
],
),
const SizedBox(width: 30),
Container(
alignment: Alignment.centerRight,
child: GFIconButton(
onPressed: () {},
icon: const Icon(
Icons.favorite_outline,
color: Colors.black,
),
type: GFButtonType.transparent,
),
)
]),
));
This is the code for my method I am using to get the images for the Card widget:
Future<String> getImg() async {
final snap = await _dbRef.child('menuItem/$publicID/img').get();
if (snap.exists) {
publicImg = snap.value.toString();
print('NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN => $publicImg');
}
return publicImg;
}
My code returns no pictures initially and upon a hot reload it gets pictures for only one item and sets it as the image for all card items.
Sorry for the long winded coded but I am struggling with this.

Retrieve data from firebase and filter duplicate fields

i want to create a page in my app where i view history of previously entered data from firestore according to date.I have a page where i try to fetch data specific to date entered, but it seems to keep returning duplicate data as shown in the image below
I only want to be able to show a date particular date once in this page but i cant seem to do that. here is the code
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('users')
.doc(user?.uid)
.snapshots(),
builder: (context, AsyncSnapshot snapshot) {
print(snapshot.data.docs);
if (snapshot.hasError) {
Get.snackbar(
'About Task',
'User message',
backgroundColor: Colors.white,
snackPosition: SnackPosition.BOTTOM,
titleText: const Text(
'Failed Adding Task',
style: TextStyle(color: Colors.red),
),
messageText: const Text(
'Something went wrong... Try again',
style: TextStyle(color: Colors.red),
),
);
}
if (snapshot.data == null) {
const Center(
child: Text('Add a task/Transaction'),
);
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasData) {
final List storeDocs = [];
snapshot.data!.docs.map((DocumentSnapshot document) {
Map a = document.data() as Map<String, dynamic>;
storeDocs.add(a);
a['id'] = document.id;
}).toList();
Calculations.getTotalBalance(storeDocs.asMap());
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
TaskModel task =
TaskModel.fromJson(snapshot.data.docs[index]);
print(Expenses.multipleDates);
return Container(
decoration: BoxDecoration(),
child: Column(
children: [
SizedBox(
height: 25,
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 53,
height: 80,
child: Text(
task.date,
style: TextStyle(fontSize: 10),
),
),
],
),
Text(
task.amount,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Colors.green),
),
Column(
children: [
Row(
children: [
Text(
task.amount,
style: const TextStyle(
fontSize: 15,
fontWeight:
FontWeight.w600,
color: Colors.red),
),
],
),
SizedBox(
height: 22,
),
Row(
children: [
GestureDetector(
onTap: () {
_showBottomSheet(
context, task);
},
child: GestureDetector(
onTap: () {
Navigator.pushNamed(
context,
SpreadSheetPage.id,
arguments: Tasks(
firestoreDocID:
task.date,
));
},
child: Text(
'View',
style: TextStyle(
color: Colors.blue),
),
),
),
],
)
],
),
],
),
const Padding(
padding:
EdgeInsets.only(left: 65, top: 8),
child: Divider(
thickness: 0.8,
),
)
],
),
);
});
} else {
return Container();
}
}),
here is what my database looks like
Based on your feedback, here's what I'd do.
Note how you can't filter the query for duplicate values directly in your query.
There is no way to tell Firestore "get all of the results from this collection, but filter out elements that have a field repeated (or duplicated) in the collection itself". This makes sense since the Firestore SDK performs simple and fast queries through the where clause. Read the docs for more info.
This means that the filtering is at your expenses, i.e. you have to implement it yourself and you will be charged for all of the reads you do. Note: any workaround to reduce the number of reads (e.g. use the not-in clause) is hacky, hard to read and maintain and it just doesn't work for a large dataset (not-in would limit your duplicates to 10, in your case), so embrace it.
Considering these premises one could implement the following brute-force solution that reads all the documents from your firestore collection and filters out the duplicates by exploiting a simple Set:
Future<List> getAndFilter() async {
final f = FirebaseFirestore.instance;
final data = await f.collection('myCollection').get();
final unfilteredData = [for (final d in data.docs) d.data()];
final seenValues = Set();
final desiredData = unfilteredData.where((currentDoc) {
if (seenValues.contains(currentDoc)) return false;
seenValues.add(currentDoc);
return true;
});
return desiredData.toList();
}
You just need to add a filter on the collection:
.collection('tasks')
.where('date', isEqualTo: selectedDate)
.snapshots()

How to implement objectbox into flutter appilcation

I am working on an app, and need help trying to change my save method to object box. I have been using path_provider to save data but want to switch it to the object box database but I am struggling to do it. The goal is to have each timer button be clicked to record a time and present them back to the user to see how long each task/button took. Below is my code for my ViewModel and my timer_page dart files.
#Entity()
class StudyViewModel {
static List<Study> studies = [];
static List<ValueChanged<ElapsedTime>> timerListeners =
<ValueChanged<ElapsedTime>>[];
static Stopwatch stopwatch = new Stopwatch();
/// load from file...
static Future load() async {
try {
File file = await getFile();
String studiesJson = await file.readAsString();
if (studiesJson.isNotEmpty) {
List studiesParsed = json.decode(studiesJson);
studies = studiesParsed.map((i) => Study.fromJson(i)).toList();
}
} catch (e) {
print(e);
}
}
static Future<File> getFile() async {
final directory = await getApplicationDocumentsDirectory();
final path = directory.path;
return File('$path/studies.json');
}
static Future saveFile() async {
File file = await getFile();
file.writeAsString(json.encode(studies));
}
SizedBox(
height: 600,
child: GridView.count(
primary: false,
padding: const EdgeInsets.all(20),
crossAxisSpacing: 10,
mainAxisSpacing: 10,
crossAxisCount: 3,
children: widget.tasks.map((element) {
final isActive = activeTask != null && activeTask == element;
return GestureDetector(
onTap: () {
// set active task or toggle is active
if (isActive) {
setState(() {
activeTask = null;
StudyViewModel.stopwatch.start();
disable = true;
});
} else {
setState(() {
activeTask = element;
StudyViewModel.stopwatch.stop();
disable = false;
});
}
},
child: Container(
color: isActive ? Colors.amber : Colors.green,
padding: EdgeInsets.all(8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
element.name,
style: TextStyle(color: Colors.white, fontSize: 25),
textAlign: TextAlign.center,
),
Text(
element.elapsedTime
)
]
),
),
);
}).toList(),
),
),
Expanded(
child: timerText,
),
Expanded(
flex: 0,
child: Padding(
padding: const EdgeInsets.only(bottom: 24.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
RaisedButton(
onPressed: disable
? null
: () {
setState(() {
StudyViewModel.stopwatch.reset();
});
},
color: Colors.red,
padding: EdgeInsets.symmetric(
horizontal: 60.0,
vertical: 20.0,
),
child: Text(
"Reset",
style: TextStyle(fontSize: 20.0, color: Colors.white),
),
),
RaisedButton(
onPressed: disable
? null
: () async {
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('Do you wish to save a time study?'),
actions: <Widget>[
FlatButton(
child: Text('Accept'),
onPressed: () async {
StudyViewModel.saveFile();
Navigator.of(context).pop();
},
),
FlatButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
]
);
}
);

I am having trouble iterating through an array of objects

I am trying to go through an array of objects, I stored them in my SharedPreferences where I go the data from firebase and add the quantity for each object, now I only want to display the title, price, and quantity of the product in the cart. I was able to pull all the values belonging to the product to the cart screen but how to loop through the nested values in the cart screen is the problem. please can anyone help me still learning more on flutter?
Cart screen
#override
Widget build(BuildContext context) {
SharedPreferences prefs = SharedPreferences.getInstance() as SharedPreferences;
var cart = prefs.getStringList('userCart');
return Row(
children: [
SizedBox(
width: getProportionateScreenWidth(88),
child: AspectRatio(
aspectRatio: 0.88,
child: Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Color(0XFFF5F6F9),
borderRadius: BorderRadius.circular(15),
),
child: Image.network(cart![0]),
// child: Image.network(cart.product.images[0]),
),
),
),
SizedBox(
width: getProportionateScreenWidth(20),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
cart.first,
// cart.product.title,
style: TextStyle(fontSize: 16, color: Colors.black),
maxLines: 2,
),
const SizedBox(
height: 10,
),
Text.rich(
TextSpan(
text: "\$${cart.product.price}",
style: TextStyle(
color: kPrimaryColor,
),
children: [
TextSpan(
text: " x${cart.numOfItem}",
style: TextStyle(
color: kTextColor,
),
),
],
),
),
],
)
],
);
}
Storing the data from firebase and adding quantity
Future<void> checkItemInCart(
Product product, int quantity, BuildContext context) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
// convert to map
var product_str = product.toMap();
// combine product with quantity
String productWithQuantity =
product_str.toString() + '-quantity-' + quantity.toString();
// check if project exists first
List<String> userCartPref = (prefs.getStringList('userCart') ?? <String>[]);
['Product-quantity-2'];
/*
update {
check if found
}
*/
List<String> temp = (prefs.getStringList('userCart') ?? <String>[]);
// add f
//userCartPref ['iphone 1','laptop 3'];
// temp ['laptop 3'];
var foundInCart = false;
for (var i = 0; i < userCartPref.length; i++) {
var item = userCartPref[i];
var items = item.split('-quantity-'); //list [product,quantity]
var old_product = items[0];
var old_qty = items[1];
if (old_product.contains(product.pid)) {
foundInCart = true;
// product exists
// delete the current item
temp.removeAt(i);
// set pref to temp
prefs.setStringList('userCart', temp);
// sum the quantity 2 1
String finalQuantity = (quantity + int.parse(old_qty)).toString();
// create string for pref with the updated quantity
String updatedProductWithQuantity =
product_str.toString() + '-quantity-' + finalQuantity;
//add item with the updated quantity iphone 2
addItemToCart(updatedProductWithQuantity, context);
showSnackBar(context, "Quantity has been updated successfully");
break;
}
}
if (userCartPref.length == 0 || foundInCart == false) {
addItemToCart(productWithQuantity, context);
showSnackBar(context, "Product added successfully to cart");
}
await getProducts();
}
Future<void> addItemToCart(String product, BuildContext context) async {
// await clearPref();
print("inside");
SharedPreferences prefs = await SharedPreferences.getInstance();
List<String> tempCartList = (prefs.getStringList('userCart') ?? <String>[]);
// print(tempCartList);
tempCartList.add(product);
prefs.setStringList('userCart', tempCartList);
}
Future<void> getProducts() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
List<String> tempCartList =
(preferences.getStringList('userCart') ?? <String>[]);
for (var i = 0; i < tempCartList.length; i++) {
var item = tempCartList[i];
var items = item.split('-quantity-');
var product_ = items[0];
var quantity_ = items[1];
}
}
you can use ListView.builder or GridView.builder to iterate over the array and render them on screen. So if I use ListView.builder, my code in cart screen would look like:
return ListView.builder(
itemCount: cart.length, //length of cart
itemBuilder: (context, index) {
return Row(
children: [
SizedBox(
width: getProportionateScreenWidth(88),
child: AspectRatio(
aspectRatio: 0.88,
child: Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Color(0XFFF5F6F9),
borderRadius: BorderRadius.circular(15),
),
child: Image.network(cart![index]),
// child: Image.network(cart.product.images[0]),
),
),
),
SizedBox(
width: getProportionateScreenWidth(20),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
cart[index].product.title,
// cart.product.title,
style: TextStyle(fontSize: 16, color: Colors.black),
maxLines: 2,
),
const SizedBox(
height: 10,
),
Text.rich(
TextSpan(
text: "\$${cart[index].product.price}",
style: TextStyle(
color: kPrimaryColor,
),
children: [
TextSpan(
text: " x${cart[index].numOfItem}",
style: TextStyle(
color: kTextColor,
),
),
],
),
),
],
)
],
);
},
);