ListView won't scroll in Flutter - android

I try to create a scrollable ListView, but for somes reasons it's not working.
My goal: create a ListView connected to Firebase. My ListView gather well datas, but scoll is impossible.
The body of my Scaffold:
body: const SingleChildScrollView(
child: RecipesInformations(),
),
My widget:
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _recipesStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Chargement recettes");
}
return ListView(
shrinkWrap: true,
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
List tags = List.from(document["tags"]);
return Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: const Color(0xFF011200)),
borderRadius: const BorderRadius.all(Radius.circular(8))),
margin: const EdgeInsets.only(
bottom: 32, left: 16, right: 16, top: 8),
padding: const EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
data["title"],
overflow: TextOverflow.fade,
maxLines: 1,
softWrap: false,
style: Theme.of(context).textTheme.headline1,
)
],
),
],
));
}).toList(),
);
},
);
}
Let me know if you need more informations.

Removing SingleChildScrollView might help.

Use ListView.builder to simplify things and remove unnecessary nested scrollviews. Something like this could work:
body: RecipesInformations(),
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _recipesStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
...
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
final document = snapshot.data!.docs[index];
return Container(
...
);
},
)
...

You'd need to use FutureBuilder instead of StreamBuilder.
From the docs, the StreamBuilder is a Widget that builds itself based on the latest snapshot of interaction with a Stream. So, only the last item is going to show up on the screen.
Instead, transform the stream to a List with the toList() method and use it on the FutureBuilder.future property like so:
...
return FutureBuilder<List<QuerySnapshot>>(
future: _recipesStream.toList(),
builder: (BuildContext context, snapshot) {
...
snapshot.data now is a List of QuerySnapshot. So, it's going to be a bit different to iterate over it:
...
return ListView(
shrinkWrap: true,
children: snapshot.data!.map((query) {
Map<String, dynamic> data = query.docs.first;
...
The result is going to be something like this:

In you ListViewconstructor, set primary: false since you are nesting it inside of a SingleChildScrollView.

Related

How to use refresh indicator inside a futurebuilder in flutter

I want to use refresh indication in flutter futurebuilder. I looked for the solutuion but but not worked in my case.
I have provided my code below.
I found a solution where they suggested to use the stack but that solutiion not worked either.
column(children:[
...
const Heading("New Workers !"),
FutureBuilder(
future: SupervisorServices.getWorkersList(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
var data = snapshot.data;
if (snapshot.hasData) {
return RefreshIndicator(
onRefresh: () {
return SupervisorServices.getWorkersList();
},
child: ListView.builder(
itemCount: data.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return Card(
child: ListTile(
leading: const SizedBox(
height: double.infinity,
child: Icon(
FeatherIcons.user,
),
),
//...
),
),
);
},
),
);
}
...//rest code
},
),
...
Try add async
onRefresh: () async{
return SupervisorServices.getWorkersList();
},

Two listview builders in a parent listview but not rendering. Only the visibles instead rendering all at once

#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
_firstListView(),
_secondListView(),
],
),
);
}
Main class that calls other listView builders
Widget _firstListView() {
return Container(
color: Colors.green,
child: ListView.builder(
itemCount: 200,
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemBuilder: (context, index) => RecCell(
index: index,
),
),
);
}
first listView builder
Widget _secondListView() {
return Container(
color: Colors.red,
child: ListView.builder(
itemCount: 200,
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemBuilder: (context, index) => ListTile(title: Text("Second $index")),
),
);
}
the second listView builder
RecCell is another statefull widget that prints in init method when the widget is build and it build 200 times on restart and never recreated on scrolling
I want them to reuse each time parent listview scrolls any help will appreciated..
Well, wrapping them in a ListView removes the lazy loading benefit of ListView.builder, because the outer ListView wants to load all children at once. I think the best way is the combine the data of the two ListView.builders into a single one. Although, wrapping them in a colored container would not be possible then, but maybe you get the desired result by wrapping the items in it. So something like:
Widget builds(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: 400,
shrinkWrap: true,
itemBuilder: (context, index) => index < 200
? Container(
color: Colors.green,
child: RecCell(
index: index,
),
)
: Container(
color: Colors.red,
child: ListTile(title: Text("Second ${index - 200}")))));
}
You could use NestedScrollView if you have nested listview in the body of the parent widget.
try this
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
Container(height: 300, child: _firstListView()),
Container(height: 300, child: _secondListView()),
],
),
);
}
-----------OR------------
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
children: <Widget>[
Container(height: 300, child: _firstListView()),
Container(height: 300, child: _secondListView()),
],
),
);
}
Use CustomScrollView instead ListViews for this case
CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => Container(),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => Container(),
),
),
],
);
because the ListView requires all available space and expands its own size as much as possible to understand how many child elements can be displayed
I did this and worked for me
Map<String, dynamic> d = {
0:..,listView widget,
1:[...list of widgets]
}
I am making a map containing all my widgets, and in the build method I am returning a listview with those widgets
return ListView(
children:[
d[0],
...d[1] // this will extract each widget from the list and insert it to list view
]
)
In this way my d[0] is horizontal listview builder and d1 are other widget that I am adding into parent list view directly now they all are rerendering when visible. Had to do some changing in my previous code
Final output:
output

How can I implement RefreshIndicator in Listview.Builder using Getx?

I want to implement RefreshIndicator in my Listview.builder but the problem is that when I place RefreshIndicator it's not working. And I search in Google the answer that I find is that place physic:
AlwaysScrollableScroll() in Listview.Builder and when I try it RefreshIndicator worked but Listview.Builder not working and I search in Google the answer that I find is that place physic:
NeverScrollableScroll() in Listview.Builder then My Listview.Builder is working OK but Refresh Indicator not working. What can I do?
I am little bit confused, what I can do: either I place AlwaysScrollableScroll() or NeverScrollable().
Here is my code:
return Scaffold(
body: SafeArea(
child: Padding(
padding: EdgeInsets.all(10),
child: Column(
children: [
FirstRow(
headingText: 'My Appointments',
context: context,
),
SizedBox(
height: 10,
),
Obx(() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppButtonLarge(
selected: appointmentController.appointmentT ==
'upcomingAppointments' ??
false,
text: 'Upcoming',
onTap: onAppointmentChange),
AppButtonLarge(
selected: appointmentController.appointmentT ==
'pastAppointments' ??
false,
text: 'Past',
onTap: onAppointmentChange),
],
);
}),
SizedBox(
height: 10,
),
Expanded(
child: SingleChildScrollView(
physics: ScrollPhysics(),
child: Column(
children: [
GetX<DoctorAppointmentController>(
builder: (controller) {
// List<Appointment> controller.appointmentList=[];
// allAppointment.forEach((element) {
// if(element!=null){
// controller.appointmentList=controller.appointmentList+element;
// }
// });
// List<Appointment> controller.appointmentList=snapshot.data;
print(controller.appointmentList.length);
return controller.isLoading.value
? AppWidgetsCard.getProgressIndicator()
: controller.appointmentList.length > 0
? RefreshIndicator(
key: refreshKey,
onRefresh: () async{
await Navigator.pushReplacement(context, PageRouteBuilder(pageBuilder: (a,b,c)=>GetAllDrAppointments(),
transitionDuration: Duration(seconds: 3)));
},
child: Obx(()=>ListView.builder(
physics: const NeverScrollableScrollPhysics(),
// reverse: true,
itemCount: controller.appointmentList.length,
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemBuilder: (context, index) {
void goToNext() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
MyAppointments(
appointment: controller
.appointmentList[
index], isDoctor: true),
));
}
if (index ==
controller.appointmentList.length -
1) {
id = controller
.appointmentList[index].id;
return Column(
children: [
TextButton(
onPressed: goToNext,
child: AppWidgetsCard
.getAppointmentCard(
controller.appointmentList[index],
goToNext, isDoctor: true), ),
Obx(
() {
return controller.loadMore.value
? AppWidgetsCard
.getProgressIndicator()
: Container(
height: 40,
width: 200,
child: AppMethodButton(
selected: true,
text: 'Load More',
onTapMethod: () {
print('data');
controller
.fetchMoreAppointments(
id);
}),
);
},
)
],
);
} else {
return TextButton(
onPressed: goToNext,
child: AppWidgetsCard
.getAppointmentCard(
controller
.appointmentList[index],
goToNext, isDoctor: true));
}
},
),
),
)
: AppWidgetsCard.getEmptyCard('Appointment');
},
),
],
),
)),
],
),
)),
bottomNavigationBar: Container(
height: 50,
child: LastRow(
page: 'Appointment',
)),
);'''
About the ScrollPhysics there are different kinds of scrolling behavior. And AlwaysScrollableScroll means that the ListView is able to scroll no matter whether the body size of the ListView is larger than the assigned size. By default, the ListView only scrolls when the size of the body of the ListView is larger than the assigned parent size. So to adopt the RefreshIndicator does not matter with the scroll physics.
To avoid the cache issues of ListView to force the ListView update, you should update the data of the ListView. You would use the Obx wrapper with the ListView and create the list like below:
List<String> data = <String>[].obs;
Obx(()=> data.value.isEmpyty ? SizedBox() : ListView(children: data.value.map((value) => Text(value)).toList())

Why is my expansion tile expanding to the whole screen

I'm trying to call an api when I expand the tile by using future builder. Which returns a list. However, when I click on it, it expands the whole screen and doesn't display anything.
Here's an image of what I'm describing:
Here's the code:
Widget build(BuildContext context) {
return FutureBuilder(
future: marketApiCall(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if(snapshot.hasData) {
return ListView.separated(
shrinkWrap: true,
separatorBuilder: (BuildContext context, int index) => Divider(),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int count) {
return ExpansionTile(
title: Text(snapshot.data[count].itemName),
children: <Widget>[
FutureBuilder(
future: getItemDetail(itemName: snapshot.data[count].urlName),
builder: (BuildContext context, AsyncSnapshot<List<dynamic>> snapshot) {
if(snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData){
debug.output(fromFunction:"Widget build", message: snapshot.data[0]['en']['description']);
return Container(
width: MediaQuery.of(context).size.width * 0.8,
height: MediaQuery.of(context).size.height * 0.8,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("Item description: ", style: TextStyle(fontWeight: FontWeight.bold)),
Expanded(
child: Text(snapshot.data[0]['en']['description'].toString().replaceAll(reg, ''))
)
],
)
],
)
);
} else {
return Center(
child: Text("Error: No data")
);
}
} else {
return Center(
child: CircularProgressIndicator(),
);
}
}
)
],
);
},
);
} else {
return ListTile(
title: Text("There was an error with the api call, please try again later")
);
}
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
);
I've tried adding shrinkwrap to see if that would work. That didn't.
Also, debug.output is just a custom function that displays print messages so there is no need to worry about that. As you can see, it's a future builder inside a futurebuilder. I've tried separating the inside future builder to a separate stateful class and returning a column to the expansion tile, however that didn't work either.
I think the proble is right after the "FutureBuilder"
return Container(
width: MediaQuery.of(context).size.width * 0.8,
height: MediaQuery.of(context).size.height * 0.8,
You're giving its child Container an 80% of the screen height, that, with the ExpansionTile's height, will occupy the whole screen (or almost).
Try removing the container height and let its child to size the container.

Flutter : How can I add divider between each List Item in my code?

How could I add divider to list? I use Flutter for Android. I want to add a divider between each List item and I want to colorize the divider and add styles.
I tried to add new divider(); but I got errors. I also tried return new divider();.
Here is the screen shot of my app:
And here is my code:
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp();
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.purple,
buttonTheme: const ButtonThemeData(
textTheme: ButtonTextTheme.primary,
)
),
home: const MyHomePage(),
);
}
}
class Kitten {
const Kitten({this.name, this.description, this.age, this.imageurl});
final String name;
final String description;
final int age;
final String imageurl;
}
final List<Kitten> _kittens = <Kitten>[
Kitten(
name: "kitchen",
description: "mehraboon",
age: 2,
imageurl:
"https://images.pexels.com/photos/104827/cat-pet-animal-domestic-
104827.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=350",
),
Kitten(
name: "garage",
description: "khashen",
age: 1,
imageurl:
"https://images.pexels.com/photos/4602/jumping-cute-playing-animals.jpg?
auto=compress&cs=tinysrgb&dpr=2&h=350",
),
Kitten(
name: "bedroom",
description: "khar zoor",
age: 5,
imageurl:
"https://images.pexels.com/photos/978555/pexels-photo-978555.jpeg?
auto=compress&cs=tinysrgb&dpr=2&h=350",
),
Kitten(
name: "living room",
description: "chorto",
age: 3,
imageurl:
"https://images.pexels.com/photos/209037/pexels-photo-209037.jpeg?
auto=compress&cs=tinysrgb&dpr=2&h=350",
),
];
class MyHomePage extends StatelessWidget {
const MyHomePage({Key key}) : super(key: key);
Widget _dialogBuilder(BuildContext context, Kitten kitten) {
return SimpleDialog(contentPadding: EdgeInsets.zero, children: [
Image.network(kitten.imageurl, fit: BoxFit.fill),
Padding(
padding: const EdgeInsets.all(16.0),
child:
Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
Text(kitten.name),
Text('${kitten.age}'),
SizedBox(
height: 16.0,
),
Text(kitten.description),
Align(
alignment: Alignment.centerRight,
child: Wrap(
children: [
FlatButton(onPressed: () {}, child: const
Text("noooo!"),color: Colors.red,),
Padding(padding: const EdgeInsets.all(2.0),),
RaisedButton(onPressed: () {}, child: const
Text("yesss!"),color: Colors.green)
],
),
)
]))
]);
}
Widget _listItemBuilder(BuildContext context, int index) {
return new GestureDetector(
onTap: () => showDialog(
context: context,
builder: (context) => _dialogBuilder(context, _kittens[index])),
child:
Container(
padding: EdgeInsets.all( 16.0),
alignment: Alignment.centerLeft,
child: Text(_kittens[index].name,
style: Theme.of(context).textTheme.headline),
),
) ;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Keys"),
centerTitle: true,
),
body: ListView.builder(
itemCount: _kittens.length,
itemExtent: 60.0,
itemBuilder: _listItemBuilder,
),
);
}
}
There are a number of ways to do the same thing. Let me compare them here.
For a short static list
Use ListTile.divideTiles
ListView(
children: ListTile.divideTiles( // <-- ListTile.divideTiles
context: context,
tiles: [
ListTile(
title: Text('Horse'),
),
ListTile(
title: Text('Cow'),
),
ListTile(
title: Text('Camel'),
),
ListTile(
title: Text('Sheep'),
),
ListTile(
title: Text('Goat'),
),
]
).toList(),
)
For a long dynamic list
Use ListView.separated.
ListView.separated(
itemCount: 100,
itemBuilder: (context, index) {
return ListTile(
title: Text('$index sheep'),
);
},
separatorBuilder: (context, index) {
return Divider();
},
)
This returns two widgets for every item, except for the last item. The separatorBuilder is used to add the divider.
For adding a divider after the last item
Create a custom item widget that uses a Divider or BoxDecoration.
Using Divider
final items = ['Horse', 'Cow', 'Camel', 'Sheep', 'Goat'];
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return Column(
children: <Widget>[
ListTile(
title: Text(items[index]),
),
Divider(), // <-- Divider
],
);
},
);
}
Using BoxDecoration
final items = ['Horse', 'Cow', 'Camel', 'Sheep', 'Goat'];
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return Container(
decoration: BoxDecoration( // <-- BoxDecoration
border: Border(bottom: BorderSide()),
),
child: ListTile(
title: Text(items[index]),
),
);
},
);
}
Both Divider and BoxDecoration are customizable as far as the line height and color go. Divider also has an indent option, but you could get a BoxDecoration to do the same thing with some padding.
For more style
Use a Card
final items = ['Horse', 'Cow', 'Camel', 'Sheep', 'Goat'];
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return Card( // <-- Card
child: ListTile(
title: Text(items[index]),
),
);
},
);
}
The most correct way is to use ListView.separated
ListView.separated(
itemCount: 25,
separatorBuilder: (BuildContext context, int index) => Divider(height: 1),
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('item $index'),
);
},
);
Put your widget inside container with BoxDecoration as
Container(
child: YourWidgetHere(),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.black26))),
);
On the flutter getting started tutorial it is covered, the solution they provide is something like this:
body: ListView.builder(
itemCount: _kittens.length,
itemExtent: 60.0,
itemBuilder: (context, i) {
// Add a one-pixel-high divider widget before each row in theListView.
if (i.isOdd) return new Divider(color: Colors.purple); // notice color is added to style divider
return _listItemBuilder();
},
),
...
That should add the dividers taking into account the odd and even rows to do so.
Also to color the divider pas "color" to the Divider Class:
new Divider(color: Colors.purple);
recently I use this code to set divider:
ListView.separated(
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text("Hello", style: TextStyle(
color: Theme
.of(context)
.primaryColor
),),
);
},
separatorBuilder: (context, index) =>Divider(height: 1, color: Colors.green),
itemCount: 30),
and it works.
For example, I have added my app's screenshot
Check out this issue:
ListView.builder should let clients specify a divider
It makes clear that:
if you need to build your list with dynamic elements, for now you'll have to deal with this issue on your own. I'd recommend in the row widget building, you include a List Divider at the bottom with a column or something, except for the last one (you can test for index == listData.length - 1).
But if, like in the example you show, you already know all the lists data before hand, or you build it without a ListView.builder, then you can and should use the named constructor ListView.divideTiles
following this Just add Divider() :
Column(
children: <Widget>[
Container(
padding: EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
Image.network(video["imageUrl"]),
Container(
height: 6.0,
),
Text(
video["name"],
textScaleFactor: 1.05,
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
),
Divider(
color: Theme.of(context).primaryColor,
)
],
);
Dart 2.3
Another way, especially for generic non-list-view: Using for in a Collection (link) with the ... spread operator
Column(
children: [
for(var i=0; i<4; i+=1)
...[Container(height: 100, width: 100),
Divider()
]])
The question assumes that we have access to material.dart for Android styling (ListTile and Divider). If you want Cupertino styles, we can:
Use a Column view wrapper for the row, and add a Container with height 1.
Column(
children: <Widget>[
row, // A custom Row
Padding(
padding: const EdgeInsets.only(
left: 16, // Adjust separator left padding.
right: 16, // Adjust separator right padding.
),
child: Container(
height: 1,
color: Styles.productRowDivider, // Custom style
),
),
],
);
The Divider is not available in cupertino.dart. We can use the same Container technique with ListView.separated:
ListView.separated(
itemCount: 100,
itemBuilder: (context, index) {
return row;
},
separatorBuilder: (context, index) {
return Container(
height: 1,
color: Styles.productRowDivider, // Custom style
);
},
);
Thats another way usig Container.
ListTile(
leading: Icon(Icons.home),
title: Text('Home'),
),
Container(height: 1, color: Colors.grey), //divider
ListTile(
leading: Icon(Icons.logout),
title: Text('Logout'),
),
Create a Container like this
Container(height: 1, color: Colors.grey),
And add with your ListTile like this
ListView.builder(
itemCount: _feed.items.length,
itemBuilder: (BuildContext context, int index) {
final item = _feed.items[index];
return Column(
children: <Widget>[
Container(height: 1, color: Colors.grey), //Container for devider
ListTile( //Your tile item
title: title(item.title),
subtitle: subtitle(item.pubDate),
leading: thumbnail(item.enclosure.url),
trailing: rightIcon(),
contentPadding: EdgeInsets.all(5.0),
onTap: () => openFeed(item.link),
)]);
},
);
Now you can see final output in my screenshot
[1]: https://i.stack.imgur.com/EZuIg.jpg

Categories

Resources