Update the SubTitle of a listview without refreshing in Flutter - android

At start, I load data from the database that populates the ListView. These data is displayed on the ListView title and subtitle. When a user taps on one of the items on the list, a showModalBottomSheet popups with fields to update the list(index). This update is carried out successfully but on the close of the showModalBottomSheet, the values on each ListView item refreshes to default (data from database).
Please, how can I update the ListView items without the ListView refreshing to initial data value?
Widget _buildSubjects(BuildContext context, int index) {
response = getFieldData(_snapshot!.data[index]);
return ListTile(
trailing: IconButton(
icon: Icon(Icons.add),
onPressed: () {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (context) {
return SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.all(10.0),
child: Text(
_snapshot!.data[index]['name']
.toString()
.toUpperCase(),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20.0,
),
),
),
Form(
key: _scoreForm,
child: Column(
children: [
scoreFields(_snapshot!.data[index]),
SizedBox(
height: 10.0,
),
ElevatedButton(
onPressed: () {
if (!_scoreForm.currentState!.validate())
return;
_scoreForm.currentState!.save();
setState(() {
response = "New Value";
});
//close bottomsheet
Navigator.pop(context);
},
child: Text("Save Score"),
),
],
),
),
],
),
),
);
},
);
},
),
title: Text(
_snapshot!.data[index]['name'].toString().toUpperCase(),
style: TextStyle(
fontWeight: FontWeight.w400,
),
),
subtitle: Text(
response,
),
onTap: () {},
);
}

You may wrap your ListTile with ValueListenableBuilder like below:
ValueNotifier<bool> newData = ValueNotifier(false);
ValueListenableBuilder<bool>(
valueListenable: newData,
builder: (context, value, child) {
return ListTile(
trailing: IconButton(
icon: Icon(Icons.add), //... rest of your code
and instead of calling
setState(() {
response = "New Value";
});
call below without setState
response = "New Value";
newData.value = !newData.value;
so now the state of the ListTile will be updated and no need to setState for the complete listview.

To update the data in Listtile(title and subtitle) you need to use Stream and Streambuilder which will update the data based on stream change from your database.

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

Problem with getting access to index of a map in flutter

I have a map<string, dynamic> ,and want to show it's components in cards by ListView.Builder, but the problem is by getting access to this map's indexes..
When running the app, it return "null" in the card!!
I have tried many solutions that I saw in StackOverFlow for a similar issues, but without a result.
Here where I identified my map:
var _item;
List listCount = [];
Map<String, dynamic> records = {};
String name;
And here where I give the var _item it's value:
MyCard(
colour: Colors.lightBlueAccent,
maker: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
StreamBuilder<int>(
stream: _stopWatchTimer2.rawTime,
initialData: 0,
builder: (context, snap) {
final value = snap.data;
final displayTime = StopWatchTimer.getDisplayTime(
value,
hours: _isHours2);
_item = displayTime;
return Padding(
padding: EdgeInsets.all(5.0),
child: Text(displayTime,
style: TextStyle(
fontSize: 30.0, color: Colors.white)),
);
},
),
],
),
),
And here in the "Save" button I give the 1st parameter of the map and assign in name variable:
createAlertDialog(buildContext, context) {
TextEditingController controller;
return showDialog(
context: context,
// barrierDismissible: false,
builder: (context) {
return AlertDialog(
title: Text(
'Type record name',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18.0),
),
content: TextField(
controller: controller,
onChanged: (value) {
name = value;
}),
actions: [
MaterialButton(
elevation: 5.0,
child: Text('Save'),
onPressed: () {
listCount.add(_item);
print(_item);
records[name] = _item;
print(records);
Navigator.pop(context);
},
),
MaterialButton(
elevation: 5.0,
child: Text('Cancel'),
onPressed: () {
Navigator.pop(context);
},
),
],
);
},
);
}
Finally here where I tried to show it in a card by listview.builder:
Container(
color: Colors.white,
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: records.length,
itemBuilder: (context, index) {
return MyCard(
colour: Colors.cyanAccent,
maker: Container(
width: 250.0,
height: 75.0,
child: Text(
'${records[index]}',
style: TextStyle(fontSize: 25.0),
textAlign: TextAlign.center,
),
),
);
},
),
),
I think the problem with the 2nd parameter of itemBuilder which it (index) because I replaced this keyword with another one randomly, and get the same result when running my app "Null".
This screenShot explain the problem:
You can get map entries as List as such:
final recordsEntries = records.entries.toList()
Then, you have a List of MapEntry and are able to access key and value associated for each item of the collection.
recordsEntries[index].key
recordsEntries[index].value

TextField value is not updated after user's input

Just 3 weeks into flutter-dart programming so I'm still a rookie.
I'm implementing a user Settings screen where the user's information is displayed.
The user can modify their information such as their first/last name and address.
I want the user's current name to be displayed as an initial value and as soon as the user modifies the field, I want to keep the change visible inside the TextField until they press the 'Update' button (see animation below). However, whenever the user changes, for example their first name, the initial value is shown again and their changes are lost (see animation below).
My TextField code for first name (last name and address are implemented similarly):
TextField(
onChanged: (text) => {},
textAlign: TextAlign.center,
controller: _firstNameController..text = userRep.firstName,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp('[a-zA-Z]'))
],
onSubmitted: (text) {
setState(() {
_firstNameController.text = text;
});
},
style: GoogleFonts.lato(
fontSize: 16.0
)
)
and the controller is defined at the beginning of the class:
final TextEditingController _firstNameController = TextEditingController();
currently under testing so I use a user mocking with defaulted values.
here is the app's current behavior:
any ideas, please?
edit: after #AndreaCostanzo1 's answer, I'm adding more info and code portion about my work:
The TextField in question is inside the build method of
class _UserSettingsScreenState extends State<UserSettingsScreen>:
class _UserSettingsScreenState extends State<UserSettingsScreen> {
final GlobalKey<ScaffoldState> _scaffoldKeyUserScreenSet = new GlobalKey<ScaffoldState>();
final TextEditingController _firstNameController = TextEditingController();
final TextEditingController _lastNameController = TextEditingController();
final TextEditingController _addressController = TextEditingController();
final TextEditingController _creditCardController = TextEditingController();
#override
Widget build(BuildContext context) {
return Material(
color: Colors.lightGreen,
child: Consumer<UserRepository>(
builder:(context, userRep, _) {
return Scaffold(
resizeToAvoidBottomInset: false,
resizeToAvoidBottomPadding: false,
backgroundColor: Colors.lightGreen[600],
key: _scaffoldKeyUserScreenSet,
appBar: AppBar(
backgroundColor: Colors.lightGreen[900],
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: null //TODO: implement navigation drawer
),
title: Text("Settings"),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(height: 20,),
CircularProfileAvatar(
userRep.avatarURL ??
'https://www.flaticon.com/svg/static/icons/svg/848/848043.svg',
borderColor: Colors.red,
radius: MediaQuery.of(context).size.height * 0.1,
initialsText: Text(
"Press to change",
textAlign: TextAlign.center,
style: GoogleFonts.lato()
),
onTap: () {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (BuildContext context) {
return Container(
height: 117,
child: Column(
textDirection: TextDirection.ltr,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ListTile(
tileColor: Colors.white,
leading: Icon(
Icons.photo_camera,
color: Colors.red,
),
title: Text("Take a new photo",
style: GoogleFonts.lato(),
),
onTap: () async {
PickedFile photo = await ImagePicker()
.getImage(source: ImageSource.camera);
if (null == photo) {
Scaffold.of(context).showSnackBar(
SnackBar(content:
Text("No image selected",
style: GoogleFonts.notoSans(fontSize: 18.0),
),
behavior: SnackBarBehavior.floating,
)
);
} else {
setState(() {
userRep.avatarURL = photo.path;
});
}
},
),
ListTile(
tileColor: Colors.white,
leading: Icon(
Icons.photo_size_select_actual_rounded,
color: Colors.red,
),
title: Text("Select from gallery",
style: GoogleFonts.lato(),
),
onTap: () async {
PickedFile photo = await ImagePicker()
.getImage(source: ImageSource.gallery);
if (null == photo) {
Scaffold.of(context).showSnackBar(
SnackBar(content:
Text("No image selected",
style: GoogleFonts.notoSans(fontSize: 18.0),
),
behavior: SnackBarBehavior.floating,
)
);
} else {
setState(() {
userRep.avatarURL = photo.path;
});
}
},
),
],
),
);
}
); //showModalBottomSheet
},
),
SizedBox(height: 30,),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: Container(
width: MediaQuery.of(context).size.width * 0.25,
height: MediaQuery.of(context).size.height * 0.1,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: SizedBox(
height: 200.0,
width: 100,
child: Text('First name',
style: GoogleFonts.montserrat(
fontSize: 16.0
),
textAlign: TextAlign.center,
),
),
),
Expanded(
flex: 3,
child: SizedBox(
height: 200.0,
width: MediaQuery.of(context).size.width * 0.5 - 10,
child: TextField(
onChanged: (text) => {},
textAlign: TextAlign.center,
controller: _firstNameController..text = userRep.firstName,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp('[a-zA-Z]'))
],
onSubmitted: (text) {
setState(() {
_firstNameController.text = text;
});
},
style: GoogleFonts.lato(
fontSize: 16.0
)
),
),
),
],
),
),
),
and the UserRepository mock looks like this:
thank you everybody in advance!
After you call setState the widget tree is rebuilt. Since you gave us just a smaller fragment of code, I can't tell for sure if this is the portion of code that generates this issue.
controller: _firstNameController..text = userRep.firstName,
However, from the video I can tell you that, after calling submit (when the widget tree is rebuilt) you are setting back the value to its original state.
If you want to give an initial state to the textfield, do this:
initState(){
super.initState();
_firstNameController=TextEditingController();
_firstNameController.text = userRep.firstName,
}
And in the text field just use
controller: _firstNameController,
Also, remember to dismiss the controller when the widget is disposed:
dispose(){
_firstNameController.dispose();
}

How do i make a scrollable list like google tasks ui in Flutter?

I'm stuck with making a scrollable list like Google Task app when you reach end of the list if any task is completed it shown in another list with custom header as you can see here, I'm using sliver
Widget showTaskList() {
final todos = Hive.box('todos');
return ValueListenableBuilder(
valueListenable: Hive.box('todos').listenable(),
builder: (context, todoData, _) {
int dataLen = todos.length;
return CustomScrollView(
slivers: <Widget>[
SliverAppBar(
floating: true,
expandedHeight: 100,
flexibleSpace: Container(
padding: EdgeInsets.only(
left: MediaQuery.of(context).size.width / 10,
top: MediaQuery.of(context).size.height / 17),
height: 100,
color: Colors.white,
child: Text(
'My Task',
style: TextStyle(fontSize: 30.0, fontWeight: FontWeight.w600),
),
),
),
SliverList(
delegate:
SliverChildBuilderDelegate((BuildContext context, int index) {
final todoData = todos.getAt(index);
Map todoJson = jsonDecode(todoData);
final data = Todo.fromJson(todoJson);
return MaterialButton(
padding: EdgeInsets.zero,
onPressed: () {},
child: Container(
color: Colors.white,
child: ListTile(
leading: IconButton(
icon: data.done
? Icon(
Icons.done,
color: Colors.red,
)
: Icon(
Icons.done,
),
onPressed: () {
final todoData = Todo(
details: data.details,
title: data.title,
done: data.done ? false : true);
updataTodo(todoData, index);
}),
title: Text(
data.title,
style: TextStyle(
decoration: data.done
? TextDecoration.lineThrough
: TextDecoration.none),
),
subtitle: Text(data.details),
trailing: IconButton(
icon: Icon(Icons.delete_forever),
onPressed: () {
todos.deleteAt(index);
}),
),
),
);
}, childCount: dataLen),
),
],
);
});
}
ShowTaskList is called on
Scaffold(
body: SafeArea(
child: Column(children: <Widget>[
Expanded(
child: showTaskList()
),
]),
),
I tried OffStageSliver to make an widget disappear if no complete todo is present but that did not work and also can not use any other widget on CustomScrollView because that conflict with viewport because it only accept slivers widget.
Here what i have achieved so far
You can try use ScrollController put it on CustomScrollView and listen to it's controller in initState like this :
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
// If it reach end do something here...
}
});
}
I suggest you make bool variable to show your widget, initialize it with false and then after it reach end of controller call setState and make your variable true, which you can't call setState in initState so you have to make another function to make it work like this:
reachEnd() {
setState(() {
end = true;
});
}
Put that function in initState. And make condition based on your bool variabel in your widget
if(end) _yourWidget()
Just like that. I hope you can understand and hopefully this is working the way you want.

A dismissed Dismissible widget is still part of the tree. Getting this error repeatedly after removing item from list

I have set UniquiId to the key of Dissmissible Widget. Adding some items after that I'm some dismissing an item. When I am going to the next page I'm getting an error (A dismissed Dismissible widget is still part of the tree.) even after removing the item from the List using Provider in the dismissed callback.
#override
Widget build(BuildContext context) {
return Dismissible(
key: UniqueKey(),
direction: DismissDirection.endToStart,
confirmDismiss: (direction) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Are you sure?'),
content: Text(
'Dou you want to remove the item from the cart?',
),
actions: [
FlatButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text('No'),
),
FlatButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text('Yes'),
),
],
),
);
},
onDismissed: (direction) {
Provider.of<Cart>(context, listen: false)
.removeItem(productId);
},
background: Container(
color: Theme.of(context).errorColor,
child: Icon(
Icons.delete,
color: Colors.white,
size: 40,
),
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 20),
margin: EdgeInsets.symmetric(
horizontal: 15,
vertical: 4,
),
),
child: Container(
child: Padding(
padding: EdgeInsets.all(8),
child: ListTile(
leading: CircleAvatar(
child: Padding(
padding: EdgeInsets.all(5),
child: FittedBox(
child: Text('\₹${(price * quantity)}'),
),
),
),
title: Text(title),
subtitle: Text(laundryName.toUpperCase()),
trailing: Text('$quantity x'),
),
),
),
);
}
}
Please help me out in this tried a lot still getting the same error. Thanks in advance.
Make sure the items are getting removed from the root List. If you are using provider, then get the updated data by keeping listView.builder widget under Consumer. So that when you dismiss an item it will be updated.

Categories

Resources