setState() causing infinite loop in flutter - android

so I'm trying to create a loop that will add a string to a List from my Bloc, however whenever i successfully added the string into the list, every single time a setstate is ran the program runs the loop all over again and adds the string into the list, i think adding a boolean for the for loop could work, but is there a more efficient way to do this?? I only want the loop to run once, even when the build method is called again.
code:
Container(
height: MediaQuery.of(context).size.height / 1.2,
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => setState(() {}),
child: Text("set state")),
ElevatedButton(
onPressed: () {
BlocProvider.of<BlackjackkBloc>(context).add(
DrawCardEvent(deck_id: deckId!, draw_count: "1"));
setState(() {
dealerCards++;
dealerVal += 10;
});
},
child: Text("deal")),
BlocBuilder<BlackjackkBloc, BlackjackkState>(
builder: (context, state) {
if (state is BlackjackkDrawLoaded) {
for (int i = 0; i < state.bjdraw.cards.length; i++) {
print("going $i");
cards.add(state.bjdraw.cards[i].image);
print("repeat");
}
return Column(
children: List.from(cards.map((name) => Text(name))),
);
/*return Text("${cards}");*/
}
return Text("o");
}),
Text("cards length = ${cards.length}"),
Container(
height: 150,
width: 100 * dealerCards.toDouble(),
child: ListView.builder(
physics: BouncingScrollPhysics(),
/*separatorBuilder: (context, index) => SizedBox(
width: 0.1,
),*/
itemCount: dealerCards,
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) {
return Container(
width: 100,
height: 150,
child: CachedNetworkImage(
imageUrl:
"https://deckofcardsapi.com/static/img/AS.png",
),
);
},
),
),
SizedBox(height: 30),
SizedBox(
height: 40,
),
Text(
"Dealer",
style: desctext,
),
SizedBox(height: 10),
Text(playerCards.toString()),
Text(
dealerVal.toString(),
style: cardtext,
),
BlocBuilder<BlackjackkBloc, BlackjackkState>(
builder: (context, state) {
if (state is BlackjackkShuffleLoaded) {
deckId = state.bjshuffle.deckId;
return Text(state.bjshuffle.deckId);
}
if (state is BlackjackkLoad) {
return CircularProgressIndicator();
}
return Text("blum load");
},
)
],
),
),

Try add a buildWhen method in your blocBuilder that only builds if current state differ from previous state.
BuildWhen: (previous, current) { current.state != previous.state }

Why are using setState within the bloc?
Emit a new state from the DrawCardEvent function and change your UI as per that state.
I guess you are using a bloc instead of a cubit. If you are new to the bloc, maybe start with cubit. Cubit is simple and is good enough in 99% of the business cases.

Related

How to access dynamic input fields values on button click in flutter

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
}
},
)

How can I implement RefreshIndicator in Listview.Builder using Getx?

I want to implement RefreshIndicator in my Listview.builder but the problem is that when I place RefreshIndicator it's not working. And I search in Google the answer that I find is that place physic:
AlwaysScrollableScroll() in Listview.Builder and when I try it RefreshIndicator worked but Listview.Builder not working and I search in Google the answer that I find is that place physic:
NeverScrollableScroll() in Listview.Builder then My Listview.Builder is working OK but Refresh Indicator not working. What can I do?
I am little bit confused, what I can do: either I place AlwaysScrollableScroll() or NeverScrollable().
Here is my code:
return Scaffold(
body: SafeArea(
child: Padding(
padding: EdgeInsets.all(10),
child: Column(
children: [
FirstRow(
headingText: 'My Appointments',
context: context,
),
SizedBox(
height: 10,
),
Obx(() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppButtonLarge(
selected: appointmentController.appointmentT ==
'upcomingAppointments' ??
false,
text: 'Upcoming',
onTap: onAppointmentChange),
AppButtonLarge(
selected: appointmentController.appointmentT ==
'pastAppointments' ??
false,
text: 'Past',
onTap: onAppointmentChange),
],
);
}),
SizedBox(
height: 10,
),
Expanded(
child: SingleChildScrollView(
physics: ScrollPhysics(),
child: Column(
children: [
GetX<DoctorAppointmentController>(
builder: (controller) {
// List<Appointment> controller.appointmentList=[];
// allAppointment.forEach((element) {
// if(element!=null){
// controller.appointmentList=controller.appointmentList+element;
// }
// });
// List<Appointment> controller.appointmentList=snapshot.data;
print(controller.appointmentList.length);
return controller.isLoading.value
? AppWidgetsCard.getProgressIndicator()
: controller.appointmentList.length > 0
? RefreshIndicator(
key: refreshKey,
onRefresh: () async{
await Navigator.pushReplacement(context, PageRouteBuilder(pageBuilder: (a,b,c)=>GetAllDrAppointments(),
transitionDuration: Duration(seconds: 3)));
},
child: Obx(()=>ListView.builder(
physics: const NeverScrollableScrollPhysics(),
// reverse: true,
itemCount: controller.appointmentList.length,
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemBuilder: (context, index) {
void goToNext() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
MyAppointments(
appointment: controller
.appointmentList[
index], isDoctor: true),
));
}
if (index ==
controller.appointmentList.length -
1) {
id = controller
.appointmentList[index].id;
return Column(
children: [
TextButton(
onPressed: goToNext,
child: AppWidgetsCard
.getAppointmentCard(
controller.appointmentList[index],
goToNext, isDoctor: true), ),
Obx(
() {
return controller.loadMore.value
? AppWidgetsCard
.getProgressIndicator()
: Container(
height: 40,
width: 200,
child: AppMethodButton(
selected: true,
text: 'Load More',
onTapMethod: () {
print('data');
controller
.fetchMoreAppointments(
id);
}),
);
},
)
],
);
} else {
return TextButton(
onPressed: goToNext,
child: AppWidgetsCard
.getAppointmentCard(
controller
.appointmentList[index],
goToNext, isDoctor: true));
}
},
),
),
)
: AppWidgetsCard.getEmptyCard('Appointment');
},
),
],
),
)),
],
),
)),
bottomNavigationBar: Container(
height: 50,
child: LastRow(
page: 'Appointment',
)),
);'''
About the ScrollPhysics there are different kinds of scrolling behavior. And AlwaysScrollableScroll means that the ListView is able to scroll no matter whether the body size of the ListView is larger than the assigned size. By default, the ListView only scrolls when the size of the body of the ListView is larger than the assigned parent size. So to adopt the RefreshIndicator does not matter with the scroll physics.
To avoid the cache issues of ListView to force the ListView update, you should update the data of the ListView. You would use the Obx wrapper with the ListView and create the list like below:
List<String> data = <String>[].obs;
Obx(()=> data.value.isEmpyty ? SizedBox() : ListView(children: data.value.map((value) => Text(value)).toList())

How to show progress bar in flutter before loading data

I have a request function and I want to show a progress bar before loading data but idk how to do that. can someone show me an example?
This code calls your function after running the linear progress indicator for a specified time.
The script makes use of no external libraries
import 'dart:async';
import 'package:flutter/material.dart';
class ProgressBarCall extends StatefulWidget {
const ProgressBarCall({ Key? key }) : super(key: key);
#override
_ProgressBarCallState createState() => _ProgressBarCallState();
}
class _ProgressBarCallState extends State<ProgressBarCall> {
double _value = 0;
#override
Widget build(BuildContext context) {
checkIndicator(delay: 2);
return Scaffold(
body: Column(
children: [
LinearProgressIndicator(
backgroundColor: Colors.grey,
color: Colors.green,
minHeight: 5,
value: _value,
),
Expanded(
child: Container(child: Text("Perform function after loading"),),
),
],
),
);
}
void checkIndicator({delay = 2}){
new Timer.periodic(
Duration(milliseconds: delay*100),
(Timer timer){
setState(() {
if(_value == 1) {
timer.cancel();
performFunction();
}
else {
_value = _value + 0.1;
}
});
}
);
}
void performFunction(){
//call your function after the loading
}
}
The performFunction() method can be used to load your data Set the duration of the linear progress indicator by setting the delay in the checkIndicator() method.
You can implement flutter_easyloading package, https://pub.dev/packages/flutter_easyloading
for the progress widget is self you can use [CircularProgressIndicator] https://api.flutter.dev/flutter/material/CircularProgressIndicator-class.html)
for managing the state show loading -> then the actual data -> in case of failure show the error message and stop loading
this can be achieved throw many different ways
1- the easies one FutureBuilder
FutureBuilder<String>(
future: _calculation, // a previously-obtained Future<String> or null
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
List<Widget> children;
if (snapshot.hasData) {
children = <Widget>[
const Icon(
Icons.check_circle_outline,
color: Colors.green,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Result: ${snapshot.data}'),
)
];
} else if (snapshot.hasError) {
children = <Widget>[
const Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'),
)
];
} else {
children = const <Widget>[
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting result...'),
)
];
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
),
);
},
);
or you can use any state management you want
bloc (example)[https://bloclibrary.dev/#/flutterweathertutorial]

Read and Unread Message feature

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.

Create lists with different types of items

I would like to create a horizontal listView where the first item is always an + icon. After get image, it will move to second item and so on.
This is what I tried
Container(
color: const Color(0xFF00FF00),
padding: EdgeInsets.only(top: 15),
height: MediaQuery.of(context).size.height * 0.25,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 1,
itemBuilder: (context, index) {
return _buildRow();
}),
)
Widget _buildRow() {
return IconButton(
icon: Center(
child: Icon(
Icons.camera,
size: 30.0,
),
),
onPressed: () {
getImage();
},
);
}
You could start your list of widgets with just the plus widget. And you add your following widgets on the first position of the widget using dart's insert funcion in lists. Something like this:
listWidgets.insert(0, yourWidget);
Doing that you plus widget always will be the last item of the list and will be displayed on the last position.
Widget initialWidget = _buildRow();
List<Widget> listWidgets = [initialWidget];
Container(
color: const Color(0xFF00FF00),
padding: EdgeInsets.only(top: 15),
height: MediaQuery.of(context).size.height * 0.25,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 1,
itemBuilder: (context, index) {
return listWidgets[index]
),
)
Widget _buildRow() {
return IconButton(
icon: Center(
child: Icon(
Icons.camera,
size: 30.0,
),
),
onPressed: () {
Widget yourWidget = <your-logic-to-create-widget>;
setState(() {
listWidgets.insert(0, yourWidget);
})
},
);
}

Categories

Resources