How to build only specific objects in the screen - flutter

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]);
},
);
}),
);
}

Related

flutter/dart: streambuilder inside list view not scrolling

Is there anyway to making a streambuilder inside a list view scrollable?
I tried wrapping the form on a column widget but its not working out. none of the information is displayed on the screen for some reason.
here is a mini video of the issue:: https://youtu.be/_GIZkwzeH0Y
is there a different way to display this information?
return Scaffold(
appBar: AppBar(
title: const Text('Job details'),
),
body: FutureBuilder(
future: createOrGetExistingJob(context),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
_setupTextControllerListener();
// at this point we want to start to listening for changes
return Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(32.0),
children: [
getStateChevrons(_jobState),
const Divider(
height: 20,
thickness: 5,
indent: 0,
endIndent: 0,
color: Colors.blue,
),
//
//
//
// Below is the streambuilder I would like to correctly display
getStateDependentButtons(context),
_jobDocumentId != null && _jobState != jobStateNew
? StreamBuilder(
stream: _jobsService
.getJobApplication(_jobDocumentId as String),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
case ConnectionState.active:
if (snapshot.hasData) {
final allJobApplications = snapshot.data
as Iterable<CloudJobApplication>;
return JobApplicationsListView(
jobApplications: allJobApplications,
onTap: (job) {
Navigator.of(context).pushNamed(
myJobApplicationsRoute,
arguments: job,
);
},
);
} else {
return const CircularProgressIndicator();
}
default:
return const CircularProgressIndicator();
}
},
)
: const Text(
'Once job is submitted, job applications will be available')
]
.map((child) => Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: child,
))
.toList(),
),
);
default:
return const CircularProgressIndicator();
}
},
),
//
//
);
Here is what jobApplicationListView looks like::
import 'package:flutter/material.dart';
import '../../services/cloud/cloud_job_application.dart';
import '/services/cloud/cloud_job.dart';
import '/utilities/dialogs/delete_dialog.dart';
/*
source: https://www.youtube.com/watch?v=VPvVD8t02U8&t=59608s
class creation :: 22:02:54
*/
typedef JobCallback = void Function(CloudJobApplication jobApplication);
class JobApplicationsListView extends StatelessWidget {
final Iterable<CloudJobApplication>
jobApplications; // list of jobApplications
//final JobCallback onDeleteJob;
final JobCallback onTap;
const JobApplicationsListView({
Key? key,
required this.jobApplications,
//required this.onDeleteJob,
required this.onTap,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: true,
itemCount: jobApplications.length,
itemBuilder: (context, index) {
final jobApplication = jobApplications.elementAt(index);
return ListTile(
onTap: () {
onTap(jobApplication);
},
title: Text(
jobApplication.jobApplicationDescription,
maxLines: 1,
softWrap: true,
overflow: TextOverflow.ellipsis,
),
trailing: IconButton(
onPressed: () async {
final shouldDelete = await showDeleteDialog(context);
if (shouldDelete) {
"onDeleteJob(job)";
}
},
icon: const Icon(Icons.delete),
),
);
},
);
}
}
On your JobApplicationsListView's ListView.builder( set physics: NeverScrollableScrollPhysics(), The parent already handling scroll event.
#override
Widget build(BuildContext context) {
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
itemCount: jobApplications.length,
itemBuilder: (context, index) {
final jobApplication = jobApplications.elementAt(index);
Also you can try with primary: false, on it.
Or you can use Column widget instead.

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]),
);
}),
);

How to implement checkbox over GridView correctly

I have checkbox for selecting and deselecting photos.
There is a visible loading screen for each tap.
_mediaList has the photo asset. mediaModel has the necessary methods to add and remove the path of selected and deselected photos respectively.
Widget build(BuildContext context) {
super.build(context);
return GridView.builder(
itemCount: _mediaList.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, mainAxisSpacing: 4.0, crossAxisSpacing: 4.0),
itemBuilder: (BuildContext context, int index) {
final saved = mediaModel.getMedia().contains(
_mediaList[index].relativePath + '/' + _mediaList[index].title);
return FutureBuilder(
future: _mediaList[index].thumbDataWithSize(200, 200),
builder: (BuildContext context, snapshot) => snapshot.hasData
? GridTile(
header: saved
? Icon(Icons.check_circle, color: Colors.white,)
: Icon(Icons.check_circle_outline, color: Colors.white,),
child: GestureDetector(
child: Image.memory(
snapshot.data,
fit: BoxFit.cover,
),
onTap: () => setState(() => saved
? mediaModel.removeMedia(
_mediaList[index].relativePath +
'/' +
_mediaList[index].title)
: mediaModel.addMedia(
_mediaList[index].relativePath +
'/' +
_mediaList[index].title))),
)
: Container());
},
);
}
EDIT: After some analysis, I found out using Provider to load images might be the right way.
Can you help me in converting this code to Provider?
Thanks in advance!!!
Screenshot:
Full code:
class FooPage extends State<SoPage> {
static const int _count = 10;
final List<bool> _checks = List.generate(_count, (_) => false);
#override
Widget build(BuildContext context) {
return Scaffold(
body: GridView.builder(
itemCount: _count,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemBuilder: (_, i) {
return Stack(
children: [
Container(color: Colors.red[(i * 100) % 900]),
Align(
alignment: Alignment.topCenter,
child: Checkbox(
value: _checks[i],
onChanged: (newValue) => setState(() => _checks[i] = newValue),
),
),
],
);
},
),
);
}
}

how to create an icon on image with inkwell after Click on the image in flutter

I made a grid view in the flutter app. But like the Pictures on the below link , I want to create an icon on the picture and change the background color After tap the picture,
I've been looking for ways, but I've finally got a question. I'd appreciate it from the bottom of my heart if you'd let me know.
Please enter img link(below)
https://firebasestorage.googleapis.com/v0/b/instaclone-2-fd9de.appspot.com/o/post%2F12344.png?alt=media&token=89d46c03-83ba-4d30-b716-e9b718c1340b
Widget _bodyBuilder() {
// TODO : 그 예시를 어떻해 stream View로 보여줄것인가
return StreamBuilder <QuerySnapshot>(
stream: _commentStream(),
builder: (BuildContext context, AsyncSnapshot snapshot){
if(!snapshot.hasData){
return Center(child: CircularProgressIndicator());
}
var items = snapshot.data?.documents ??[];
var fF = items.where((doc)=> doc['style'] == "오피스룩").toList();
var sF = items.where((doc)=> doc['style'] == "로맨틱").toList();
var tF = items.where((doc)=> doc['style'] == "캐주").toList();
fF.addAll(sF);
fF.addAll(tF);
fF.shuffle();
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 0.6,
mainAxisSpacing: 2.0,
crossAxisSpacing: 2.0),
itemCount: fF.length,
itemBuilder: (BuildContext context, int index) {
return _buildListItem(context, fF[index]);
});
},
);
}
Widget _buildListItem(context, document) {
return Ink.image(
image : NetworkImage(document['thumbnail_img']),
fit : BoxFit.cover,
child: new InkWell(
//I think we need to get something in here....
onTap: (){},
),
);
}
You should create List of Image which have isSelected value and when the user clicks on item them set true/false base of the old value which have imageURL and isSelected variable. First, you should store value in List Of Image obj. which coming from Firebase/API then flow below step. I have created a demo and post here. Please take reference.
Example code
class Demo extends StatefulWidget {
#override
_DemoState createState() => _DemoState();
}
class _DemoState extends State<Demo> {
List<ImageData> imageList;
#override
void initState() {
super.initState();
imageList = ImageData.getImage();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 0.5,
crossAxisCount: 5,
crossAxisSpacing: 2.0,
mainAxisSpacing: 2.0),
itemCount: imageList.length,
itemBuilder: (builder, index) {
return InkWell(
onTap: () {
setState(() {
imageList[index].isSelected = !imageList[index].isSelected;
});
},
child: Stack(
children: [
_getImage(imageList[index].imageURL),
Opacity(
opacity: imageList[index].isSelected ? 1 : 0,
child: Stack(
children: [
Container(
width: double.infinity,
height: double.infinity,
color: Colors.black38,
),
Center(
child: CircleAvatar(
backgroundColor: Colors.greenAccent,
child: Icon(
Icons.check,
color: Colors.white,
),
),
)
],
),
)
],
));
},
),
);
}
_getImage(url) => Image.network(
url,
height: 500,
fit: BoxFit.fitHeight,
);
#override
void dispose() {
super.dispose();
}
}
class ImageData {
String imageURL;
bool isSelected;
int id;
ImageData(this.imageURL, this.isSelected, this.id);
static List<ImageData> getImage() {
return [
ImageData('https://picsum.photos/200', false, 1),
ImageData('https://picsum.photos/100', false, 2),
ImageData('https://picsum.photos/300', false, 3),
ImageData('https://picsum.photos/400', false, 4),
ImageData('https://picsum.photos/500', false, 5),
ImageData('https://picsum.photos/600', false, 6),
ImageData('https://picsum.photos/700', false, 7),
ImageData('https://picsum.photos/800', false, 8),
ImageData('https://picsum.photos/900', false, 9),
];
}
}
Output

Dynamic ListView height inside another ListView

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: