I am running into an issue trying to populate a SliverFixedExtentList with children from a JSON API response. I have a function _getMyListOfCustomJsonObjects() that returns a Future<'List<'CustomJsonObject>>. I am fairly new to flutter, but my understanding of SliverLists is that they should behave similarly to a ListView. I have been able to populate a ListView of these children using a FutureBuilder and ListView.builder. However, making the change to a SliverList is throwing me an error.
Below is an example of my code
CustomScrollView(
slivers: [
SliverAppBar(),
FutureBuilder(
future: _getMyListOfCustomJsonObjects,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data != null)
return SliverFixedExtentList(
itemExtent: 150,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return
Container(
color: Colors.red,
child: Text('${snapshot.data[index].Name}'));
},
childCount: snapshot.data.length),
);
else {
return Padding(
padding: EdgeInsets.only(top:50),
//alignment: Alignment.center,
child: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
new CircularProgressIndicator(
value: null,
strokeWidth: 4,
backgroundColor: Color(0xFFe5ac3b),
semanticsLabel: "Loading",
valueColor:
new AlwaysStoppedAnimation<Color>(Color(0xFF12336c)),
),
],
),
);
}
},
),
],
),
I think I might be close, but this is my first time trying to use Slivers so I'm not sure if I'm missing a key component to make this all work. Below are the errors I get when I try to run this code.
flutter: Another exception was thrown: 'package:flutter/src/widgets/framework.dart': Failed assertion: line 4345 pos 14: 'owner._debugCurrentBuildTarget == this': is not true.
flutter: Another exception was thrown: Duplicate GlobalKeys detected in widget tree.
Any suggestions to achieve populating a SliverList with Future<List<CustomJSONObject>>?
Related
I made a DataTable in flutter and it has about 10 columns and that's more than what the screen can handle so I wrapped the DataTable inside a SingleChildScrollView widget and this solution worked fine until the rows inside the DataTable grew up and exceeded the screen height and I couldn't scroll down because of the scroll direction is set to horizontal in the SingleChildScrollView widget!
And as a temporary solution, I wrapped the DataTable inside a fittedBox inside the SingleChildScrollView but this doesn't solve the whole problem and still, there is some responsibility issues.
What I need is a way to make the DataTable scrollable in both directions horizontally and vertically.
This is my code
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(16),
child: Card(
child: Container(
padding: const EdgeInsets.all(16),
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: FutureBuilder(
future: getCategories(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
} else {
return FittedBox(
child: DataTable(
headingRowColor: MaterialStateProperty.resolveWith(
(states) => Colors.grey.shade900),
columns: _columns,
rows: _rows,
),
);
}
},
),
),
),
),
);
}
The easiest solution I know, is to wrap the SingleChildScrollView in a second SingleChildScrollView.
https://stackoverflow.com/a/57539405/1151983
But there are also other approaches:
https://stackoverflow.com/a/63546017/1151983
Can't figure it out how to solve this problem. So, here is the simplest code which represents my problem:
Scaffold(
body: Stack(
children: <Widget>[
...
Scrollbar(
child: ListView.builder(
primary: false,
shrinkWrap: true,
itemCount: _mapBloc?.mapData?.companies?.count ?? 0,
itemBuilder: (context, index) {
final company = _mapBloc?.mapData?.companies?.data[index];
return InkWell(
child: Hero(
tag: company.id,
child: Card(
child: Container(
height: 50,
width: double.infinity,
),
),
),
onTap: () {
Navigator.of(context)
.pushNamed('/company', arguments: company)
.then(
(results) {
if (results is PopWithResults) {
PopWithResults popResult = results;
}
},
);
},
);
},
),
)
],
),
);
And stack trace:
The following assertion was thrown during a scheduler callback:
There are multiple heroes that share the same tag within a subtree.
Within each subtree for which heroes are to be animated (i.e. a PageRoute subtree), each Hero must
have a unique non-null tag.
...
The items count in ListView changes with every database request. If the size is up to 5 widgets in ListView, then clicking works correctly, as soon as the list expands to, for example, 8 elements, I get the error written above. With what it can be connected? I tried to use unique Hero tags, but this does not solve the problem.
I need some advice and hope you can help me. If you need more information, please, write the comment.
Thanks for your attention!
I'm aiming for a page that looks like this -
ListView
[Profile _ Image] {Swiper}
[SizedBox]
[Profile Detail-1 ]{Text}
[Profile Detail-2 ]{Text}
[Profile Detail-3 ]{Text}
[Profile Detail-N ] {Text}
I looked at the Flutter cookbook example of MultiList
The cookbook expects all items in the listview to implement the same class. What if this is not possible.
I have tried using index of ListViewBuilder to return Widget based on index.
Is that the right approach? Shall I be doing something completely different - like siglechildScrollView?
Thanks!
Edit1-
Current Code that I'm using -
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) {
this._feedBloc.loadMore();
}
return false;
},
child: ListView.builder(
padding: EdgeInsets.only(bottom: 72),
itemCount: this._postItems.length + 1,
itemBuilder: (context, index) {
if (this._postItems.length == index) {
if (this._isLoadingMore) {
return Container(
margin: EdgeInsets.all(4.0),
height: 36,
width: 36,
child: Center(
child: CircularProgressIndicator(),
),
);
} else {
return Container();
}
}
if(index==0){
return WdgtProfileImage();}
else if(index==1){
return SizedBox(2.0);}
return WdgtUserPost(
model: this._postItems[index],
onPostClick: onPostClick,
);
//return postItemWidget(
// postItem: this._postItems[index], onClick: onPostClick);
}),
);
You can use a CustomScrollView instead of the normal Listview.builder. The CustomScrollView takes in a list of slivers to which you can pass/use a SliverList to build a list.
CustomScrollView(
slivers: <Widget>[
//A sliver widget that renders a normal box widget
SliverToBoxAdapter(
child: WdgtProfileImage(),
),
//A sliver list
SliverList(
//With SliverChildBuilderDelegate the items are constructed lazily
//just like in Listview.builder
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return WdgtUserPost(
model: _postItems[index],
onPostClick: onPostClick,
);
},
childCount: _postItems.length,
),
),
if (_isLoadingMore)
//your loading widget shown at the bootom of the list
SliverToBoxAdapter(
child: Container(
margin: EdgeInsets.all(4.0),
height: 36,
width: 36,
child: Center(
child: CircularProgressIndicator(),
),
),
),
],
)
Additional links to docs:
SliverList
SliverChildBuilderDelegate
SliverToBoxAdapter
I want to add SingleChildScrollView in my page but i have PageView.
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new IneatAppBarPollWidget()
.getAppBar('Blackblox', 'assets/img/logo_ineat.png', cand),
body: new Center(
child: SingleChildScrollView(
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new Expanded(
child: new Container(
child: new FutureBuilder<List<PollQuestionsDto>>(
future: _future,
// ignore: missing_return
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('Failed connection API');
case ConnectionState.waiting:
return new Text('Wait...');
case ConnectionState.done:
if (snapshot.hasData) {
if (snapshot.data != null) {
return PageView.builder(
scrollDirection: Axis.horizontal,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, index) {
print(this.responses);
return PollListViewItem(
entitled:
snapshot.data[index].entitled,
answers:
snapshot.data[index].answers,
currentAnswer:
this.responses[index],
onSelect: (Answer answer) {
this.responses[index] = answer;
},
);
},
);
}
}
break;
case ConnectionState.active:
}
}),
),
),
new ProgressBar(),
new Container(
margin: EdgeInsets.only(right: 10.0),
child: new RaisedButton.icon(
onPressed: () async {
//await Poll().submitPoll(answers: answers);
setState(() {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) =>
PollPage()));
});
},
label: Text('Suivant'),
icon: Icon(Icons.navigate_next),
),
alignment: Alignment.bottomRight,
),
],
),
),
),
);
}
}
When I added this, I have this error :
RenderFlex children have non-zero flex but incoming height constraints are unbounded.
When a column is in a parent that does not provide a finite height constraint, for example if it is in a vertical scrollable, it will try to shrink-wrap its children along the vertical axis. Setting a flex on a child (e.g. using Expanded) indicates that the child is to expand to fill the remaining space in the vertical direction.
These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child cannot simultaneously expand to fit its parent.
Consider setting mainAxisSize to MainAxisSize.min and using FlexFit.loose fits for the flexible children (using Flexible rather than Expanded). This will allow the flexible children to size themselves to less than the infinite remaining space they would otherwise be forced to take, and then will cause the RenderFlex to shrink-wrap the children rather than expanding to fit the maximum constraints provided by the parent.
RenderBox was not laid out: RenderFlex#4c442 relayoutBoundary=up12 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
'package:flutter/src/rendering/box.dart':
Failed assertion: line 1681 pos 12: 'hasSize'
I recreated your case with a sample data. Below are the issues that I fixed:
Column used inside SingleChildScrollView expands to fill vertical space by default if we don't set the mainAxisSize for it. So here, you will need to add mainAxisSize: MainAxisSize.min, which tells column to take only the minimum space.
The children to be displayed inside Column were wrapped in Expanded which again takes up default space for its child. Here, instead of Expanded, use Flexible which takes the space as applicable to its child.
The Container used to display the data from future builder again expands to the entire screen if not provided a height. So just provide a custom height to the Container.
Working code below:
body: Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Flexible(
child: Container(
height: 200,
child: FutureBuilder<Post>(
future: post,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('Failed connection API');
case ConnectionState.waiting:
return new Text('Wait...');
case ConnectionState.done:
if (snapshot.hasData) {
if (snapshot.data != null) {
return PageView.builder(
scrollDirection: Axis.horizontal,
itemCount: 5,
itemBuilder: (BuildContext context, index) {
return Card(
child: Text('test'),
);
},
);
}
}
break;
case ConnectionState.active:
}
// By default, show a loading spinner.
return CircularProgressIndicator();
},
),
)
),
Container(
margin: EdgeInsets.only(right: 10.0),
child: new RaisedButton.icon(
onPressed: () {},
label: Text('Suivant'),
icon: Icon(Icons.navigate_next),
),
alignment: Alignment.bottomRight,
)
],
),
)
),
Based on the sample data I used, I was able to see below result and was able to scroll horizontally to see the data properly.
AlertCardWidget is a widget that i wrote. I return it in itemBuilder but nothing shown. Here is my code:
Flexible(
child: Padding(
child: SingleChildScrollView(
child: ListView.builder(
itemCount: state.data.length,
itemBuilder: (BuildContext context, int index) {
state.data["datas"].map<Widget>((f) {
return AlertCardWidget(
positionId: "${f["8020074"]}",
shipowner: "${f["8020076"]}",
customer: "${f["8020170"]}",
salesRepresenter: "${f["8020176"]}",
operationRepresenter: "${f["8020177"]}",
textContentFontColor:
AppTheme(Theme.of(context).brightness)
.cardFontBackgroundColor,
textfont: Colors.redAccent,
);
}).toList();
},
),
),
),
),
No error to show.
I have items that why I use ListView. The problom of using Listview instead ListView.builder is taking "Vertical viewport was given unbounded height error". The problem has solved when writing Listview like child of Expanded widget. Here is my code:
Expanded(
child: Padding(
padding: const EdgeInsets.all(4.0),
child: ListView(
children: state.data["datas"].map<Widget>((f) => AlertCardWidget(positionId: "${f["8020074"]}",
shipowner: "${f["8020076"]}",
customer: "${f["8020170"]}",
salesRepresenter: "${f["8020176"]}",
operationRepresenter: "${f["8020177"]}",
textContentFontColor: AppTheme(Theme.of(context).brightness).cardFontBackgroundColor,
textfont: Colors.redAccent,)).toList(),
),
),
),
Maybe a silly question, but why are you mapping a list into a ListView.builder?
Have you tried using the index for each iteration instead?
Because what I understand from that code is that every ["datas"] item you've got will generate the whole list for as many times as state.data.length has.
Maybe try this out:
Flexible(
child: Padding(
child: SingleChildScrollView(
child: ListView.builder(
itemCount: state.data.length,
itemBuilder: (BuildContext context, int index) {
return AlertCardWidget(
positionId: state.data[index]["datas"]["8020074"],
shipowner: state.data[index]["datas"]["8020076"],
customer: state.data[index]["datas"]["8020170"],
salesRepresenter: state.data[index]["datas"]["8020176"],
operationRepresenter: state.data[index]["datas"]["8020177"],
textContentFontColor:
AppTheme(Theme.of(context).brightness)
.cardFontBackgroundColor,
textfont: Colors.redAccent,
);
},
),
),
),
),
If that doesn't work, would you mind showing us which data are you trying to retrieve?
Your itemBuilder function does not return a value.
Edit: It should return a single Widget for every entry in your list.
Something like this should work.
Also, Padding Widget is missing the padding property.
Flexible(
child: Padding(
child: SingleChildScrollView(
child: ListView.builder(
itemCount: state.data.length,
itemBuilder: (BuildContext context, int index) {
final f = state.data[index];
return AlertCardWidget(
positionId: "${f["8020074"]}",
shipowner: "${f["8020076"]}",
customer: "${f["8020170"]}",
salesRepresenter: "${f["8020176"]}",
operationRepresenter: "${f["8020177"]}",
textContentFontColor:
AppTheme(Theme.of(context).brightness)
.cardFontBackgroundColor,
textfont: Colors.redAccent,
);
},
),
),
),
),