I am currently working on the messaging section of my app. I am using a streambuilder and streams in conjunction with a GroupedListView so that the messages can be grouped according to date.
However, I am having some problems because the GroupedListView takes a list as its 'elements' parameter. And we know streams aren't necessarily lists. I have looked into converting streams to lists but I can't seem to find a solution.
Here's what the code looks like:
Expanded( //so that we can move the text field to the bottom
child: StreamBuilder(
stream: db.chatStream(widget.receiverUser.uid),
builder: (context, snapshot) {
return GroupedListView<Chat, DateTime>(
reverse: true, //so that the texts start from bottom to top
order: GroupedListOrder.DESC, //get the proper order of sent messages
padding: const EdgeInsets.all(8),
elements: db.chatStream(widget.receiverUser.uid), //THIS IS WHERE THE PROBLEM IS!!!
groupBy: (chat) => DateTime(
chat.dateTime.year,
chat.dateTime.month,
chat.dateTime.day
),
groupHeaderBuilder: (Chat chat) => SizedBox(
height: 40,
child: Center(
child: Card(
color: Colors.black45,
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(
DateFormat.yMMMd().format(chat.dateTime),
style: const TextStyle(color: Colors.white, fontSize: 12),
),
),
),
),
),
itemBuilder: (context, Chat chat) {
bool isMe = chat.senderId == uid;
return Align(
alignment: isMe ? Alignment.centerRight
: Alignment.centerLeft,
child: Column(
children: [
Align(
alignment: isMe ? Alignment.centerRight
: Alignment.centerLeft,
child: Card(
color: isMe
? Colors.purpleAccent
: Colors.white,
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(12),
child: Text(
chat.message,
style: TextStyle(
color: isMe
? Colors.white
: Colors.black
),
),
),
),
),
Align(
alignment: isMe
? Alignment.topRight
: Alignment.topLeft,
child: Padding(
padding: isMe ? const EdgeInsets.only(
right: 12)
: const EdgeInsets.only(left: 12),
child: MidText(text: DateFormat('kk:mm').format(
chat.dateTime)),
)
)
],
),
);
}
);
}
),
),
Is there a way to convert streams to lists so I can pass it to the "elements" parameter? Or do I need to take a different approach?
I also came across this SO post but it's without an answer. But this is essentially my same problem as well:
Example
I would thoroughly appreciate any help!
I'm not sure if what you asked for is something that exists. But what I would do is create a Stream<List<_YourType_>> and with the snapshot given, I would use the data as my list.
PS: If you initialize your StreamBuilder like StreamBuilder<List<_YourType_>>(... then your snapshot will be an AsyncSnapshot<List<_YourType_>> and its data value will already be a List<_YourType_> with no need to cast or anything!
PS2: If I were you, I would look for the time package as it has a .date getter for DateTime or even create your own like:
extension DateTimeExtension on DateTime {
DateTime get date => isUtc ? DateTime.utc(year, month, day) : DateTime(year, month, day);
}
Just so it's easier to get your dates without the time included.
You should probably declare your StreamBuilder as StreamBuilder<List>. You will run into an error saying object cannot access resource otherwise.
Related
This is the result that I want.
This is the current output that I have.
How can I achieve this result specifically the lines that are present in between the list tiles? I am using a ListView to show the rules. Below is the code for the ListTile that I am using.
Widget ruleTile(String title) {
return ListTile(
contentPadding: EdgeInsets.zero,
leading: Image.asset(
"assets/images/sun.png",
width: 40.w,
),
title: Text(
title,
style: MyTextStyle.littlesmaller,
),
);
}
You can use Stack and dotted_line package combo to create something like that:
Stack(children: [
DottedLine(
direction: Axis.vertical,
lineLength: linelength,
lineThickness: 1.0,
)
// You ListTile Code
],)
Thanks to #pmatatias comment, I figured it out. This is the updated code I used to get the desired output.
Widget ruleTile(String title, num index) {
Widget connector = const DashedLineConnector(
color: Color(0xFFFACC15),
gap: 3,
);
return TimelineTile(
node: TimelineNode(
indicator: Image.asset(
"assets/images/sun.png",
width: 40.w,
),
startConnector: index == 0 ? null : connector,
endConnector: index == rulesList.length - 1 ? null : connector,
),
nodeAlign: TimelineNodeAlign.start,
contents: ListTile(
contentPadding: EdgeInsets.zero,
title: Text(
title,
style: MyTextStyle.littlesmaller,
),
),
);
}
I have been looking for several days to integrate a data search in a listview builder. So far, I have succeeded in a listview but I would like to integrate the indexing system in listview builder to use indexes.
I would like not to display all the documents, but only the ones I would have searched from a search bar. Thank you for your help.
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
var doc = snapshot.data!.docs[index];
var data = doc.data() as Map<String, dynamic>;
return Column(
children: [
SizedBox(
height: MediaQuery.of(context).size.height * 0.0237,
),
FadeIn(
child: GestureDetector(
onTap: () {
Get.toNamed("Route", arguments: RecupDataDocuments(docID: doc.id.toString()));
},
child: Container(
height: MediaQuery.of(context).size.height * 0.102,
width: double.maxFinite,
padding: EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * 0.096
),
decoration: BoxDecoration(
color: Theme.of(context).shadowColor,
borderRadius: BorderRadius.circular(MediaQuery.of(context).devicePixelRatio / 0.16),
boxShadow: [
BoxShadow(
offset: Offset(
0,
MediaQuery.of(context).size.height * 0.006
),
color: Colors.black.withOpacity(0.15),
blurRadius: 4,
blurStyle: BlurStyle.normal
),
]
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
data["nom_categorie"],
style: TextStyle(
fontSize: MediaQuery.of(context).size.height * 0.0237,
color: Theme.of(context).primaryColor
),
),
Icon(
FontAwesomeIcons.chevronRight,
color: Theme.of(context).primaryColor,
size: MediaQuery.of(context).size.height * 0.026
)
],
),
),
),
)
],
);
},
);
I'm not sure what you mean by integrate the indexing system but there are a few ways to go about it.
You need to keep in mind that Firestore is not meant to be used a full text search platform. Google recommends using dedicated search services such as Algolia, Meilisearch or Typesense; The later two being open source. There are some project where people have tried to do this on firebase but personally not seeing much support as Full Text search is a different animal all together.
On the flip side, if you simply want to search based on Firestore queries, you should be fine. You could also use the flutter autocomplete class. I would also recommend looking into FutureBuilder as that is the recommended way of dealing with Futures.
As for the code in your question, it's hard to help with specific Firestore query strings if you have not included the code where you actually retrieve data.
I was building my Widgets from a list that was predefined in a file of MyClass I created. This worked but I wanted to be able to store persisted data for adding a Boolean favorite field.
I created the Hive Types/Fields for my class, generated the type adapters, and successfully loaded the Hive box on first run of the app, and I can print values to the console, so I know the data is all there and correct.
In the class I have, name, image url path to asset image and a favorite field.
Before when I was using the list to get my data I was able to get the image URL like this:
Expanded(child: Image.asset(widget.MyClass.imageURL)),
Now I want to get this from the Hive box
Box<MyClass> box = Hive.box<MyClass>('myClassBox');
//This is where I am stuck
Expanded(child: Image.asset(box.???)),
I tried box.values.where and box.get() to then get to imageURL field. But get requires a key, which I don't have to pass it from
Widget build(BuildContext context)
And I then have the same issue when trying to access the favorite field, which I am using the Favorite Button package (favorite_button 0.0.4). And I will then update the true/false value based on the button being tapped.
If someone can point me in the right direction that would be great.
Thanks.
Edit:
Here is the Widget:
Widget build(BuildContext context) => GestureDetector(
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => TaskPage(job: widget.job), //Need to get data from Hive now
)),
child: Container(
padding: const EdgeInsets.all(16),
height: 100,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
Expanded(flex: 3, child: buildText()),
Expanded(child: Image.asset(widget.job.imageUrl)),//Need to get data from Hive now
GestureDetector(
child: Icon(
widget.job.fav ? Icons.favorite : Icons.favorite_border, //Need to get data from Hive now
),
onTap: () {
// add/remove from favorites list
}
),
],
),
),
);
Second Edit: Here is the same code after implementing the suggestion given
Widget build(BuildContext context) => GestureDetector(
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => TaskPage(job: Hive.box<Job>('jobBox').get(context)), //This bit is still broken so I need to look at this
)),
child: Column(
children:
Hive.box<Job>('jobBox').values.toList().map(
(elementList) => Container(
padding: const EdgeInsets.all(16),
height: 100,
decoration: BoxDecoration(
color: white,
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
Expanded(flex: 3, child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
elementList.name,
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20),
),
SizedBox(height: 10),
//Text('Num tasks in job'),
],
)),
Expanded(child: Image.asset(elementList.imageURL)),
GestureDetector(
child: Icon(
elementList.fav
? Icons.favorite
: Icons.favorite_border,
color: elementList.fav ? Colors.red.shade200 : Colors.grey,
),
onTap: () {
//To do
}
// )
),
],
),
),
)
.toList(),
),
);
Assuming that you have only 1 data in the box, you can access that stored data like this.
Box<MyClass> box = Hive.box<MyClass>('myClassBox');
if(box.isNotEmpty) {
final data = box.values.first;
// use data
} else {
// empty state
}
Hive values could have keys, depending on how you use it. If you used box.put(key, value), you can use box.get(key) to work with keys and values.
If you used box.add(value), it stores the data with auto assigned indexes starting from 0. So you can usebox.getAt(index) to get a data with index.
I am using map for iterating the items on it like this,
...eventModifierProvider.selectedEvents.map((EventStore ) {
return Dismissible(
key: UniqueKey(),
onDismissed: (direction) {
eventModifierProvider.events.remove( what should be here?);
},
background: Container(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 20, 0),
child: Icon(Icons.close, size: 25, color: Colors.white),
),
decoration: BoxDecoration(color: Colors.redAccent)
),
child: Column(
children: [
TimeAndDuration(
time: "09:12",
amPm: "AM",
duration: "1 Hour 3 min",
),
ParticularDateEvent(
lectureSubject: EventStore.subject.toString(),
lectureStandard: EventStore.level.toString(),
lectureRoom: EventStore.room.toString(),
),
],
),
);
}),
In the selectedEvents map there is a key of DateTime and values are stored in EventStore list.
Now, I am also using the Dismissible widget for removing the item from the map. How can I do that here? Do help me
You need to get the index like this:
int index = eventModifierProvider.selectedEvents.indexOf(e);
Add the key
Key(index)
Remove it with index
RemoveAt(index)
I hope this is helpful
I have Listview.builder that contains data from an array in Firestore that an array in subcollection my structure data Like this
But when to showing that array to Listview its showing the only first, which is 0. I need to get all data In array to list view. How can I fixe itemCount to get all data?
Collcetion
-Institute
--document
---PI3naj7N7GgwlDSjK3rV0Twiu2S2
----subCollcetion
------Ravs
-------documentsubCollcetion
English
field
Ravs name :English
item array
0
Contains Data Map
1
Contains Data Map
2
Contains Data Map
.
.
.
.
this my function
StreamBuilder(
stream:Firestore.instance.collection("Institute").document(
'PI3naj7N7GgwlDSjK3rV0Twiu2S2').collection("Ravs")
.where('Ravs name',isEqualTo:'English ' ).snapshots() ,
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) return Container(
alignment: Alignment.center,
child: Text(
'it has error ',
style: TextStyle(fontSize: 12),),
);
return snapshot.hasData
? Scaffold(
appBar: new AppBar(backgroundColor: Colors.brown[600],),
body:Card(
child:ListView.builder(
itemCount:snapshot.data.documents.length,
itemBuilder: (_,index){
DocumentSnapshot ds =snapshot.data.documents[index];
return Directionality(
textDirection: TextDirection.rtl,
child:Card(
child:InkWell(
onTap: (){
},
child:Column(
children: <Widget>[
new Container(
padding: const EdgeInsets.only(top: 10),
width: 500,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(150)),
),
alignment: Alignment.topCenter,
child:
FadeInImage.assetNetwork(fit:
BoxFit.fill,placeholder:
'assets/images/image.jpg',
image: ds.data['item'][index]['Iamge path']),
),
new Container(
padding: const EdgeInsets.all(10.0),
alignment: Alignment.topRight,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(" name Rav:${ds.data['item'][index]['Ravs_name'] }",textDirection: TextDirection.rtl, style: Theme.of(context).textTheme.title),
Text("Date:${ds.data['item'][index]['Rav_Date']} ",textAlign: TextAlign.right, style: TextStyle(color: Colors.black.withOpacity(0.5))),
],
)
)
],
)
)
)
);
}
),
)
) : Center(child: CircularProgressIndicator(),);
},
);
This structure you are presenting seems to have collection in collection which is not possible. However, as this is possible to run your code, I assume that in the structure Ravs is a subcollection of documents that have field Rav name.
So the code creates query with .where('Ravs name',isEqualTo:'English '), this create snapshot of documents which I believe contains only one document. So bellow statement :
itemCount:snapshot.data.documents.length
equals to 1, and this is why you get one iteration in your itemBuilder.
I do not have any chance to test it, but I think that solution should be:
change itemCount:snapshot.data.documents[0].data['item'].length - or something like that - to give you proper number of items to iterate trough.
First line of itemBuilder should be set to: DocumentSnapshot ds =snapshot.data.documents[0]; This is because you want to iterate over array in the only one document.
If it do not work at once you should play with it... I am basing this on the fact that what you presented is possible to run :).
I hope it will help!