Flutter - list duplicates old and new values - firebase

I have a method for receiving push from Firebase Cloud Messaging, and within it, the notification is allocated within a list. What happens is that this method is making the old value a duplicate of the new value.
Firebase method:
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
if (message.notification != null) {
widget._localNotificationList.title = message.notification.title;
widget._localNotificationList.body = message.notification.body;
widget._pushDecode.notifList.add(widget._localNotificationList);
savePush(); //this method maintains notification on the user's screen, with sharedPreferences
setState(() {});
}
});
Page View:
ListView.builder(
itemCount: widget._pushDecode.notifList.length,
itemBuilder: (context, i) {
return Card(
margin: EdgeInsets.all(10),
elevation: 4,
child: ListTile(
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => removePush(i),
),
title: Text(
widget._pushDecode.notifList.reversed.elementAt(i).title,
),
subtitle: Text(
widget._pushDecode.notifList.reversed.elementAt(i).body,
),
),
);
},
),

You need to use the key value because it does not know if the widget that was in the position you are adding it for example is different then the previous, but using keys he can always know that two widgets are different when they are the same type like your card.
ListView.builder(
itemCount: widget._pushDecode.notifList.length,
itemBuilder: (context, i) {
return Card(
key: UniqueKey(), // you can also use ValueKey('this must be unique for each element in the list.')
margin: EdgeInsets.all(10),
elevation: 4,
child: ListTile(
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => removePush(i),
),
title: Text(
widget._pushDecode.notifList.reversed.elementAt(i).title,
),
subtitle: Text(
widget._pushDecode.notifList.reversed.elementAt(i).body,
),
),
);
},
),

Related

How to toLowerCase() on data from Flutter Firestore?

I made an application and coded a search system between documents in the application. But there is a problem:
The incoming record is registered as "Durmuş Bolat" in Firestore. And as you can see, the letter D of Durmuş is capitalized. That's why I have to capitalize the first letter in the search. And that's a problem.
I want to get the result of "Durmuş Bolat" even if I write it in the following ways:
durmuş bolat
DURMUŞ BOLAT
DuRmUs BoLaT
As a solution to this, I thought of shrinking the searched content and all the incoming content. So all incoming data and searched content will be downsized with toLowerCase.
I don't have the slightest idea where to place this toLowerCase code. Can you help me?
Codes:
var _searchText;
return Scaffold(
appBar: AppBar(
title: Container(
height: 50,
width: 250,
child: TextFormField(
autofocus: true,
style: TextStyle(color: Colors.white),
onChanged: (value) {
setState(() {
_searchText = value;
});
},
),
),
),
body: Column(
children: [
Container(
height: MediaQuery.of(context).size.height * 0.8, //MediaQuery.of(context).size.height * 0.8,
child: StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: FirebaseFirestore.instance.collection('bolatTamir').where('nameSurname', isGreaterThanOrEqualTo: _searchText).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
return InkWell(
child: ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(snapshot.data!.docs[index].data()['urunFotografi']),
),
title: Text(snapshot.data!.docs[index].data()['nameSurname'], style: TextStyle(fontSize: 20),),
subtitle: Text(snapshot.data!.docs[index].data()['yapildiMi'] == false ? 'Tamamlanmadı' : 'Tamamlandı', style: TextStyle(fontSize: 17),),
trailing: Text(snapshot.data!.docs[index].data()['tamirUcreti'], style: TextStyle(fontSize: 20),),
),
);
},
);
},
),
),
],
),
);
Thank you in advance for your help.
To my knowledge, lower-casing text that is already stored is not possible with Firebase. One can only lower-case the search term.
If it is just names and not tons of text, one could
.split(' ') these names, then
.toLowerCase() all resulting words, then
store them in a search-index-collection.
Then, search this search-index-collection and resolve the $userId.
.
users/34td24y3gtdb724/
{
name: 'Durmuş Bolat'
}
searchindex/
{
word: 'durmuş',
userId: 34td24y3gtdb724
word: 'bolat',
userId: 34td24y3gtdb724
}
Google itself asks to use Third-Party-Providers for full text search:
https://cloud.google.com/firestore/docs/solutions/search
Also, there is an approach to generate permutations as an Array:
https://medium.com/flobiz-blog/full-text-search-with-firestore-on-android-622af6ca5410

How to sort a list with Firestore Flutter?

When creating notes, they are displayed in a different order for me. Notes taken from Firestore. Can I sort the list and display data by creation date, from oldest to newest? I am getting data from firestore
list_note_page.dart
Widget build(BuildContext context) {
return ListView.builder(
itemCount: snapshot.data?.docs.length,
itemBuilder: (context, index) {
QueryDocumentSnapshot<Object?> documentSnapshot =
snapshot.data!.docs[index];
return Dismissible(
key: Key(documentSnapshot.id),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
Database().deleteNote(documentSnapshot.id);
},
background: Container(
padding: const EdgeInsets.only(right: 20),
alignment: Alignment.centerRight,
color: Colors.red,
child: const Text(
'Delete',
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
child: ListTile(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: ((context) => AddAndEditNotePage(
header: documentSnapshot['name'],
title: documentSnapshot['title'],
date: documentSnapshot['date'],
id: documentSnapshot.id,
)))),
title: Text(documentSnapshot['name']),
subtitle: Text(documentSnapshot['title']),
trailing: Text(documentSnapshot['date'] ?? ''),
));
});
}
I recommend to add an additional field to your documents, perhaps called created_date that will take a timestamp of the time when you push it to Firebase, (unless that date field you have there is the creation date of that document) then you can do:
Firestore.instance
.collection('YOUR_COLLECTION')
.orderBy('created_date').get()
By default, the descending order is false (oldest to newest).
Check this link or this for further reference.

UI not updated Flutter Web

I have a Table reading data from Firestore, using StreamBuilder. I have a button Add Element below the table that when clicked opens a pop-up form. After the user fills the form and clicks the button, data is stored in Firestore, the Dialog form is closed and the user is redirected to the table. When I use only text form fields in my form, the table is updated with the new data that the user just pushed in Firestore. The problem started to occurs when I added an Upload Picture Form, which uploads the picture in Firebase Storage and pushes the download URL as a field inside the other information of the form. I fill the form and the Table doesn't update. The project is in Flutter Web so I am using image_picker_web for the upload process and the Image file is MediaInfo type. As I said it started to happen only when I added the upload picture form along with other TextFormFields.
// Upload Picture Field
InkWell(
onTap: () async {
final MediaInfo _image =
await ImagePickerWeb.getImageInfo;
setState(() {
image = _image;
});
},
child: Container(
child: Center(
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Icon(
CupertinoIcons.cloud_upload,
color: Colors.grey[600],
),
const SizedBox(
width: 5,
),
Text(
'Upload the invoice picture',
style: TextStyle(
color: Colors.grey[600],
),
),
],
),
),
),
),
//Create Invoice Button
MaterialButton(
onPressed: () async {
Invoice invoice = Invoice(
_controllerInvoiceNumber.text,
_controllerLocation.text,
invoiceDate,
_controllerAmount.text,
);
Database().addNewInvoice(
invoice,
_userId!,
image!,
);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const HomePage(),
),
);
}
},
child: const Text(
'Create',
),
),
//Table
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('Invoices')
.snapshots(),
builder: (context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Column(
children: [
SizedBox(
child: PlutoGrid(
columns: editableColumns,
rows: _createRows(snapshot.data),
),
),
Padding(
padding: const EdgeInsets.all(10),
child: Center(
child: MaterialButton(
child: const Text(
'Add Invoice',
style: TextStyle(color: Colors.white),
),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return const AddInvoiceForm();
},
);
},
),
),
),
],
);
},
),

StreamBuilder not updating after an item is removed Flutter

I am new to Flutter and this is my first time asking a question on Stackoverflow. I apologize for any misunderstanding. I will try my best to make it clear.
I am using sqflite for storing user's favorites and populating a list from the DB on a page, named Favorites screen. This Favorites page is one of the items on my bottom navbar.
My issue is that when I tap on an item from the favorites list which takes me to a screen where I can unfavorite that item. I double-checked that it is really removed from the DB by logging the rows count. But when I go back to the Favorites page, that item is still on the list. If I go to one of the pages from the bottom navbar and go back to the Favorites screen, the item isn't there. I understand that the page is being rebuilt again this time but my intention was the Stream will constantly listen for a change.
I have also implemented a slide to dismiss feature on the fav screen, which works as intended. But I am using the same logic on both.
StreamBuilder code in Favorite screen
StreamBuilder<List<WeekMezmurList>>(
stream: favBloc.favStream,
builder: (context, AsyncSnapshot<List<WeekMezmurList>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Text(
"Loading Favorites...",
style: TextStyle(fontSize: 20),
),
);
} else if (snapshot.data == null) {
return Center(
child: Text(
"No Favorites yet!",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
);
} else {
return ListView.builder(
physics: BouncingScrollPhysics(),
padding: const EdgeInsets.fromLTRB(5.0, 10.0, 5.0, 10.0),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return new GestureDetector(
onTap: () =>
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
AudioPlayerScreen(
mezmurName: snapshot.data[index].mezmurName,
),
),
),
child: Slidable(
key: new Key(snapshot.data[index].mezmurName),
actionPane: SlidableDrawerActionPane(),
actionExtentRatio: 0.25,
// closes other active slidable if there is any
controller: slidableController,
secondaryActions: <Widget>[
IconSlideAction(
caption: 'Share',
color: Colors.indigo,
icon: Icons.share,
onTap: () =>
_share(snapshot
.data[index]),
),
IconSlideAction(
caption: 'Delete',
color: Colors.red,
icon: Icons.delete,
onTap: () =>
_swipeDelete(
context, snapshot.data[index].mezmurName),
),
],
child: Card(
color: Colors.white,
child: Padding(
padding: EdgeInsets.symmetric(
vertical: 15.0,
horizontal: 10.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Row(
children: <Widget>[
_misbakChapter(
snapshot.data[index].misbakChapters),
SizedBox(width: 15),
_displayFavoritesMisbakLines(
snapshot.data[index], index),
],
)
],
),
),
),
),
);
},
);
}
},
);
slide to delete code in Favorites screen
// deletes the specific favorite from the sqflite db
Future<void> _swipeDelete(BuildContext context, String mezmurName) async {
try {
favBloc.delete(mezmurName);
} catch (e) {
CupertinoAlertDialog(
content: Text("Something went wrong. Please try again."),
actions: <Widget>[
CupertinoDialogAction(
child: Text(
"Ok",
),
onPressed: () => Navigator.of(context).pop(),
),
],
);
}
}
I have the same logic in the second screen, the screen I get when I tap on one of the items from the Fav list.
favBloc.delete(widget.mezmurName);
BLoC code, I got the concepts from this Medium article
class FavoritesBloc{
FavoritesBloc(){
getFavorites();
}
final databaseHelper = DatabaseHelper.instance;
// broadcast makes it to start listening to events
final _controller = StreamController<List<WeekMezmurList>>.broadcast();
get favStream => _controller.stream;
void dispose() {
_controller.close();
}
getFavorites () async{
_controller.sink.add(await databaseHelper.getFavorites());
}
insert(WeekMezmurList fav){
databaseHelper.insertToDb(fav);
getFavorites();
}
delete(String mezmurName){
databaseHelper.delete(mezmurName: mezmurName);
getFavorites();
}
}
Delete method in the DB class
// deleting a value from the db
delete({String mezmurName}) async {
var dbClient = await getDb;
try {
await dbClient
.delete(TABLE, where: '$MEZMUR_NAME = ?', whereArgs: [mezmurName]);
} catch (e) {
}
}
I have tried to research this issue but all I have found were for remote databases.
Just to make it more clear, I took a screen record.
Thank you in advance!
The reason why StreamBuilder on the first screen doesn't update with the changes made is because it uses a different instance of FavoritesBloc(). If you'd like for the bloc to be globally accessible with a single instance, you can declare it as
final favBloc = FavoritesBloc();
Otherwise, you can follow what has been suggested in the comments and pass FavoritesBloc as an argument between screens.

Cant get StreamBuilder to display data from cloud firestore

I know I have a connection to the database and no errors are appearing so I'm pretty confused. The title and code should summarize the problem fairly well. Think I'm missing something?
here is the main code that should be displaying cards with titles from firebase
mainList() {
StreamBuilder(
stream: Firestore.instance.collection('Events').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text('Loading');
} else {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot userPost = snapshot.data.documents[index];
return Container(
width: MediaQuery.of(context).size.width,
height: 350.0,
child: Padding(
padding: EdgeInsets.only(top: 8.0, bottom: 8.0),
child: Material(
elevation: 14.0,
shadowColor: Color(0x802196F3),
child: Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width,
height: 200.0,
child: Text(
'${userPost['title']}',
))
],
),
),
))),
);
},
);
}
});
}
and here is where the function is called:
lass MyAppmain extends State<MyApp> {
#override
Widget build(BuildContext context) {
var listView = ListView.builder(
itemCount: local.length,
itemBuilder: (BuildContext cnxt, int index) {
return new Text(local[index]);
});
return MaterialApp(
home: PageView(
controller: controller,
children: <Widget>[
//home page---------------------------
Scaffold(
appBar: AppBar(
title: Text(
'Events',
),
elevation: 20,
),
//main list view for the cards
//think I use streambuilder for this so google before starting
body: mainList(),//RIGHT HERE
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.push(context, NewEventTransition());
},
mini: true,
),
),
//Profile Page-------------------------------
Scaffold(
appBar: AppBar(
title: Text(
'Profile',
),
elevation: 20,
),
),
],
));
}
}
Want a listview of cards holding the titles from firebase (will soon be more than titles but want to get this working first)
This is a common problem.
return ListView.builder(
itemCount: snapshot.data.documents.length, // this line is the culprit!
itemBuilder: (context, index) {
print(snapshot.data.documents.length); // it will print null
.......
}
See, It takes some time to fetch data from firebase. When ListView.builder is called the value of snapshot.data.documents.length is actually null. Tho after few seconds it gets data but till then ListView had built the UI and that's why it's blank. To check the value, you can add a Print statement like shown above.
Now there are few ways to solve this problem:
Make an int variable say totalLength, make a function say setTotalLength which makes a call to Firebase/Firestore database and use setState to assign this value to totalLength and then change that code to this:
itemCount: totalLength,
You should Call setTotalLength in your initState method.
Or, you can change your code to this, But I'm NOT 100% sure that this will work:
itemCount: snapshot.data.documents.length ?? 0 // returns 0 if the value is null, may reload UI again when data comes

Resources