I am working with APIs and using a delete method to delete an item with a unique id from a list. The delete method is working but I need to reload the page everytime I want to see the results. I tried to add a setState() function inside a button and call the delete method from there but it is not working. I am not getting any errors however.
Delete method:
Future <void> deleteData(todo) async {
var urlToUpdate = Uri.parse('https://todoapp-api.apps.k8s.gu.se/todos/${todo.id}?key=${testKey}');
try {
await http.delete(urlToUpdate, headers: {"Content-Type": "application/json"}, body: jsonEncode({
"id": todo.id,
"title": todo.title,
"done": todo.done
}));
} catch (err) {
print(err);
}
}
setState method:
child: IconButton(
onPressed: () {
setState(() {
var deleteTodo = TodoItem(id: id, title: '', done: false);
deleteData(deleteTodo);
});
},
I can't provide the whole code because it is too large but the delete method comes right after :
class _TodoListState extends State {
and before initState and Widget build.
My TodoItemsList works like this:
Future fetchPosts() async {
try {
await getKey();
final response = await HTTP.get(Uri.parse('${url}${todos}${testKey}'));
final jsonData = jsonDecode(response.body);
setState(() {
TodoItemsList = jsonData;
});
print(jsonData);
} catch (err) {
print('Error');
}
}
This empty list is just above the Widget build
List TodoItemsList = [];
This widget is inside by body property:
Widget getBody() {
return ListView.builder(
itemCount: TodoItemsList.length,
itemBuilder: (context, index) {
return getCard(TodoItemsList[index]);
});
}
you should wait until the deleteData finished.
After that, remove the local TodoItem from the list by yourself.
child: IconButton(
onPressed: () async {
var deleteTodo = TodoItem(id: id, title: '', done: false);
await deleteData(deleteTodo);
setState(() {
=> remove TodoItem from the local list =<
});
}
Because network request usually takes times. You should display something like CircularProgressIndicator when deleteData is running. But that's another story.
setState method is used to reflect any change of data over some widget, if you need to remove a element from a list need has that element linked to a widget
example:
If you has
listOfMovie = ['Avatar, Avengers', 'Dune', 'Hulk'];
ListView.builder(
itemCount: listOfMovie.length,
itemBuilder: (_, index) => Text(listOfMovie[index],
));
then
child: IconButton(
onPressed: () {
setState(() {
listOfMovie = ['Avatar, Avengers'];
});
},
If you notice listOfMovie is linked to ListView widget
Related
My problem is with Futures, because they should be obtained before build() method executed, as the documentation states:
The future must be obtained earlier, because if the future is created
at the same time as the FutureBuilder, then every time the
FutureBuilder's parent is rebuilt, the asynchronous task will be
restarted.
I know that Futures should be called in initstate() function before the build method executed, but my case is different.
I want to get data from api as a Future, but the request I am sending to the api needs some parameters that user should select inside the screen's build() method.
And I don't know what the parameter of the request will be until user selects in build() method, and I have to call the api in the build() method and use FutureBuilder there, but that makes FutureBuilder to get constantly called, and I don't want that.
basically, I don't want to call FutureBuilder indefinetely, and I can't put my Future inside initState() because the Future needs some parameters that user later selects when the screen is shown inside build() method.
inside the build method:
FutureBuilder<List<LatLng>>(
builder: (context, snapshot) {
if (snapshot.hasData) {
return PolylineLayer(
polylines: [
Polyline(
points: snapshot.data!,
strokeWidth: 4,
color: Colors.purple),
],
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
} else {
return Container();
}
},
future: Provider.of<NavigationProvider>(context)
.getNavigationPoints(pointToGoTo!),
),
now if you look at the code, at the final lines, I am sending the parameter pointToGoTo to the function which calls the backend.
simply, I want to get rid of calling api and getting data back as a Future inside build method, I want to do it in initState or somewhere else that prevents the build methods calling backend indefinitely.
is there any way to fix this problem?
Thanks in advance.
Firstly, create future state variable and a nullable params and use it with conditional if while using FutureBuilder.
I will recommend checking Fixing a common FutureBuilder and StreamBuilder problem
Now you can follow this example. It is missing progressBar on API recall, StreamBuilder might be better option in cases like this.
class Foo extends StatefulWidget {
const Foo({super.key});
#override
State<Foo> createState() => _FooState();
}
class _FooState extends State<Foo> {
int? params;
Future<int> fetch(int? data) async {
await Future.delayed(Duration(seconds: 1));
return (params ?? 0) * 2;
}
late Future<int> future = fetch(params);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
DropdownButton<int?>(
value: params,
items: List.generate(
12,
(index) => DropdownMenuItem(
value: index,
child: Text("$index"),
)).toList(),
onChanged: (value) {
future =
fetch(params); // this will only call api with update data
setState(() {
params = value;
});
},
),
if (params != null)
FutureBuilder<int>(
future: future,
builder: (context, snapshot) {
if (snapshot.hasData) return Text("${snapshot.data}");
return CircularProgressIndicator();
},
)
],
),
);
}
}
class Testing extends StatefulWidget {
const Testing({super.key});
#override
State<Testing> createState() => _TestingState();
}
class _TestingState extends State<Testing> {
bool isFetched = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer<SomethingProvider>(
builder: (context, prov, child) {
if (!isFetched) {
prov.getData("a", "b");
Future.delayed(const Duration(milliseconds: 200), () {
isFetched = true;
});
}
if (prov.newData.isNotEmpty) {
return Column(
// make widget tree from here
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}
class SomethingProvider extends ChangeNotifier {
List newData = [];
Future getData(param1, param2) async {
newData = ["testingdata"];
}
}
void _myMatches() {
if (SignUp.userUid != null) {
FirebaseFirestore.instance
.collection("posts")
.where(
'owner id',
isEqualTo: SignUp.userUid,
)
.where("User Id", isNotEqualTo: [])
.where("rental status", isEqualTo: false)
.get()
.then((value) {
value.docs.forEach((result) {
print(result.data());
});
});
} else {
FirebaseFirestore.instance
.collection("posts")
.where(
'owner id',
isEqualTo: Loginpage.userUid,
)
.where("User Id", isNotEqualTo: [])
.where("rental status", isEqualTo: false)
.get()
.then((value) {
value.docs.forEach((result) {
print(result.data());
});
});
}
}
}
Hi, I am using flutter and firestore to write a program. My function that reads the data is as follows:(mentioned above)
which i call when a specific button is pressed. This leads to the data being read from firestore to be printed on the console. What do I do to display it on my emulator. How do I wrap this data in a widget so I can display it on the screen on whichever page i want?
The key is to use a FutureBuilder to render UI after you get the data, and show loading before that. Then inside builder of FutureBuilder, use ListView and ListTile(or anything you like) to render list items.
A minimum example might looks like this:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MaterialApp(
home: App(),
));
}
class App extends StatelessWidget {
Future<QuerySnapshot<Map<String, dynamic>>> getData() {
// Handle any data retrieval logic you want
return FirebaseFirestore.instance.collection('posts').get();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<QuerySnapshot<Map<String, dynamic>>>(
// plug your future snapshot here
future: getData(),
builder: (context, snapshot) {
// Check loading
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
// Check error
final queryData = snapshot.data;
if (snapshot.hasError || queryData == null) {
return Icon(Icons.error);
}
return Scaffold(
// Use ListView.builder to render only visible items
body: ListView.builder(
itemCount: queryData.docs.length,
itemBuilder: (context, index) {
// Get data inside docs
final docData = queryData.docs[index].data();
return ListTile(
title: docData['title'],
subtitle: docData['subtitle'],
);
},
),
);
});
}
}
I have a function that is supposed to fetch me a list of Restaurants objects from firestore based on location.
the function does its job perfectly when i first run the app but after using the app from another device and updating resturants data in firestore documents, i somehow get duplicates of the restaurants list items.
here is the code for the function that fetch the the restaurants objects list:
Future<void> fetchRestaurantsList() async {
try {
Position position = await Geolocator().getCurrentPosition(
desiredAccuracy:
Platform.isIOS ? LocationAccuracy.lowest : LocationAccuracy.high);
final dbRestaurant = firestore
.collection('testing')
.document('users')
.collection('restaurant');
geo.collection(collectionRef: dbRestaurant)
.within(
center: GeoFirePoint(
position.latitude,
position.longitude
),
radius: 45.0,
field: 'resturantLocation')
.listen((event) {
restaurantList.clear();
await event.forEach((element){
final distance = Distance.getDistanceFromLatLonInKm( // calculating distance for each restaurant
position.latitude,
position.longitude,
element.data['location']['geopoint'].latitude,
element.data['location']['geopoint'].longitude)
restaurantList.add(Restaurant(
id: element.documentID,
logo: element.data['logo'],
name: element.data['name'],
distance: distance ,
));
notifyListeners();
});
});
} catch (e) {
print(e.toString());
}
} finally {
notifyListeners();
}
}
and this is the page that contains the list: (its under a parent widget which contains other tabs)
class RestruntsListTab extends StatefulWidget {
final MainModel model;
RestruntsListTab({#required this.model});
#override
State<StatefulWidget> createState() {
return _RestruntsListTabState();
}
}
class _RestruntsListTabState extends State<RestruntsListTab>
#override
void initState() {
widget.model.fetchRestaurantsList();
widget.model.checkLocationService().then((isActive) {
if (isActive) {
} else {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
language.enableLcation,
style: TextStyle(
fontFamily: 'eff', fontSize: 18, fontWeight: FontWeight.bold),
),
backgroundColor: Colors.grey,
));
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<MainModel>(
builder: (context, child, model) {
return ListView.builder(
itemCount:model.restaurantList.length,
itemBuilder: (context,index) {
return Row(
children: <Widget>[
Text(model.restaurantList[index].name),
Text(model.restaurantList[index].distance),
],
)
}
);
})
}
}
this is a simplified code for demonstration but the actual code is pretty similar.
if you have encountered similar issues kindly share your experience.
thank you all.
check that fetchRestaurantsList() method is not called on widget build
or it is in StreamBuilder method...it's because .listen((event) { this method it is like a stream so you have to use flag like bool variable to run the code inside it
if(mybool==false){// the other code goes.... setStste({mybool=true;})}
in this way it only excute the code once
There might be something wrong with the code, but I don't see it. What you can try doing is wrapping the content of forEach with
if(restaurantList.where((item) => item.id == element.documentID).isEmpty){
}
That should filter out duplicates.
I have a ListView.builder that reads all items from a list and shows them to the user. The builder is child of a RefreshIndicator, which adds an item when updating. However, the item is only shown in the listView when I rebuild the entire widget. Why is that and how can I change it so that I see the item immediately after updating? Even after repeated refreshing, no new items appear...
Thx for any help
class User {
static String id = 'id-001';
static List<Item> list = [];
}
class DatabaseService {
final CollectionReference users = Firestore.instance.collection('users');
Future fetchAndAddToUserList()async{
var doc = await users.document(User.id).get();
User.list.add(doc.data['list']);
}
}
Future<Null> _refresh() async {
await DatabaseService().fetchAndAddToUserList();
setState(() {
sortList();
});
return;
}
RefreshIndicator(
onRefresh: _refresh,
key: _refreshIndicatorKey,
child: Container(
child: ListView.builder(
itemBuilder: (ctx, index) {
return ListTile(
item: User.list[index],
);
},
itemCount: User.list.length,
),
),
),
FutureBuilder to the rescue.
See https://flutter.dev/docs/cookbook/networking/fetch-data
I am able to fetch and print the data in json format in console and I am able to display the entire body of data in list format in the flutter app. But I can't figure out how to display only a specific key and its value in list. Its a list of maps. I have removed the consumer key and secret from this code.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main(){
runApp(MaterialApp(
home: CustomHome(),
));
}
class CustomHome extends StatefulWidget {
#override
_CustomHomeState createState() => _CustomHomeState();
}
class _CustomHomeState extends State<CustomHome> {
List data;
Future<String> getData() async{
var response = await http.get('https://jbaat.com/wp-json/wc/v3/products/?consumer_key=&consumer_secret=');
setState(() {
var converted = jsonDecode(response.body);
data = converted;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
this.getData();
}
#override
Widget build(BuildContext context) {
print(data);
return Scaffold(
body: ListView.builder(
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, int index) {
return Container(
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text('$data'),
),
),
);
}),
);
}
}
Below is the response
[{id: 493, name: Bas 5 Min White Half Sleeve T-Shirt, slug: bas-5-min-white-half-sleeve-t-shirt, permalink: https://www.jbaat.com/product/bas-5-min-white-half-sleeve-t-shirt/, date_created: 2019-12-14T23:39:08, date_created_gmt: 2019-12-14T18:09:08, date_modified: 2019-12-14T23:48:01, date_modified_gmt: 2019-12-14T18:18:01, type: variable, status: publish, featured: false, catalog_visibility: visible, description: , short_description: , sku: , price: 500.00, regular_price: , sale_price: , date_on_sale_from: null, date_on_sale_from_gmt: null, date_on_sale_to: null, date_on_sale_to_gmt: null, price_html: <span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">₹</span>500.00</span>, on_sale: false, purchasable: true, total_sales: 0, virtual: false, downloadable: false, downloads: [], download_limit: -1, download_expiry: -1, external_url: , button_text: , tax_status: taxable, tax_class: , manage_stock: false, stock_quantity: null, stock_status: instock, backorders: no,
Jbaat, I'd recommend creating a model for your response data and use the values accordingly from each item's instance. There are few online converters available which converts your json response to Dart models, here is one - https://javiercbk.github.io/json_to_dart/. Below is a quick example of what it would look like based on your response data,
class Items {
final List<Items> items;
Items({this.items});
factory Items.fromJson(List<dynamic> json) {
List<Items> itemList = json.map((i) => Items.fromJson(i)).toList();
return Items(
items: itemList
);
}
}
class Item {
final int id;
final String name;
......
Item({this.id, this.name,.....});
factory Item.fromJson(Map<String, dynamic> json) {
return Item(
id: json['id'],
name: json['name']
......
);
}
}
And then your getData() would be,
Future<Items> getData() async{
var response = await http.get('https://jbaat.com/wp-json/wc/v3/products/?consumer_key=&consumer_secret=');
return Items.fromJson(json.decode(response.body)).items;
}
You should now have Item list which can be used to get specific item info. You should also use FutureBuilder to call your getData() instead of calling it in initState to make sure data is available before building widgets like so,
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Scaffold(
body: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
Item item = snapshot.data[index]; //Your item
return Container(
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(item.name),
),
),
);
}),
);
} else {
return Center(child: CircularProgressIndicator());
}
});
}
Hope this helps. Good luck!
Change the type for data to
List<Map<String,dynamic>>
A possible implementation for your use case:
Map y;
var keytobesearched='name';
List<Map<String,dynamic>> x= [{'id': 493, 'name': 'Bas 5 Min White Half Sleeve T-Shirt', 'slug': 'bas-5-min-white-half-sleeve-t-shirt'}];
x.forEach((Map<String,dynamic> ele){
if(ele.containsKey(keytobesearched))
y=Map.from(ele);
// display/alter y to your liking
});
If you would want a complete plug and play Woocommerce Sdk that handles authentication, products, customer, shipping etc for you, you can use the Woo Commerce SDK library for flutter at https://pub.dev/packages/woocommerce
Woocommerce myWoocommerce = WooCommerce(baseUrl: yourBaseUrl, consumerKey: yourConsumerKey, consumerSecret: consumerSecret);
Then simply get your lists eg:
WooProduct products = await myWocommerce.getProducts(); // returns the ist of products.
for (var product in products){ print(product.name)};