Dynamic ListView height inside another ListView - flutter

I need to make a dynamic ListView height inside another ListView. None of the answers here I have come to didn't really answer it. I've made a simple example of what I'm trying to do so you can simply copy and paste it to try it and play with it. I've got problem with sub ListView where I need to make it grow or shrink based on number of items in it (problem commented in program)
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
List<List<bool>> subList = [
[true, true],
[true]
];
class _TestState extends State<Test> {
#override
Widget build(BuildContext context) {
return ListView.separated(
itemBuilder: (BuildContext context, int index) {
return Column(
children: <Widget>[
ListTile(
trailing: IconButton(
icon: Icon(Icons.add),
onPressed: () {
setState(() {
subList[index].add(true);
});
},
),
title: Text(
'item $index',
style: TextStyle(fontWeight: FontWeight.w600),
),
),
Container(
height: 100, // <--- this needs to be dynamic
child: ListView.builder(
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int subIndex) {
return TestRow(
text: 'sub$subIndex',
onRemove: () {
setState(() {
subList[index].removeAt(subIndex);
});
});
},
itemCount: subList[index].length,
),
)
],
);
},
separatorBuilder: (BuildContext context, int index) => Divider(),
itemCount: subList.length);
}
}
class TestRow extends StatelessWidget {
final String text;
final Function onRemove;
const TestRow({this.onRemove, this.text});
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(text),
IconButton(
icon: Icon(Icons.remove),
onPressed: onRemove,
)
],
),
);
}
}
BTW I managed to make a workaround by changing height of container (commented part) to height: 50.0 * subList[index].length where 50 is height of sub title. I'm still looking for a proper way of doing it where I wouldn't need to hardcode height of the tile and calculate it
Here is video of the project with workaround how it should work

Try setting the shrinkWrap property to true and remove the container
ListView.builder(
shrinkWrap: true, //<-- Here
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int subIndex) {
return TestRow(
text: 'sub$subIndex',
onRemove: () {
setState(() {
subList[index].removeAt(subIndex);
});
});
},
itemCount: subList[index].length,
)
Output:

Related

How to assign TextEditingController to dynamicaly created textfield in model and retrieve data from TextEditingController in flutter

What I actually want is to create text fields against each index of my list and display this list of text fields in expansion tile with Title 'index' of list and children are Textfields which user will create dynamically on button click so I Created list of text fields dynamically and now want to assign TextEditingController to each of textfields and retirive data
This is my Model I created
class MyModel {
List<Widget> widgets;
bool isShow = false;
MyModel({ required this.widgets, required this.isShow});
}
This is my Controller
class DummyController extends ChangeNotifier {
List<MyModel> myModelList = [];
addTextFieldToDay(var index) {
myModelList[index].widgets.add(TextField());
notifyListeners();
}
}
And here is how I am calling above function and displaying textfields
ListView.builder(
itemCount: vm.myModelList.length,
itemBuilder: ((context, index) {
return ExpansionTile(
title: Text('Day ${index + 1}'),
children: [
IconButton(
onPressed: () {
vm.addTextFieldToDay(index);
},
icon: Icon(Icons.add)),
Container(
color: Colors.white,
height: 150,
child: ListView(children:vm.myModelList[index].widgets)
),
I tried all previously asked question solutions: But the problem i faced in this is i don't know how to get value of specific index controller
i.e
List<TextEditingController> controllers = [];
TextEditingController controller = TextEditingController();
controllers.add(controller);
It here:
class Home extends StatefulWidget {
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
List<Widget> listData = [];
List<TextEditingController> listDataCTL = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('AppBar'),
),
body: Column(
children: [
TextButton(
onPressed: () {
var ctl = TextEditingController(text: "index");
var textF = TextField(
controller: ctl,
);
listData.add(textF);
listDataCTL.add(ctl);
setState(() {});
},
child: Text("Add")),
Expanded(
child: ListView.builder(
itemCount: listData.length + 1,
itemExtent: 50,
itemBuilder: (context, index) {
if (index == 0) {
return GestureDetector(
onTap: () {
print(listDataCTL.first.text);
},
child: Container(
child: Text('Header'),
),
);
}
return Container(
child: listData[index - 1],
);
},
),
),
],
));
}
}
Here is how achieve this I remove model class and create List of dynamic type and added days to my list with key value pair
class DummyController extends ChangeNotifier {
List days = [];
int addedDay =0;
List finalMap = [];
addDaysToList(){
addedDay++;
days.add({'day$addedDay':<TextEditingController>[]});
finalMap.add({'day$addedDay':<String>[]});
notifyListeners();
}
addTextFieldsToList(var index){
days[index]['day${index+1}'].add(TextEditingController());
notifyListeners();
}}
And this is what i do in my view
Container(
child: SizedBox(
height: 500,
child: ListView.builder(
itemCount: vm.days.length,
itemBuilder: ((context, index) {
return ExpansionTile(
title: Text('Day ${index + 1}'),
children: [
IconButton(
onPressed: () {
vm.addTextFieldsToList(index);
},
icon: Icon(Icons.add)),
Container(
color: Colors.grey[300],
height: 150,
child: ListView.builder(
itemCount: vm.days[index['day${index+1}'].length,
itemBuilder: ((context, mindex) {
return TextField(
controller: vm.days[index]['day${index+1}'][mindex],);
})) ),],);
})),),),

i want to add a row in listview.builder but it goes blank

i want to add a row ( list of buttons that do filter the list), i tried wrapping listTile in column, listview.builde in column but it doesn't work. tried wrapping GetBuilder also but it doesn't work.
enter image description here
My Code :-
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:quizzy/data_controller.dart';
import '../models/showQuestion.dart';
class AllQuestionBank extends StatefulWidget {
const AllQuestionBank({Key? key}) : super(key: key);
#override
State<AllQuestionBank> createState() => _AllQuestionBankState();
}
class _AllQuestionBankState extends State<AllQuestionBank> {
final DataController controller = Get.put(DataController());
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
controller.getQuestionList();
});
return Scaffold(
appBar: AppBar(
title: const Text(' Question Bank'),
),
body: GetBuilder<DataController>(
builder: (controller) => controller.QuestionList.isEmpty
? const Center(
child: Text('😔 NO DATA FOUND (: 😔'),
)
: ListView.builder(
itemCount: controller.QuestionList.length,
itemBuilder: (context, index) {
return ListTile(
title: showQuestion(controller.QuestionList[index]),
);
}),
),
);
}
}
You could redefine your ListView as:
ListView.builder(
itemCount: controller.QuestionList.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return WhateverRowYouWant();
}
return ListTile(
title: showQuestion(controller.QuestionList[index - 1]),
);
}),
You can just define the scroll direction to be horizontal.
ListView.builder(
itemCount: controller.QuestionList.length,
scrollDirection: Axis.horizontal, <- added this line
itemBuilder: (context, index) {
return ListTile(
title: showQuestion(controller.QuestionList[index]),
);
}),
You can also find an example from the official docs here
Try this.
physics: NeverScrollableScrollPhysics(), shrinkWrap: true,
To add Row() on top you need Column() widget for sure
After that, you have to wrap ListView.builder() with the Expanded() widget this will help you
Ex.
return Scaffold(
body: SafeArea(
child: Column(
children: [
Row(
children: [
TextButton(
onPressed: () {},
child: Text('Filter'),
),
],
),
Expanded(
child: GetBuilder<DataController>(
builder: (controller) => controller.QuestionList.isEmpty
? const Center(
child: Text('😔 NO DATA FOUND (: 😔'),
)
: ListView.builder(
shrinkWrap: true,
itemCount: controller.QuestionList.length,
itemBuilder: (context, index) {
return ListTile(
title: showQuestion(controller.QuestionList[index]),
);
},
),
),
),
],
),
),
);
Please, try this!!
This is the combination of GetxController and ListView with a top row I use:
class MyController extends GetxController {
var isRunning = true.obs; // set to isRunning.value = false; if done loading QuestionList
RxList<ProductModel> QuestionList = <ProductModel>[].obs;
}
Obx( () => controller.isRunning.isTrue
? 'Loading'
: ListView.builder(
itemCount: controller.QuestionList.length + 1,
itemBuilder: (BuildContext context, int index) {
if (index == 0) {
return Text('TOP ROW');
}
return ListTile(
title: showQuestion(controller.QuestionList[index - 1]),
);
}),
);

RefreshIndicator not working with Scrollview

I am trying to use RefreshIndicator with Scrollview as want extra widgits too with list in RefreshIndicator but it is not working.
Basically I want to use RefreshIndicator then inside it a Scrollview with a column with multiple widgets.
Thanks in advance!
Here is my code:
class _DashboardState extends State<Dashboard> {
List<String> _demoData = [];
#override
void initState() {
_demoData = [
"Flutter",
"React Native",
"Cordova/ PhoneGap",
"Native Script"
];
super.initState();
}
#override
Widget build(BuildContext context) {
Future doRefresh() {
return Future.delayed(
Duration(seconds: 0),
() {
setState(() {
print("refresh worked");
_demoData.addAll(["Ionic", "Xamarin"]);
});
},
);
}
return RefreshIndicator(
onRefresh: doRefresh,
child: SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
height: 400,
child: ListView.builder(
itemBuilder: (ctx, idx) {
return Card(
child: ListTile(
title: Text(_demoData[idx]),
),
);
},
itemCount: _demoData.length,
),
),
],
),
),
);
}
}
The problem is that your list that has the 'RefreshIndicator' is in another list, this only scrolls the parent list and the child list where the 'RefreshIndicator' is located will not work, delete the parent list and have it only display the child list like the following example:
Another problem is also that the 'doRefresh' method is inside the 'build' method of the Widget, take it out of the 'build' method like the example: (in seconds put 2 or 3 seconds to see the animation)
class _DashboardState extends State<Dashboard> {
List<String> _demoData = [];
#override
void initState() {
_demoData = [
"Flutter",
"React Native",
"Cordova/ PhoneGap",
"Native Script"
];
super.initState();
}
Future doRefresh() {
return Future.delayed(
Duration(seconds: 3),
() {
setState(() {
print("refresh worked");
_demoData.addAll(["Ionic", "Xamarin"]);
});
},
);
}
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: doRefresh,
child: ListView.builder(
itemBuilder: (ctx, idx) {
return Card(
child: ListTile(
title: Text(_demoData[idx]),
),
);
},
itemCount: _demoData.length,
));
}
}
If you want to add more widgets, those widgets have to be inside the ListView, if you want to use more lists it is better to use a CustomScrollView instead of the ListView
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: doRefresh,
child: ListView(
children: [
...List.generate(
_demoData.length,
(idx) => Card(
child: ListTile(
title: Text(_demoData[idx]),
),
)),
Container(
height: 100,
width: double.infinity,
color: Colors.red,
),
...List.generate(
_demoData.length,
(idx) => Card(
child: ListTile(
title: Text(_demoData[idx]),
),
)),
],
));
}

How to build only specific objects in the screen

I have built an app that shows images like Pinterest
like that;
And, When I scroll down, after passing 25 images, my build widget refreshs the page and adds new 25 images to the screen and my screen has 50 images and so on. However, this takes me again the top of the screen because of adding new 25 images. because of that, I see again the images that I already see.
And, I want to don't refresh the page again while adding new 25 images but my builder widget works only in this way.
My class file that has builder widget;
class HomeView extends StatefulWidget {
#override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
HomeViewModel viewModel;
#override
Widget build(BuildContext context) {
return BaseView<HomeViewModel>(
onModelReady: (model) {
model.init();
model.setContext(context);
viewModel = model;
},
builder: (context, value) => buildScaffold,
model: HomeViewModel(),
);
}
Scaffold get buildScaffold {
return Scaffold(
body: CustomScrollView(
/* physics: BouncingScrollPhysics(), */
slivers: <Widget>[
buildSliverAppBar,
buildSliverStaggeredGrid,
],
),
);
}
SliverAppBar get buildSliverAppBar {
return SliverAppBar(
floating: true,
pinned: true,
snap: false,
backgroundColor: Colors.white,
title: buildSearchField,
actions: <Widget>[buildIconButtonClose, buildanotherbutton],
);
}
TextField get buildSearchField {
return TextField(
controller: viewModel.searchController,
decoration: InputDecoration(hintText: "Search...", border: InputBorder.none),
onSubmitted: (String keyword) => viewModel.loadImages(keyword),
);
}
IconButton get buildIconButtonClose {
return IconButton(
onPressed: () => viewModel.resetImages(),
icon: Icon(Icons.close, color: Colors.black),
);
}
IconButton get buildanotherbutton {
return IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Passaword()),
);
},
icon: Icon(Icons.ac_unit, color: Colors.black),
);
}
Widget get buildSliverStaggeredGrid {
return SliverPadding(
padding: EdgeInsets.all(0),
sliver: Observer(builder: (_) {
return viewModel.loadingImages
? SliverToBoxAdapter(
child: Center(child: CircularProgressIndicator()))
: SliverStaggeredGrid.countBuilder(
crossAxisCount: 2,
itemCount: viewModel.images.length,
itemBuilder: (BuildContext context, int index) =>
buildImageTile(index),
crossAxisSpacing: 5,
mainAxisSpacing: 10,
staggeredTileBuilder: (int index) {
return buildStaggeredTile(viewModel.images[index]);
},
);
}),
);
}
StaggeredTile buildStaggeredTile(UnsplashImage image) {
double aspectRatio = image.height / image.width;
double columnWidth = MediaQuery.of(context).size.width / 2;
return StaggeredTile.extent(1, aspectRatio * columnWidth);
}
Widget buildImageTile(int index) {
return FutureBuilder(
future: viewModel.loadImage(index),
builder: (context, snapshot) => ImageTileWidget(image: snapshot.data),
);
}
}
If you want my whole code, I can share with you. I thought my file that has builder widget code is enough to solve the problem.
my builder widget here;
Widget get buildSliverStaggeredGrid {
return SliverPadding(
padding: EdgeInsets.all(0),
sliver: Observer(builder: (_) {
return viewModel.loadingImages
? SliverToBoxAdapter(
child: Center(child: CircularProgressIndicator()))
: SliverStaggeredGrid.countBuilder(
crossAxisCount: 2,
itemCount: viewModel.images.length,
itemBuilder: (BuildContext context, int index) =>
buildImageTile(index),
crossAxisSpacing: 5,
mainAxisSpacing: 10,
staggeredTileBuilder: (int index) {
return buildStaggeredTile(viewModel.images[index]);
},
);
}),
);
}

Error in the code, BuildContext in Flutter

I am making an app with different build methods so that I can make a list of items to save them on another screen when the "love heart" button is tapped. But I am getting errors in the code. I am following the Flutter Codelabs app tutorial part 2.
My code:
import 'package:aioapp2/lists.dart';
import 'package:flutter/material.dart';
class _FavoriteListState extends State<FavoriteList> {
final _suggestions = [];
final Set<Widget> _saved = Set<Widget>();
Widget _buildList() {
return ListView.builder(
itemCount: 53,
itemBuilder: (BuildContext context, int index) {
return _buildRow(_suggestions[index]);
},
);
}
Widget _buildRow(Widget website){
final bool alreadySaved = _saved.contains(website);
return Card(
child: Container(
child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
subtitle: Row(
children: <Widget>[
// Image.asset('lib/images/${images[index]}'),
],
),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _buildList(),
);
}
}
class FavoriteList extends StatefulWidget {
#override
_FavoriteListState createState() => _FavoriteListState();
}
The error that I'm facing is in the Image.asset() line. On typing the following line its showing red line under the "index". But it shouldn't and that's the problem! Any help?
You are attempting to reference the variable index from within the _buildRow method, but that variable doesn't exist there. Take a look at this excerpt from your code
Widget _buildList() {
return ListView.builder(
itemCount: 53,
itemBuilder: (BuildContext context, int index) { // <-- index declared here
return _buildRow(_suggestions[index]);
},
);
}
Widget _buildRow(Widget website){
final bool alreadySaved = _saved.contains(website);
return Card(
child: Container(
child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
subtitle: Row(
children: <Widget>[
// Image.asset('lib/images/${images[index]}'), // <-- index has not been declared here
],
),
),
),
);
}
This is an example of scope. The variable index is defined in the builder method within _buildList. That means the variable only exists there. You can't access it outside that method, so when you try to access it within _buildRow, you get an error.
If you want to pass the value of index to the _buildRow, you need to pass it as an argument to the _buildRow method:
Widget _buildList() {
return ListView.builder(
itemCount: 53,
itemBuilder: (BuildContext context, int index) { // <-- index declared here
return _buildRow(_suggestions[index], index); // Passing it as an argument
},
);
}
Widget _buildRow(Widget website, int index) {
final bool alreadySaved = _saved.contains(website);
return Card(
child: Container(
child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
subtitle: Row(
children: <Widget>[
Image.asset('lib/images/${images[index]}'), // <-- index has been declared in the parameter list so everything is ok here
],
),
),
),
);
}
(Your code doesn't declare images anywhere either, but I'm assuming you declare it elsewhere in code that you didn't share since you aren't reporting the error there.)
Try like this a simple workaround:
import 'package:flutter/material.dart';
void main() => runApp(
MaterialApp(
home: FavoriteList(),
)
);
class _FavoriteListState extends State<FavoriteList> {
final _suggestions = [{
'image' : 'ic_play.png'
},
{
'image' : 'ic_play.png'
},
{
'image' : 'ic_play.png'
},
{
'image' : 'ic_play.png'
}
];
final Set<Widget> _saved = Set<Widget>();
Widget _buildList() {
return ListView.builder(
itemCount: _suggestions.length,
itemBuilder: (BuildContext context, int index) {
return _buildRow(_suggestions[index]);
},
);
}
Widget _buildRow(dynamic website){
return Card(
child: Container(
child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
subtitle: Row(
children: <Widget>[
Image.asset('assets/images/${website['image']}'),
],
),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _buildList(),
);
}
}
class FavoriteList extends StatefulWidget {
#override
_FavoriteListState createState() => _FavoriteListState();
}
Shouldnt
Image.asset('lib/images/${images[index]}'),
be like
Image.asset('lib/images/${index}.png'), // Provide you have 0.png, 1.png .... in lib folder
Or you should had an String array names images like
String[] images=["1.png","2.png"];
=====
I cant see any variable images which you want to reference in the commented code.