How to append new data in ListView Flutter - flutter

I Wanna ask for my problem in flutter. How to append new data from another screen (has pop) to main screen (contain list view data from json) this is my code from main screen.
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => FormUser()))
.then((VALUE) {
/////////
Here for adding new data from VALUE into list view
/////
});
});
This my listview
Widget _buildListView(List<User> users) {
return ListView.separated(
separatorBuilder: (BuildContext context, int i) =>
Divider(color: Colors.grey[400]),
itemCount: users.length,
itemBuilder: (context, index) {
User user = users[index];
return ListTile(
leading: Icon(Icons.perm_identity),
title: Text(capitalize(user.fullName)),
subtitle: Text(user.gender),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text(user.grade.toUpperCase()),
Text(user.phone)
],
),
);
},
);
}
}
This is return data from add screen
User{id: null, fullname: Camomi, gender: pria, grade: sma, phone: 082232, email: ade#gmul.com}
This is my class model of typedata
class User {
int id;
String fullName, gender, grade, phone, email;
User(
{this.id,
this.fullName,
this.gender,
this.grade,
this.phone,
this.email});
}

You add an element to your list of users. Then you call setState so your widget's build function gets called and builds the new view with the list of users that now contains the new element.
And please do not use .then() in your onPressed method. Make the method async and use await or if you really want to use .then(), at least return the Future it gives you.

You will need to do extra steps:
Wait for the result when calling push() method:
final result = await Navigator.push(...);
In your FormUser widget, when you finish entering data and press on Done or similar, you should return the created user:
// userData will be assigned to result on step 1 above. Then you add that result (user) to your list
Navigator.pop(context, userData);
You can find a very good tutorial here.

First of all make sure that Your class extends StatefulWidget.
I assume your list of users looks like this (or it's empty like [] ) :
List<User> users = [user1, user2, user3];
In Your FloatingActionButton in onPressed you should have this method (copy the content to onPressed or write a method like this somewhere in your code).
The method must be async to await the user
void _addUser() async {
final user = await Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FormUser()));
if (user != null) {
setState(() => users.add(user));
}
}
This code should work fine, enjoy :)

Related

how to let consumer listen to multiple parameters in flutter?

I need to let the consumer widget listen to multiple variables depending on a boolean value.
this is the model class
class Lawyer{
Data? data;
double? distance = 0;
Lawyer({this.data, this.distance});
factory Lawyer.fromJson(Map<String, dynamic> json) =>
Lawyer(data: Data.fromJson(json['listing_data']));
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
class Data{
String? title;
String? email;
String? phone;
Location? location;
List<String>? logo;
List<String>? cover;
Data({this.title, this.email, this.phone, this.logo, this.cover, this.location});
factory Data.fromJson(Map<String, dynamic> json) {
var logo = json['_job_logo'];
var cover = json['_job_cover'];
var long = json['geolocation_long'];
var lat = json['geolocation_lat'];
return Data(title: json['_job_tagline'], email: json['_job_email'],
location: Location(latitude: json['geolocation_lat'], longitude: json['geolocation_long']),
phone: json['_job_phone'], logo: List<String>.from(logo),
cover: List<String>.from(cover)
);
}
}
and this is the view model notifier
class LawyerAPIServices extends ChangeNotifier{
final url = "https://dalilvision.com/wp-json/wp/v2/job_listing";
List<Lawyer> lawyersList = [];
List<Lawyer> staticLawyersList = [];
Future<List<Lawyer>> fetchLawyers() async{
final response = await get(Uri.parse(url.toString()));
if(response.statusCode == 200){
var dynamicLawyersList = jsonDecode(response.body);
print('$dynamicLawyersList');
lawyersList = List<Lawyer>.from(dynamicLawyersList.map((x) => Lawyer.fromJson(x)));
staticLawyersList = lawyersList;
lawyersList.forEach((element) {print('all lawyers: ${element.data!.location}');});
notifyListeners();
return lawyersList;
}
else{
notifyListeners();
throw Exception(response.statusCode);
}
}
Future<List<Lawyer>> getFullListOfLawyers() async {
notifyListeners();
print('fulll list: ${staticLawyersList.length}');
return staticLawyersList;
}
}
and finally this is the consumer widget
Consumer<LawyerAPIServices>(
builder: (context, value, child) => FutureBuilder(
future: _list,
builder: (BuildContext context, AsyncSnapshot<List<Lawyer>> snapshot) {
if (snapshot.hasData){
return ListView.separated(
physics: const NeverScrollableScrollPhysics(),
scrollDirection: Axis.vertical,
shrinkWrap: true,
separatorBuilder: (context, index) => const Divider(color: Colors.transparent),
itemCount: value.lawyersList.length,
itemBuilder: (context, index) {
return InkWell(
child: LawyerWidget(
title: snapshot.data![index].data!.title!,
email: snapshot.data![index].data!.email!,
phone: snapshot.data![index].data!.phone!,
logo: snapshot.data![index].data!.logo![0],
cover: snapshot.data![index].data!.cover![0]
),
);
}
}
);
}
else if(snapshot.hasError){
return Center(
child: Text(snapshot.error.toString())
);
}
else {
return const CircularProgressIndicator(
strokeWidth: 2,
);
}
},
),
)
In the notifier class there are two lists, the staticLawyerList is initialized only once when getting the list from a network call and then used as a backup list, and the lawyersList is the one that will be manipulated.
what I have done until now is to get the initial value of lawyersList by a network call, then somehow the staticLawyersList values are always equal to lawyersList, even if I made any change or manipulate the lawyersList these changes will automatically reflect on the staticLawyersList which is really weird.
now what I want to achieve exactly is to apply a condition to update the UI with the appropriate list depending on this condition.
if(setByPosition == false){
//update UI with `staticLawyersList`
}
else {
//update UI with `lawyersList`
}
update!!!!!!!!
here's how I update my consumer
CheckboxListTile(
activeColor: Colors.black,
value: isChecked,
onChanged: (value) async {
saveSharedPreferences(value: value!);
if(value == true) {
Provider.of<LawyerAPIServices>(context, listen: false).sortLawyersList(
devicePosition: widget.position, lawyersList: widget.list);
}
else{
Provider.of<LawyerAPIServices>(context, listen: false).getFullListOfLawyers();// the list returned by this function don't applied to the consumer
}
setState(() {
isChecked = value;
Navigator.pop(context);
});
},
title: const Text('Filter by distance'),
),
A few things to consider:
When you do this "staticLawyersList = lawyersList" you actually have two "pointers" to the same list. It works that way for lists, sets, classes, etc.. only basic types as int, double, string are really copied.
You can use this instead: "staticLawyersList = List.from(lawyersList);"
It doesn't seem you need the ChangeNotifier in your LawyerAPIServices. You could create an instance of LawyerAPIServices in the widget you need it and call fetchLawyers. Do it in the initState of a StatefullWidget if you don't want the list to be rebuilt multiple times. In your build method use a FutureBuilder to read the Future and decide what to show in the UI.
class _MyWidget extends State<MyWidget> {
late final LawyerAPIServices lawyerApi;
// Create this variable to avoid calling fetchLawers many times
late final Future<List<Lawyer>> lawyersList;
#override
void initState() {
super.initState();
// Instantiate your API
lawyerApi = LawyerAPIServices();
// This will be called only once, when this Widget is created
lawyersList = lawyerApi.fetchLawyers();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Lawyer>>(
future: lawyersList,
builder: ((context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
if (setByPosition) {
//update UI with `lawyersList`
return _listView(snapshot.data!);
} else {
//update UI with `staticLawyersList`
// Since the Future state is Complete you can be sure that
// the staticLawyersList variable in your API was already set
return _listView(lawyerApi.staticLawyersList);
}
case ConnectionState.none:
return const Text('Error');
default:
return const CircularProgressIndicator.adaptive();
}
}),
);
}
Widget _listView(List<Lawyer> lawyersList) {
return ListView.separated(
physics: const NeverScrollableScrollPhysics(),
scrollDirection: Axis.vertical,
shrinkWrap: true,
separatorBuilder: (context, index) =>
const Divider(color: Colors.transparent),
itemCount: lawyersList.length,
itemBuilder: (context, index) {
return InkWell(
child: LawyerWidget(
title: lawyersList[index].data!.title!,
email: lawyersList[index].data!.email!,
phone: lawyersList[index].data!.phone!,
logo: lawyersList[index].data!.logo![0],
cover: lawyersList[index].data!.cover![0]),
);
});
}
}
If for any reason you need to share the same LawyerAPIServices across multiple widgets, you could instantiate it on the top of your tree and send it down using Provider or as a parameter.
The method getFullListOfLawyers doesn't need to return a Future, since staticLawyersList is a List (not a Future). You could get this list directly using "LawyerAPIServices.staticLawyersList" or maybe something like this could make sense:
Future<List> getFullListOfLawyers() async {
if(staticLawyersList.isEmpty) {
await fetchLawyers();
}
print('fulll list: ${staticLawyersList.length}');
return Future.value(staticLawyersList);
}
as #Saichi-Okuma said that to copy the content of a list you should use staticLawyersList = List.from(lawyersList) because in dart and most of the java compiler programming languages when you use staticLawyersList = lawyersList this means that you are referring to the lawyersList by the staticLawyersList.
then I manipulate the lawyersList as I want with help of staticLawyersList
lawyersList.clear();
lawyersList.addAll(staticLawyersList);
But when I did so, the consumer didn't apply the changes based on the staticLawyersList although the logcat shows that the staticLawyersList length is 10 which is what I want (full list without filtration).
the conclusion of my problem can be listed in two points:
1- the consumer is listening to only one list lawyersList and I think it still exists.
2- the pointer problem as #Saichi-Okuma mentioned.
here are the full code changes
void getFullListOfLawyers() {
lawyersList.clear(); // to make sure that the list is clean from older operations
lawyersList.addAll(staticLawyersList);// the trick
notifyListeners();
}
Future<List<Lawyer>> fetchLawyers() async{
final response = await get(Uri.parse(url.toString()));
if(response.statusCode == 200){
var dynamicLawyersList = jsonDecode(response.body);
print('$dynamicLawyersList');
lawyersList = List<Lawyer>.from(dynamicLawyersList.map((x) => Lawyer.fromJson(x)));
staticLawyersList = List.from(lawyersList);// use this statment instead of staticLawyersList = lawyersList
lawyersList.forEach((element) {print('all lawyers: ${element.data!.location}');});
notifyListeners();
return lawyersList;
}
else{
notifyListeners();
throw Exception(response.statusCode);
}
}
The Consumer Widget gets rebuild every time you call notify notifyListeners, regardless the state of any lists.
Maybe you are not accessing the Instance of the API being consumed. Make sure you are using the 2nd parameter of the Consumer builder.
Consumer<LawyerAPIServices>(builder: (context, lawyerAPI, child) =>
FutureBuilder(
future: lawyerAPI.fetchLawyers(),
builder: ((context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
if (setByPosition) {
//update UI with `lawyersList`
return _listView(snapshot.data!);
} else {
//update UI with `staticLawyersList`
// Since the Future state is Complete you can be sure that
// the staticLawyersList variable in your API was already set
return _listView(lawyerAPI.staticLawyersList);
}
case ConnectionState.none:
return const Text('Error');
default:
return const CircularProgressIndicator.adaptive();
}
}),
I don't think you need the code below for this particular need. It'd override your lawyersList and notify to all listeners even though nothing really changed. Just access your staticLawyersList directly, since it was populated when you called fetchLawyers.
void getFullListOfLawyers() {
lawyersList.clear(); // to make sure that the list is clean from older operations
lawyersList.addAll(staticLawyersList);// the trick
notifyListeners();
}

How to call a method when navigating back from another screen in flutter?

I have a list view of items. For each item in the list, there is an edit option, when on click that im navigating from ListScreen to the EditScreen. Edit Screen has a static update button but the editing view is changed according to the item selected to be edited From the ListScreen.
Now when I update the items in Edit screen, and press update button I can navigate to ListScreen back but I need the list view to rebuild on that. The function to build the list is given inside of a FutureBuilder in ListScreen it self. In the initial navigate to the ListScreen im calling that method in initState().
Is there a way that I can manage this?
ListScreen
//provider
late AddItemProvider? _updateItemProvider;
#override
void initState() {
_dataFuture = _getAddedData().whenComplete(() => refresh());
super.initState();
}
void _gotoEditAccess(BuildContext ctx, String title) async {
var nav =
Navigator.pushNamed(ctx, suggestionUpdateUIRoute, arguments: title);
// of(context)
if (nav != null && nav == true) {
await _dataFuture; //_getAddedData().whenComplete(() => refresh());
}
}
//in the body I have this list view
//added item is the list view item which holds Function parameters for onEdit and onDelete
//used as onTap: () => widget.onEdit!(),
ListView.builder(
shrinkWrap: true,
itemCount: _listOfItems.length,
itemBuilder: (context, index) {
return AddedItem(
icon: _listOfItems[index].icon,
title: _listOfItems[index].title,
content: _listOfItems[index].content,
onEdit: () async {
_gotoEditAccess(
context, _listOfItems[index].title!);
},
onDelete: () {
_deleteItem(_listOfItems[index].title!);
},
);
},
),
edit screen have a base view with common update button and the body is being replaced with a actual editing view matching what needs to be edited.
EditScreen
#override
Widget build(BuildContext context) {
_updateItemProvider = Provider.of<AddItemProvider>(context, listen: false);
...
//rest of the body for matched editing screen...
//inside the base view of edit screen
_goBack() {
Navigator.pop(context, true);}
#override
Widget build(BuildContext context) {
_updateItemProvider = Provider.of<AddItemProvider>(context, listen: false);
_submitBtn() => Consumer<AddItemProvider>(
builder: (_, AddItemProvider updateItemProvider, __) {
_updateItemProvider = updateItemProvider;
return ButtonWidget(
btnColor: CustomColors.green600,
borderColor: CustomColors.green600,
textColor: CustomColors.mWhite,
text: "Update",
eButtonType: eButtonType.bText,
eButtonState: _submitBtnState,
onPressed: () async {
await saveSelectedList(updateItemProvider.updateItems!);
_updateItemProvider!.removeAll();
_goBack();
},
);
},
);
You need to write this code were you want to navigate
onTap:() async{
var test = await Navigator.of(context).push(
MaterialPageRoute(builder: (context) =>EditProfile()));
if(test != null || test == true){
// perform your function
}
}
You need to pass any content when navigate back from edit screen
Navigator.pop(context, true);

Getx How to refresh list by using Obx

I'm working with ReorderableSliverList but I have no idea how to observe the list based on my data dynamically.
Screen 1
ReorderableSliverList(
delegate: ReorderableSliverChildBuilderDelegate(
(BuildContext context, int index) {
final data = controller.products[index];
return ItemView(data);
},
childCount: controller.products.length),
onReorder: _onReorder,
)
At screen2 will have a add button to call controller insert new data into list
controller
var products = List<Product>.empty().obs;
void add(String name) {
if (name != '') {
final date = DateTime.now().toIso8601String();
ProductProvider().postProduct(name, date).then((response) {
final data = Product(
id: response["name"],
name: name,
createdAt: date,
);
products.add(data);
Get.back();
});
} else {
dialogError("Semua input harus terisi");
}
}
The code above need to click Hot reload in order to show data in screen 1 if data has changed from screen 2.
I'm trying to use Obx to make it refresh automatically but the result it still the same.
Code
ReorderableSliverList(
delegate: ReorderableSliverChildBuilderDelegate(
(BuildContext context, int index) {
final data = controller.products[index];
return Obx(
() => controller.products.isEmpty
? Center(
child: Text("BELUM ADA DATA"),
)
: ItemView(data)
);
}, childCount: controller.products.length),
onReorder: _onReorder,
)
You need to wrap the whole ReorderableSliverList with Obx like this:
Obx(()=>ReorderableSliverList(
...
...
));

Flutter: How to delete item from listview?

DBHelper dbHelper = DBHelper();
List<Map<String, dynamic>> lists;
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Map<String, dynamic>>>(
future: dbHelper.selectMemo(userkey, 1),
builder: (context, snapshot){
if(snapshot.hasData){
if(snapshot.data.length != 0){
lists = List<Map<String, dynamic>>.from(snapshot.data);
return ListView.separated(
separatorBuilder: (context, index){
return Divider(
thickness: 0,
);
},
itemCount: lists.length,
itemBuilder: (context, index){
return ListTile(
title: Text(lists[index]["memo"]),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: (){
setState(() {
lists = List.from(lists)..removeAt(index);
});
},
),
);
},
);
}
}
},
);
}
This is my code. My lists come from sqlflite. And I want to delete my item from Listview. But this code doesn't work. I don't know where I made the mistake.
This behavior is normal. If you print some logs in the build statement, you will find that every time you click the delete button (setState), Widget will build again.
In addition, lists are re-assigned to DB data after each build
lists = List<Map<String, dynamic>>.from(snapshot.data);
So, it looks like the delete operation is not working.
This phenomenon if you've seen Flutter setState part of the source code will be well understood.
In setState, the callback is performed first, and then mark dirty
void setState(VoidCallback fn) {
final dynamic result = fn() as dynamic;
_element.markNeedsBuild();
}
So, there are two ways to solve this problem:
(1) Do not directly change the value of lists, but when the delete button is pressed, to delete the data in the database, so that when Widget build again, the data taken out of the database is correct.
(2) Add a flag to judge whether the data is initialized, and then add a judgment before assigning lists. If the data is initialized, assignment operation will not be carried out
I hope it worked for you. ^-^

How to solve the index in list view is not correct

I am using list view + pagination in Flutter to show my response data.
I face the problem when I selected the first list item the details such as id is the other list item id. I have used print() to check the id, it always shows the wrong id.
I want to show the details about my image, but it gives me wrong id. So it will show other image.
How can I solve the problem?
There is no need to define id and title as variables of the State object.
You can pass them as a parameter to the selectedItem method instead, the problem is you always set the id and title to the last item built so it will always navigate with its details instead of the actually selected item.
class _HomePage State extends State<HomePage > {
GlobalKey<PaginatorState> paginatorGlobalKey = GlobalKey();
#override
Widget build(BuildContext context) {
body: return Paginator.listView(
key: paginatorGlobalKey,
pageLoadFuture: sendPagesDataRequest,
pageItemsGetter: listItemsGetterPages,
listItemBuilder: listItemBuilder,
loadingWidgetBuilder: loadingWidgetMaker,
errorWidgetBuilder: errorWidgetMaker,
emptyListWidgetBuilder: emptyListWidgetMaker,
totalItemsGetter: totalPagesGetter,
pageErrorChecker: pageErrorChecker,
scrollPhysics: BouncingScrollPhysics(),
);
}
Future<PagesData> sendPagesDataRequest(int page) async {
String url = Uri.encodeFull("https://API_URL?page=$page");
http.Response response = await http.get(url);
PagesData pagesData = pagesDataFromJson(response.body);
return pagesData;
List<dynamic> listItemsGetterPages(PagesData pagesData) {
List<Project> list = [];
pagesData.data.forEach((value) {
list.add(value);
});
return list;
}
Widget listItemBuilder(dynamic item, int index) {
return InkWell(
onTap: () => selectedItem(item,context), // pass the item iteself in the selectedItem function
child: new CachedNetworkImage(
imageUrl:= item.image,
placeholder: (context, url) => new CircularProgressIndicator(),
errorWidget: (context, url, error) => new Icon(Icons.error),
fit:BoxFit.fill,
),
);
}
Widget loadingWidgetMaker() {
return Container(
alignment: Alignment.center,
height: 160.0,
child: CircularProgressIndicator(),
);
}
void selectedItem(dynamic item,BuildContext context) { // add a parameter for item
Navigator.of(context).pushNamed(
DetailsPage.routeName,
arguments: {
'id': item.id, // Move the item.id here
'title': item.title // Move the item.title here
});
}
}