ListView displays items twice - android

I am trying to build a listview in flutter with the following :
The expected functionality is the listview should display 1 item at a time.
class SimpleContentScreen extends StatefulWidget {
#override
_SimpleContentScreenState createState() => _SimpleContentScreenState();
}
class _SimpleContentScreenState extends BaseState<SimpleContentScreen> {
List<SimpleContent> simpleContentList;
List<SimpleContent> displayList = List();
int _currentIndex = 0;
#override
Widget build(BuildContext context) {
simpleContentList = getOOFirstContent();
displayList.add(simpleContentList[_currentIndex]);
return Scaffold(
appBar: buildAppBar("Introduction"),
body: _buildListView(),
floatingActionButton: _buildFab(),
);
}
FloatingActionButton _buildFab() => FloatingActionButton(
onPressed: () {
if( _currentIndex < simpleContentList.length - 1 ) {
setState(() {
_currentIndex = _currentIndex + 1;
displayList.add(simpleContentList[_currentIndex]);
});
}
},
child: Icon(Icons.navigate_next),
foregroundColor: Colors.white,
backgroundColor: Colors.blueGrey,
);
ListView _buildListView() => ListView.builder(
key: Key("_simple_content_list"),
itemCount: displayList.length,
itemBuilder: (context, position) {
return _buildItemView( displayList[position] );
}
);
_buildItemView(SimpleContent displayList) => Container(
padding: const EdgeInsets.all(12),
margin: EdgeInsets.fromLTRB(0, 8, 32, 8),
decoration: BoxDecoration(color: Colors.blueAccent),
child : new Text(
displayList.contentString,
style: buildTextSimpleContent(20))
);
}
Upon press of FAB - it's adding the items twice. Why is this? I have solved it by clearing the displayList and adding all items from 0 to the current index.
I tried setting key to the listview, but that didn't solve it.
Any help or insight appreciated.

setState calls the build method of the Widget to build
So this is what's happening
onPressed method is called when FAB is clicked
_currentIndex = _currentIndex + 1;
displayList.add(simpleContentList[_currentIndex]);
This adds a new item
But then build method is again called
So you again add the element in the list in build method displayList.add(simpleContentList[_currentIndex]);
Solution 1
Remove
simpleContentList = getOOFirstContent();
displayList.add(simpleContentList[_currentIndex]);
from build and add it to initState
Solution 2
delete
displayList.add(simpleContentList[_currentIndex]);
from setState so that the element is added only once
For more details on StateFul Widget Lifecycle method refer here

Related

flutter: how to make a listview update without pressing a button?

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(),
),
);

Flutter changing Icon onTap in my animated List

I'm trying to change my icon after I tap on my List Item. I already tried different things: I tried the onTap method but the icon just does not want to change. I'm very new to flutter and I would love to find some help for my problem :). Here is my code.
I already searched for solutions but I didn't got it working in my project
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'To-Do List',
theme: ThemeData(
primaryColor: Colors.white,
brightness: Brightness.dark,
),
home: Scaffold(
appBar: AppBar(title: Text('To-Do List'),
backgroundColor: Colors.amber,
),
body: BodyLayout(),
),
);
}
}
class BodyLayout extends StatefulWidget {
#override
BodyLayoutState createState() {
return new BodyLayoutState();
}
}
class BodyLayoutState extends State<BodyLayout> {
// The GlobalKey keeps track of the visible state of the list items
// while they are being animated.
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
// backing data
List<String> _data = [];
final _isdone = Set<String>();
// bool selected = false;
List<bool> selected = new List<bool>();
Icon notdone = Icon(Icons.check_box_outline_blank);
Icon done = Icon(Icons.check_box);
TextEditingController todoController = TextEditingController();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
SizedBox(
height: 445,
child: AnimatedList(
// Give the Animated list the global key
key: _listKey,
initialItemCount: _data.length,
// Similar to ListView itemBuilder, but AnimatedList has
// an additional animation parameter.
itemBuilder: (context, index, animation) {
// Breaking the row widget out as a method so that we can
// share it with the _removeSingleItem() method.
return _buildItem(_data[index], animation);
},
),
),
TextField(
controller: todoController,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'To-Do'
),
),
RaisedButton(
child: Text('Insert item', style: TextStyle(fontSize: 20)),
onPressed: () {
_insertSingleItem();
},
),
RaisedButton(
child: Text('Remove item', style: TextStyle(fontSize: 20)),
onPressed: () {
_removeSingleItem();
},
)
],
);
}
// This is the animated row with the Card.
Widget _buildItem(String item, Animation animation) {
final isdone = _isdone.contains(item);
selected.add(false);
return SizeTransition(
sizeFactor: animation,
child: Card(
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
trailing: Icon(
isdone ? Icons.check_box: Icons.check_box_outline_blank
),
onTap: (){
setState(() {
});
},
),
),
);
}
void _insertSingleItem() {
int insertIndex = 0;
setState(() {
_data.insert(0, todoController.text);
});
// Add the item to the data list.
// Add the item visually to the AnimatedList.
_listKey.currentState.insertItem(insertIndex);
}
void _removeSingleItem() {
int removeIndex = 0;
// Remove item from data list but keep copy to give to the animation.
String removedItem = _data.removeAt(removeIndex);
// This builder is just for showing the row while it is still
// animating away. The item is already gone from the data list.
AnimatedListRemovedItemBuilder builder = (context, animation) {
return _buildItem(removedItem, animation);
};
// Remove the item visually from the AnimatedList.
_listKey.currentState.removeItem(removeIndex, builder);
}
}```
You have already mentioned the icons above. You simply need to use them instead of declaring new ones again.
// This is the animated row with the Card.
Widget _buildItem(String item, Animation animation) {
final isdone = _isdone.contains(item);
selected.add(false);
return SizeTransition(
sizeFactor: animation,
child: Card(
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
trailing: isdone ? done: notdone, // use the icon variables you have already defined
onTap: (){
setState(() {
// add the item to _isdone set if it is not added and remove it if it is added when tapped on the list item
if(isdone) {
_isdone.remove(item);
} else {
_isdone.add(item);
}
});
},
),
),
);
}
In this code, I have added the item and removed the item in setSate() in the onTap(), so that whenever you tap the list item, _isdone Set gets updated and the build() is reloaded. Which makes your layout and data update itself every time you tap on the list item.

Flutter listview within listview not scrolling

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.

Flutter size button

I'm making a FlatButton for sizes so the user is going to select his own size.
how can I make the button border goes bold when the user presses the button?
-the buttons are created by ListView.builder so I can't set local variables for them.
you can create a variable which hold the button number who's border you want to set bolder and on click you can change value of that variable.
following example clear your idea.
import 'package:flutter/material.dart';
class TextFieldInput extends StatefulWidget {
#override
_TextFieldInputState createState() => _TextFieldInputState();
}
class _TextFieldInputState extends State<TextFieldInput> {
final List<int> list = [1,2,3,4,5,6,7,8,9,0];
int number = -1;
#override
Widget build(BuildContext context) {
return Center(
child: Container(
child: ListView.builder(
itemCount: (list.length).ceil(),
itemBuilder: (context, int index){
return new FlatButton(
key: Key(index.toString()),
child: new Text(list[index].toString()),
shape: Border.all(
width: number==index ? 5.0 : 3.0
),
onPressed: (){
setState(() {
number = index;
});
}
);
}
)
),
);
}
}

How to detect scroll position of ListView in Flutter

I'm using ListView widget to show items as a list. In a window three, items viewing must the middle item place in the middle.
So how can I detect position of ListView when scrolling stop?
How to detect ListView Scrolling stopped?
I used NotificationListener that is a widget that listens for notifications bubbling up the tree. Then use ScrollEndNotification, which indicates that scrolling has stopped.
For scroll position I used _scrollController that type is ScrollController.
NotificationListener(
child: ListView(
controller: _scrollController,
children: ...
),
onNotification: (t) {
if (t is ScrollEndNotification) {
print(_scrollController.position.pixels);
}
//How many pixels scrolled from pervious frame
print(t.scrollDelta);
//List scroll position
print(t.metrics.pixels);
},
),
majidfathi69's answer is good, but you don't need to add a controller to the list:
(Change ScrollUpdateNotification to ScrollEndNotification when you only want to be notified when scroll ends.)
NotificationListener<ScrollUpdateNotification>(
child: ListView(
children: ...
),
onNotification: (notification) {
//How many pixels scrolled from pervious frame
print(notification.scrollDelta);
//List scroll position
print(notification.metrics.pixels);
},
),
You can also achieve this functionality with the following steps
import 'package:flutter/material.dart';
class YourPage extends StatefulWidget {
YourPage({Key key}) : super(key: key);
#override
_YourPageState createState() => _YourPageState();
}
class _YourPageState extends State<YourPage> {
ScrollController _scrollController;
double _scrollPosition;
_scrollListener() {
setState(() {
_scrollPosition = _scrollController.position.pixels;
});
}
#override
void initState() {
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text('Position $_scrollPosition pixels'),
),
body: Container(
child: ListView.builder(
controller: _scrollController,
itemCount: 200,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.mood),
title: Text('Item: $index'),
);
},
),
),
);
}
}
The NotificationListener now accepts a type argument which makes the code shorter :)
NotificationListener<ScrollEndNotification>(
child: ListView(
controller: _scrollController,
children: ...
),
onNotification: (notification) {
print(_scrollController.position.pixels);
// Return true to cancel the notification bubbling. Return false (or null) to
// allow the notification to continue to be dispatched to further ancestors.
return true;
},
),
If you want to detect the scroll position of your ListView, you can simply use this;
Scrollable.of(context).position.pixels
In addition to #seddiq-sorush answer, you can compare the current position to _scrollController.position.maxScrollExtent and see if the list is at the bottom
https://coflutter.com/flutter-check-if-the-listview-reaches-the-top-or-the-bottom/ Source
If some want to Detect the bottom of a listview then use this way
NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
if (notification.metrics.atEdge) {
if (notification.metrics.pixels == 0) {
print('At top');
} else {
print('At bottom');
}
}
return true;
},
child: ListView.builder(itemBuilder: (BuildContext context) {
return YourItemWidget;
})
)
I would say You can easily detect Scroll Position by
#override
void initState() {
super.initState();
_scrollController = ScrollController();
_scrollController.addListener(() {
var _currectScrollPosition = _scrollController.position.pixels;//this is the line
});
}
but If you are going to call setState in addListener() ; It is okay, but this will cause to rebuild your entire build(context). This is not a good practice for Animations specially.
What I would recommand is to seprate your scrolling widget into a seprate StatefulWidget , Then get the sate of that widget by calling
_yourScrollableWidgetKey.currentState?.controller.addListener(() {
//code.......
setState(() {});
});
Note: Set a GlobalKey, and assign to your StatFulWidget.
final _yourScrollableWidgetKey = GlobalKey<_YourScrollAbleWidget>();
StackedPositionedAnimated(
key: _yourScrollableWidgetKey,
),

Categories

Resources