I have a problem when I use a widget for showing a list of items by a future builder, I have a filter object and when I change something in filter screen, automatically get the change in list screen, but always show me the same data but the items list have a different items that the UI is showing.
This is my container:
body: Container(
color: Colors.white,
child: FutureBuilder<ResponseHome>(
future: Connection(context: context).filterPlaces(filter: _filter),
builder: (context, snapshot) {
if (snapshot.hasData) {
return _formatCategories(context, snapshot.data.news, snapshot.data.places);
} else if (snapshot.hasError) {
var message = Utils.parseError(context, snapshot.error);
return Center(
child: Text("$message"),
);
}
return Center(
child: CircularProgressIndicator(),
);
},
),
)
This is the function that i use for make the widget:
Widget _formatCategories(BuildContext context, List<Publication> publications, ResponsePlaces response) {
List<Category> categories = <Category>[];
if (response.hotels.isNotEmpty) {
categories.add(Category(title: "Hoteles", places: response.hotels));
}
if (response.restaurants.isNotEmpty) {
categories.add(Category(title: "Restaurantes", places: response.restaurants));
}
if (response.touristic.isNotEmpty) {
categories.add(Category(title: "Actividades", places: response.touristic));
}
for (var category in categories) {
print("Category ${category.title} ${category.places.length}");
}
return _containerLists(context, publications, categories);
}
Widget _containerLists(BuildContext context, List<Publication> publications, List<Category> categories) {
List<Widget> items = <Widget>[];
items.clear();
items.add(
Routes.newsSlider(publications)
);
for (var index in categories) {
items.add(
Routes.placeList(index)
);
}
return SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Container(
child: Column(
children: items,
),
),
);
}
This is the evidence if you see I pick another type of place, but always showing me the hotels.
And this is the evidence that I got a list with different data.
https://i.stack.imgur.com/lIwoN.png
It could happen in case you have the same size of your items List. If that's true, then your Element Tree couldn't recognize that Widgets in your Column has changed and you have to use UniqueKey for them. You can read more about it here, try to apply it and give a feedback please
Related
so I'm currently working on an application that has a listview on the first screen (implemented on main.dart).
The listview fetches it's data from internet (async).
The problem is that, the listview does not get updated when the data is changed.
(I can implement this functionality simply by designing a 'reload' button and pressing it every time I want the new data. But that's not what I want right now).
In other words, how can I update the listview automatically?
EDIT1: ADDING SOME CODE
code might be messy; see the description at the end.
class RssFeed extends StatelessWidget {
String title;
String pubDate;
RssFeed(this.title, this.pubDate);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Align(
alignment: Alignment.topRight,
child: Text(title),
),
Text(pubDate)
],
),
);
}
}
class FeedsList extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _FeedsListState();
}
}
class _FeedsListState extends State<FeedsList> {
List<Widget> list1 = new List<Widget>();
#override
void initState() {
super.initState();
ls();
}
Future ls() async {
list1.clear();
list.clear();
sites1.clear();
RSS_reader rss_reader = new RSS_reader();
for (var i in saver.list.items) {
sites1.add(
site(siteAdress: i.siteAdress, siteDescription: i.siteDescription));
}
var res = await rss_reader.Get_items(sites1);
for (var val in res) {
list.add(InkWell(
onTap: () => _launchURL(val.item.link),
child: Container(
height: 50,
color: Colors.amber[100],
child: Center(
child: new RssFeed(val.item.title, val.item.pubDate.toString()),
),
)));
}
print(list.length);
setState(() {
list1 = list;
});
}
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: list1.length,
itemBuilder: (BuildContext context, int i) {
return list1[i];
}));
}
}
DESCRIPTION:
As you can guess, this is a RSS reader.
So, I have a class RSSFeed; which makes one of the tiles of Listview.
then in the FeedsList class (stateful widget), I make the listview.
I have a class called RSS_reader and a method Get_items, which gets a bunch of sites as input and puts those sites' newest feeds in a list ('res' in the above code).
Then, I put the items in a list of 'Container's and then build the listview.
Then, in the main function, I create a container like below:
Container(
height: 500,
width: 580,
child: FeedsList(),
)
and there appears the problem; the FeedsList class does not get updated automatically. although if I put a button and navigate to FeedsList class through that button, the list is refreshed and OK.
Thanks for reading and help.
If you just want to fetch data once from your external source use a FutureBuilder, if you want to fetch data multiple times take a look to StreamBuilder. Both widgets will have the behavior you are looking for, with no refresh button.
Simple example of how to use a FutureBuilder:
Future<List<String>> _fetchData() {
return // fetch data from source
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _fetchData,
builder: (BuildContext context, AsyncSnapshot<List<String>> snapshot) {
if (snapshot.hasData && snapshot.data != null) {
// This widget will be built when data is fetched
const List<String> list = snapshot.data;
return ListView(
children: list.map(
(element) => ListTile(
title: Text(element),
),
).asList(),
);
} else {
// This widget will be built while you are waiting for your data to be fetched
return Container(
child: Center(
child: Text("Loading data..."),
),
);
}
},
);
}
You have to stream data and ListView will update automatically.
In the button that you say you can re call your ls() functions, your list should update on tap button
sample:
return Scaffold(
body: ListView.builder(
itemCount: list1.length,
itemBuilder: (BuildContext context, int i) {
return list1[i];
},
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.refresh),
onPressed: () => ls(),
),
);
As my first Flutter project I'm building an app for a newspaper. There are a number of news categories. For those categories, I have created a generic Widget, in the home screen that Widget will be shown multiple times for multiple categories in a Listview, those category Widgets have Listview in them too as I bring multiple news from those categories. The Widget class that has this generic Widget is Newsfeed.dart which will be given below.
This generic Widget is called from a another Widget class WidgetFactory.dart which actually calls API and builds the home screen by using the above mentioned generic Widget for categories. This class uses a Listview.builder which is inside FutureBuilder.
The problem is, when I open the app in the screen the news appears but I can't scroll, it stays fixed. I have checked if the API is actually bringing the news, in console I have printed the API response, all of the news are fetched but still I can't scroll.
the flow is main.dart -> WidgetFactory() -> Newsfeed()
WidgetFactory()
class WidgetFactory extends StatefulWidget {
#override
_WidgetFactoryState createState() => _WidgetFactoryState();
}
class _WidgetFactoryState extends State<WidgetFactory> {
List homeScreenCategories = [4, 14, 13, 23, 8015, 22];
Future<List> newsPostList;
List<List<NewsPost>> categoryNewsPostList;
#override
void initState() {
super.initState();
newsPostList = fetchNews();
}
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Container(
alignment: Alignment.center,
child: Container(
child: RefreshIndicator(
child: FutureBuilder(
future: newsPostList,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(snapshot.data == null) {
return Container(
child: CircularProgressIndicator()
);
} else {
return ListView.builder(
shrinkWrap: true,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return _getCategoryNews(snapshot, index);
},
);
}
},
),
onRefresh: () {
fetchNews();
}
),
),
);
}
Future<List> fetchNews() async {
String url = "url";
Response response = await Dio().get(url);
if(response.statusCode == 200) {
List newsPostList = [];
for(int i=0; i<response.data.length; i++) {
newsPostList.add(response.data[i]);
}
return newsPostList;
} else {
throw Exception("Failed to fetch category");
}
}
Widget _getCategoryNews(snapshot, int index) {
List<NewsPost> newsPostList = [];
for(var c in snapshot.data[index]['items']) {
NewsPost newsPost = NewsPost.getNewsPostFromAPI(c);
newsPostList.add(newsPost);
}
return Newsfeed(newsPostList, "National");
}
}
Newsfeed()
class Newsfeed extends StatefulWidget {
String categoryName;
List<NewsPost> newsPostList;
Newsfeed(this.newsPostList, this.categoryName);
#override
_NewsfeedState createState() => _NewsfeedState(this.newsPostList, this.categoryName);
}
class _NewsfeedState extends State<Newsfeed> {
final GlobalKey<ScaffoldState> _scaffoldKeyTwo = new GlobalKey<ScaffoldState>(debugLabel: '_MainScreenKey');
String categoryName;
_NewsfeedState(this.newsPostList, this.categoryName);
List<NewsPost> newsPostList;
var dio = new Dio();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Container(
alignment: Alignment.center,
child: ListView.builder(
shrinkWrap: true,
itemCount: newsPostList.length,
itemBuilder: (BuildContext context, int index) {
print(newsPostList[index]);
return _getNewsPostWidgets(index);
}
),
);
}
Widget _getNewsPostWidgets(int index) {
var newsPost = newsPostList[index];
if(index < 5) {
if(index == 0) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
ScaleTransitionRoute(
page: NewsPostDetails(newsPostList, index)
)
);
},
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(10, 0, 0, 0),
//constraints: BoxConstraints(minWidth: double.infinity, maxWidth: double.infinity),
constraints: BoxConstraints.expand(
width: double.infinity,
height: 40
),
color: const Color(0xFF2b4849),
child: Text(
this.categoryName,
style: TextStyle(
fontSize: 33,
color: Colors.white
),
),
),
BlockHeadline(newsPost)
],
)
);
}
else {
return GestureDetector(
onTap: () {
Navigator.push(
context,
ScaleTransitionRoute(
page: NewsPostDetails(newsPostList, index)
)
);
},
child: ListedNews(newsPost),
);
}
}
else {
return Container(
color: const Color(0xFF2b4849),
child: index == 5 ? FlatButton(
child: Text(
"See More",
style: TextStyle(
color: Colors.white
),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => NewsFeedForSpecificCategory(newsPostList)
)
);
},
) : Container(),
);
}
}
openNewsPostDetails(List<NewsPost> newsPostList, int index) {
Navigator.push(
context,
ScaleTransitionRoute(
page: NewsPostDetails(newsPostList, index)
)
);
}
}
What I have tried
I found some questions that relates to this problem a bit. Tried those.
I used shrinkwrap=true in my Listview.builder, but of no use.
Tried using Column inside SingleChildScrollView(), still did not work.
Inside the Listview.builder added physics as AlwaysScrollable(), also in vain.
As I'm new to flutter what I tried might seem dumb.
One more thing is, the news that shows up in the home screen, that takes me to the details page fine and there swiping left right takes me to other news also. It's the home screen that is causing trouble, not scrolling.
It would be great help if you could kindly give some clues.
I have found the solution to this problem. It was quite simple actually.
In the build() method of my _NewsfeedState class I have added ClampingScrollPhysics() as physics.
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Container(
alignment: Alignment.center,
child: ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: newsPostList.length,
itemBuilder: (BuildContext context, int index) {
print(newsPostList[index]);
return _getNewsPostWidgets(index);
}
),
);
}
It worked.
Putting ListView inside ListView is an anti pattern.
There are several solutions for this problem,
Solution 1:
Merge those two list of items into a single list of items and
display it as a single ListView.
Solution 2:
You can use SliverList for your use case. SliverList can wrap multiple SliverLists inside a single CustomScrollView.
This will help you.
child: ListView.builder(
physics: NeverScrollableScrollPhysics(),
Thanks.
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
Hello i have 2 screens in flutter the 1st page contains many JSON data like image,strings, etc to be pass in 2nd page how to to this? Thank in advance.
I have the my code below i stuck here for almost 4 hours now Can anybody help me.
//first page
Map data;
List userData;
Future getItems() async {
http.Response response = await http.get("MY_IP/olstore_serv/get_items");
data = json.decode(response.body);
setState(() {
userData = data["item"];
});
onTap: () {
Navigator.of(context, rootNavigator: true).push(
new CupertinoPageRoute(
builder: (context) {
return new ItemDetails(todo:userData);
},
),
);
},
/// second page
class ItemDetails extends StatelessWidget {
final List todo;
ItemDetails({this.todo});
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('i want to display item name here pls help me'),
),
child: Container(
child: ListView(
physics: BouncingScrollPhysics(),
children: <Widget>[
Text('here too'),
]
),
),
);
}
}
Please try below code for listview.
ListView.builder(
itemCount: todo.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return Text(todo[index].yourKey);
},
);
Searching is working but I have applied the search filter in itemBuilder whenever I search for something and it finds at position 5 for example than itemBuilder building view for those positions where items have not found.
I want the list of founded items or no record text single times.
Expanded(
child: ListView.builder(
itemCount: productList.length,
itemBuilder: (BuildContext context, int index) {
return filter == null || filter == "" ? productItem(productList[index][DatabaseHelper.columnProductName], height, productList[index][DatabaseHelper.columnPrice] )
: productList[index][DatabaseHelper.columnProductName].toLowerCase().contains(filter.toLowerCase())
? productItem(productList[index][DatabaseHelper.columnProductName], height, productList[index][DatabaseHelper.columnPrice] ) : new Container(child: Text('No record'),);
},
),
),
My Controller
#override
void initState() {
super.initState();
_query();
controller.addListener(() {
setState(() {
filter = controller.text;
});
});
}