I am trying to create Read and Unread Message feature in my chat application.
So far I can create only Unread, which means when I send Message to firebase collection, I decleared my
READ property field as FALSE,
I am confused how the second user will change the value of the "READ: TRUE" back if he checks the chat room, and if the second user is currently in the chat room he should still change the READ: true.
here is the my send message data:
sender_id:12233,
reciever_id: 6767,
message:'hello please help',
read: false,
here is my code
StreamBuilder(
stream: firestore
.collection('chat')
.doc(widget.peerid)
.collection('Messages')
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.data.docs.isEmpty) {
return Center(
child: Column(
children: [
Container(
child: Icon(
FontAwesomeIcons.comments,
size: 40,
),
),
Text(
'Say Hello',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
),
)
],
),
);
}
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
final authid =
snapshot.data.docs[index].data()['idFrom'];
final msg =
snapshot.data.docs[index].data()['content'];
bool check =
authid == auth.currentUser.uid ? true : false;
return Padding(
padding: EdgeInsets.symmetric(
horizontal: 15,
vertical: 10,
),
child: Column(
crossAxisAlignment: check
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: check
? Colors.indigo
: Colors.white,
borderRadius: check
? BorderRadius.only(
bottomLeft: Radius.circular(30),
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
)
: BorderRadius.only(
bottomRight:
Radius.circular(30),
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
),
child: ConstrainedBox(
constraints:
BoxConstraints(maxWidth: 150),
child: Column(
children: [
Text(
msg,
style: GoogleFonts.raleway(
textStyle: TextStyle(
color: check
? Colors.white
: Colors.black,
),
),
),
Text('')
],
),
),
)
],
),
);
});
}),
),
),
Container(
height: Platform.isIOS ? 95 : 80,
padding: EdgeInsets.only(top: 8.0),
decoration: BoxDecoration(
borderRadius:
BorderRadius.only(topRight: Radius.circular(70)),
color: Theme.of(context).backgroundColor,
),
child: ListTile(
leading: Icon(
Icons.add,
color: Theme.of(context).primaryColor,
),
title: TextFormField(
controller: messagesController,
onChanged: (value) {
messagesController.text = value;
},
decoration: InputDecoration(
hintText: 'Enter your messgae here...',
border: InputBorder.none,
),
maxLines: null,
),
trailing: messagesController.text.trim() == null
? Container(
width: 40,
height: 45,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [
Constants.color1,
Constants.color2,
],
),
),
child: GestureDetector(
onTap: () async {
await ControllerApi().sendMessage(
content: messagesController.text.trim(),
chatID: widget.peerid,
messageType: 'text',
myID: auth.currentUser.uid,
selectedUserID: widget.userid,
);
},
child: Icon(
Icons.send,
size: 20,
color: Colors.white,
),
),
)
: Container(),
),
)
],
Thanks.
let's write a function that marks the opponent's messages as read.
Future<void> seeMsg(int peerId) async{
final query = await FirebaseFirestore.instance
.collection('chat')
.doc(peerId)
.collection('Messages')
.where('sender_id', isEqualTo: peerId)
.where('read', isEqualTo: false)
.get();
query.docs.forEach((doc) {
doc.reference.update({'read': true});
});
}
Then, call this function inside StreamBuilder body.
Every time, a new message is generated inside this chat collection, this function checks for unread messages from your peer and marks them as read.
Inside your chat app, when you load messages to user, lets say you already have loaded messages and have a List<Message> messages.
Lets say you show messages with ListView.builder:
return ListView.builder(
shrinkWrap: true,
controller: _controller,
reverse: true,
itemCount: messages.length,
itemBuilder: (context, index) {
final message = messages[index];
// We check if message is new (dont know your entity so lets say it has boolean `read`, that false when new.
final bool isNew = !message.read;
// Given that you store your fire id in myId variable we check if message is received and not sent.
final bool toMe = message.recieverId == myId;
// Check that we need to mark message as read
if (isNew && toMe) {
return FutureBuilder<void>(
future: markAsRead(), // markAsRead is a Future function to change your FIELD value in firestore.
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
return YourMessageWidgetThatWasSeen()
}
return YourMessageWidgetThatWasNotSeen();}); //FutureBuilder ends here.
return MessageBubble(); // or this message is already seen by user and we return message bubble like usually do.
markAsRead() function implementation depends on what structure your database has, for example when your each user has its own collection of messages to store, AND if you have a collections for a dialog preview with last message shown, you will need to create a batch with 4 update operations in it.
Use this flutter library
visibility_detector
Please see the below code, it will help you to implement the read & unread message.
#override
Widget build(BuildContext context) {
return ListView.separated(
itemBuilder: (ctx, index) {
ChatModel chatModel =
ChatModel.fromJson(snapshot.data.docs[index].data());
switch (chatModel.messageType) {
case MessageType.TEXT:
{
return VisibilityDetector(
key: Key(snapshot.data.docs[index].id),
onVisibilityChanged: (VisibilityInfo visibilityInfo) {
var visiblePercentage = visibilityInfo.visibleFraction * 100;
if (visiblePercentage == 100 &&
!chatModel.isSeen &&
chatModel.sender != userController.userModel.value.uid) {
FirebaseFirestore.instance
.collection(FirebaseKeys.chatRoom)
.doc(AppHelper.getChatID(
userController.userModel.value.uid, userModel.uid))
.collection(FirebaseKeys.messages)
.doc(snapshot.data.docs[index].id)
.update({
"isSeen": true,
});
}
},
child:
TextMessage(chatModel, snapshot.data.docs[index].reference),
);
}
case MessageType.GIF:
{
return GifMessage(chatModel, userModel.uid);
}
case MessageType.IMAGE:
{
return ImageMessage(snapshot.data.docs[index].reference,
chatModel, userModel.uid);
}
case MessageType.AUDIO:
{
break;
}
case MessageType.VIDEO:
{
break;
}
case MessageType.PDF:
{
break;
}
case MessageType.FILE:
{
break;
}
case MessageType.OTHER:
{
break;
}
}
return FlutterLogo();
},
shrinkWrap: true,
itemCount: snapshot.data.docs.length,
controller: scrollController,
reverse: true,
physics: ClampingScrollPhysics(),
separatorBuilder: (BuildContext context, int index) {
return Container(
height: 5,
margin: EdgeInsets.only(top: 2, bottom: 2),
);
},
);
}
Generally, it's not correct to keep this mark in the same table.
Because you need to add access rights sender and receiver to change all messages.
Better solution is to create another table something like _metadata and keep this mark there.
Related
I am working on an attendance application where I assign wages to the workers. I want to store all the wages given to the workers into the database. But the problem is I want to access all the given values on button click. I have no idea how it can be done in flutter. I am a beginner.
I have given all the codes and the image of what output i want.
Image of Emulator
Here is my code...
ATTENDANCE SCREEN
...rest code...
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Upload Patti'),
content: SingleChildScrollView(
child: ListBody(
children: [
TextFormField(
controller: _mainWagesController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "Enter Amount",
prefixIcon: Icon(Icons.wallet, color: Colors.blue),
),
),
],
),
),
actions: <Widget>[
ElevatedButton(
onPressed: () {
Navigator.pop(context);
newWages = _mainWagesController.text;
setState(() {});
},
child: const Text("Assign Wages"),
),
],
);
},
);
},
child: const Icon(Icons.check_circle),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.00),
child: Column(children: [
const SizedBox(
height: 20,
),
Center(
child: Text(
"Date : ${DateFormat.yMMMEd().format(DateTime.parse(widget.attendanceDate.toString()))}",
style: const TextStyle(fontSize: 20),
),
),
const SizedBox(
height: 20,
),
FutureBuilder(
future: SupervisorAttendanceServices.getAttendancesDetailsList(
widget.attendanceId),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
var data = snapshot.data['hamal'];
return ListView.builder(
itemCount: data.length,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return HamalAttendanceWidget(
workerId: data[index]['worker_id'],
name: data[index]['worker_name'],
wages: newWages,
masterAttendanceId: widget.attendanceId,
isPrensent: data[index]
['attendance_worker_presense']
.toString());
});
} else if (snapshot.hasError) {
return const Center(
child: Text("Something went wrong !"),
);
} else {
return const Center(child: LinearProgressIndicator());
}
},
),
]),
),
),
...rest code
widget
Widget build(BuildContext context) {
return Card(
child: Column(children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(
width: 10,
height: 50,
),
const Icon(FeatherIcons.user),
const SizedBox(
width: 20,
),
Text(
widget.name,
style: const TextStyle(fontSize: 18),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
width: 150,
height: 60,
child: TextFormField(
// onChanged: _onChangeHandler,
initialValue: widget.wages.toString(),
decoration: const InputDecoration(
hintText: "Wages",
prefixIcon: Icon(
Icons.wallet,
color: Colors.blue,
)),
)),
],
)
]),
);
}
I suggest you use a StateManager for your application, for example GetX
is a good solution. Create a controller file like the below:
// define this enum outside of class to handle the state of the page for load data
enum AppState { initial, loading, loaded, error, empty, disabled }
Rx<AppState> pageState = AppState.initial.obs;
class AttendanceCntroller extends GetxController{
RxList<dynamic> dataList=RxList<dynamic>();
#override
void onInit() {
//you can write other codes in here to handle data
pageState(AppState.loading);
dataList.value=
SupervisorAttendanceServices.getAttendancesDetailsList(attendanceId);
pageState(AppState.loaded);
super.onInit();
}
}
and in your view(UI) page, handle it in this way:
class AttendanceView extends GetView<AttendanceCntroller>{
#override
Widget body(BuildContext context) {
// TODO: implement body
return Obx( ()=> controller.pageState.value==AppState.loading ? const
Center(child: LinearProgressIndicator()) : ListView.builder(
itemCount: controller.dataList.length,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return HamalAttendanceWidget(
workerId: controller.dataList['worker_id'],
name: controller.dataList['worker_name'],
wages: newWages,
masterAttendanceId: widget.attendanceId,
isPrensent: controller.dataList[index]
['attendance_worker_presense']
.toString());
})
)
}
}
for more data read the GetX link and read clean architecture with the GetX sample repository of my GitHub it have advanced management of states with GetX with dependency injection handling.
If you want to have prefilled value in TextFormField, you can either use initialValue or controller parameter.
The value of controller parameter will help you to get/update the value of TextFormField.
For controller parameter refer below.
TextEditingController controller = TextEditingController(text: 'This is text will be pre-filled in TextFormField');
...
TextFormField(
controller: controller,
);
Create List or Map of those controllers.
List<TextEditingController> listOfControllers = [ controller1, controlle2,...];
Use for loop through this List on onClick() method of Button.
ElevatedButton(
onPressed: () {
for(var controllerItem in listOfControllers) {
print(controllerItem.text); // the value of TextFormField
}
},
)
I am trying to change the view of profile page on my application.
I need different views when I press the "Posts" (Gönderiler) button and "Activities" button.
So I added buttons with toggles. When I try to change the view of page, I click one of the buttons, but it just doesn't work.
Here is my code.
Expanded(
child: Container(
child: ElevatedButton(
style: ButtonStyle(
shape: MaterialStateProperty.all<
RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(40.0),
side: BorderSide(
width: 2, color: Color(0xff8082d0)),
),
),
backgroundColor:
MaterialStateProperty.all<Color>(
Color(0xfff0f1f1),
),
),
child: Text(
'Gönderiler'.toUpperCase(),
style: TextStyle(
color: Color(0xff8082D0),
fontFamily: 'Poppins-Medium',
fontSize: 10.0,
fontWeight: FontWeight.w800,
),
),
onPressed: () => buildPostView('post'),
),
),
),
Here is the function that I call when I press the button:
buildPostView(String type) {
//print('Post ' + isTogglePost.toString());
//print('Activities ' + isToggleActivities.toString());
//print('About ' + isToggleAbout.toString());
if (type == 'post') {
isTogglePost = true;
isToggleActivities = false;
isToggleAbout = false;
return buildGridPost();
} else if (type == 'activity') {
isTogglePost = false;
isToggleActivities = true;
isToggleAbout = false;
return Container();
}
}
It does not go in to itemBuilder. Also there is no error. Here is the function:
buildGridPost() {
return StreamGridWrapper(
Axis.vertical,
shrinkWrap: true,
padding: const EdgeInsets.symmetric(horizontal: 10.0),
stream: postRef
.where('ownerId', isEqualTo: widget.profileId)
.orderBy('timestamp', descending: true)
.snapshots(),
//physics: const ScrollPhysics(),
itemBuilder: (_, DocumentSnapshot snapshot) {
PostModel posts =
PostModel.fromJson(snapshot.data() as Map<String, dynamic>);
return PostTile(
post: posts,
);
},
);
}
It works when I put the buildPostView function as a body, but it does not work when I call this function from Button.
I hope somebody can help me
Hello please I am new in flutter mobile devellopement. I would like to do a pagination (Lazy loading from streambuilder with firestore). Indeed when I do a stream all the documents load and it takes a long time and also my application sometimes bugs (maybe because I loaded a lot of data in memory). I would like to simplify things by using a pagination but I don't really know how to do it. or you can load 10 documents per call. please help me find a solution to avoid bugging my application and load less document per call. here is the full code of the feed part
class FeedJob extends StatefulWidget {
FeedJob({Key? key}) : super(key: key);
#override
_FeedJobState createState() => _FeedJobState();
}
class _FeedJobState extends State<FeedJob> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection("job_feed")
.orderBy("time", descending: true)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return Column(
children: [
Expanded(
flex: 0,
child: Column(children: [
TiTle(title: "Feeds"),
])),
Expanded(
child: ListView(
children: [
ListView(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
children: snapshot.data!.docs.map((e) {
return Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
child: Column(
children: [
ListTile(
leading: Container(
width: 40,
height: 40,
alignment: Alignment.topCenter,
decoration: BoxDecoration(
image: DecorationImage(
alignment:
Alignment.topCenter,
fit: BoxFit.cover,
image:
CachedNetworkImageProvider(
e.get(
'photo'))),
color: Colors.blue
.withOpacity(.2),
borderRadius:
BorderRadius.all(
Radius.circular(
20))),
),
trailing: Column(
children: [
Icon(Icons.comment_rounded,
size: 15,
color: Colors.grey),
Text("comment".tr,
style: TextStyle(
fontSize: 8,
color: Colors.grey))
],
),
title: Text(e.get('name'),
style: TextStyle(
color: Colors.black,
fontSize: 10,
fontWeight:
FontWeight.bold)),
subtitle:
Text(e.get('time').toString(),
style: TextStyle(
fontSize: 8,
color: Colors.grey,
)),
),
Padding(
padding: const EdgeInsets.only(
left: 5.0,
right: 8,
bottom: 15),
child: Text(
e.get('description'),
textAlign: TextAlign.justify,
),
)
],
),
),
)
],
);
}).toList()),
],
),
),
],
);
}
}));
}
}
You can use startAfterDocument to tell Firestore what was the last document you fetched (assuming you keep a reference to it each time).
// Prepare the query.
final List<JobModel> fetchedData = [];
final int firstBatchSize = 6;
final int nextBatchSize = 12;
DocumentSnapshot<JobModel>? lastDocument;
final Query<DestinationModel> query = FirebaseFirestore.instance
.collection('jobs')
.orderBy('time', descending: true)
.withConverter<JobModel>(
fromFirestore: (snapshot, _) => JobModel.fromFirestore(snapshot),
toFirestore: (JobModel job, _) => job.toFirestore(),
);
// Set the starting point of the query.
if (lastDocument != null) query.startAfterDocument(lastDocument);
// Set the limit for the query.
query.limit(fetchedData.isEmpty ? firstBatchSize : nextBatchSize);
// Run the query.
final QuerySnapshot<JobModel> results = await query.get();
// Do something with the results; Store the last document fetched.
class IlacProductController extends GetxController {
final IlacProductRepo ilacProductRepo;
IlacProductController({required this.ilacProductRepo});
List<dynamic> _ilacproductList = [];
List<dynamic> get ilacproductList => _ilacproductList;
bool _isLoaded = false;
bool get isLoaded => _isLoaded ;
Future<void> getIlacProductList() async {
Response response = await ilacProductRepo.getIlacProductList();
if (response.statusCode == 200) {
print("got ilaç");
_ilacproductList = [];
_ilacproductList.addAll(Product.fromJson(response.body).products);
_isLoaded=true;
update();
} else {return print("something wrong");}
}
}
I have 13 different controller codes similar to the above code, each representing a different category
#override
Widget buildResults(BuildContext context) {
return GetBuilder<HalkSagligiProductController>(builder: (allProduct) {
return allProduct.isLoaded
? ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: allProduct.halksagligiproductList.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Get.toNamed(RouteHelper.getHalkProduct(index));
},
child: Container(
margin: EdgeInsets.only(
left: Dimensions.width20,
right: Dimensions.width10,
bottom: Dimensions.height15),
child: Row(
children: [
//image section
Container(
width: Dimensions.listViewImgSize,
height: Dimensions.listViewImgSize,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
Dimensions.radius20),
color: Colors.white38,
image: DecorationImage(
fit: BoxFit.cover,
image: CachedNetworkImageProvider(AppConstans
.BASE_URL +
AppConstans.UPLOAD_URL +
allProduct
.halksagligiproductList[index]
.img!))),
),
//text section
Expanded(
child: Container(
height: Dimensions.listViewTextContSize,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(
Dimensions.radius20),
bottomRight: Radius.circular(
Dimensions.radius20)),
color: Colors.white),
child: Padding(
padding: EdgeInsets.only(
left: Dimensions.width10,
right: Dimensions.width10),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.center,
children: [
BigText(
text: allProduct
.halksagligiproductList[index]
.name!,
),
SizedBox(
height: Dimensions.height10,
),
ExpandableProductText(
text: allProduct
.halksagligiproductList[index]
.description!),
SizedBox(
height: Dimensions.height10,
),
],
),
),
),
)
],
)),
);
})
: const CircularProgressIndicator(
color: AppColor.mainColor,
);
});
}
In the above code, I am pulling the image name and description with getx.
My question is: How can I search 13 different controllers with Getx, both by name and by the words in the description, on the search screen?
final searchController = Get.find<SearchController>();
You can use multiple time, this function in different screens.
Declare final ilacProductController = Get.put(IlacProductController());
into the screen and use object ilacProductController for access.
My app is simple Authentication todo app where I store authenticate user UID in firestore document red mark represents login user UID and blue mark represents tasks of that particular user of below image.
Firestore image
Using QuerySnapshot I am trying to get particular user data but my code is not working dynamically.
I wanted to get particular user content array data and login user uid is same as document id here is my code.
StreamBuilder<QuerySnapshot>(
stream: _firestore.collection('Users').snapshots(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(
'Some thing went wrong! \n Restart your app!',
style: TextStyle(fontSize: 28.0, color: Colors.red),
));
} else if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(
color: Colors.cyan,
));
} else if (snapshot.hasData || snapshot.data != null) {
return ListView.builder(
shrinkWrap: true,
//------ This is static how to do it dynamically where only particular user content length will get?
itemCount: snapshot.data?.docs[1]['content'].length,
itemBuilder: (BuildContext context, int index) {
//------ This is static how to do it dynamically where I know that 1 index user is login that's why I gave index 1 how to do it dynamic?
DocumentSnapshot? documentSnapshot = snapshot.data?.docs[1];
return Padding(
padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 8.0),
child: Card(
color: Colors.black,
elevation: 10,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 20.0, horizontal: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: GestureDetector(
onTap: () async {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Edit Task'),
content: TextFormField(
controller: editTodoController,
decoration: InputDecoration(
hintText: 'Task info.',
errorText: _validate ? "Enter task" : null,
// label: Text("Task info"),
),
),
actions: [
TextButton(
onPressed: () async {
try {
final contentList = _firestore.collection('Users').doc(_user?.uid);
final docSnap = await contentList.get();
List content = docSnap.get('content');
ref.update({
'content': FieldValue.arrayRemove([content[index]]),
});
ref.update({
'content': FieldValue.arrayUnion([editTodoController.text]),
});
setState(() {
if (editTodoController.text.isEmpty) {
_validate = true;
} else {
_validate = false;
Navigator.of(context).pop();
}
editTodoController.clear();
// addTodoController.text.isEmpty ? _validate = true : _validate = false;
});
} catch (e) {
print(e);
}
},
child: Text('UPDATE'),
)
],
),
);
},
child: Text(
'${documentSnapshot!['content'][index]}',
// '${index} \t ${todoList[index]}',
style:
TextStyle(fontSize: 20.0, color: Colors.white),
),
),
),
IconButton(
onPressed: () async {
try {
final contentList = _firestore.collection('Users').doc(_user?.uid);
final docSnap = await contentList.get();
List content = docSnap.get('content');
await ref.update({
'content': FieldValue.arrayRemove([content[index]]),
});
} catch (e){
print(e);
}
},
icon: Icon(Icons.delete, color: Colors.white54, size: 29.0,)
),
],
),
),
),
);
},
);
}
return CircularProgressIndicator();
})