Set state from FutureBuilder in Flutter - flutter

I've been trying to set a property based on a response from a FutureBuilder but can't seem to get it working. How can I set _activityLength after the future resolves without setting during build?
FutureBuilder<QuerySnapshot>(
future: _future,
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('Press button to start');
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
final documents = snapshot.data.documents;
_activityLength = documents.length;
return Expanded(
child: ListView.separated(
shrinkWrap: true,
separatorBuilder: (context, index) => Divider(
color: Colors.black,
height: 0,
),
itemCount: documents.length,
itemBuilder: (context, index) => _activityTile(
documents[index],
),
),
);
}
}
},
)
The FutureBuilde is in a Column widget in the body of the Scaffold and the value that I need to set is in the _itemsHeaderText Something like this:
body:
...
child: Column(
children: <Widget>[
Container(
width: double.infinity,
decoration: BoxDecoration(
border: Border(
bottom:
BorderSide(color: Colors.grey, width: 1.0),
),
),
child: Padding(
padding: const EdgeInsets.only(
left: 15.0,
right: 15.0,
top: 10.0,
bottom: 10.0),
child: _itemsHeaderText(),
),
),
_itemsBody(),
],
),

You can copy paste run full demo code below
You can use _future.then((value) in initState() and do job in addPostFrameCallback like setState
And after future resolves, you can get value of _future, you can do further processing if need
In demo code, I get value length and display on screen
You can see working demo, data length change from 0 to 9
code snippet
#override
void initState() {
super.initState();
_future = _getUsers();
_future.then((value) {
print("data length ${value.length}");
WidgetsBinding.instance.addPostFrameCallback((_) {
print("other job");
setState(() {
_dataLength = value.length;
});
});
});
}
working demo
output
I/flutter (18414): data length 9
I/flutter (18414): other job
full code
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: CategoryTab(title: 'Flutter Demo Home Page'),
);
}
}
class CategoryTab extends StatefulWidget {
CategoryTab({Key key, this.title}) : super(key: key);
final String title;
#override
_CategoryTabState createState() => _CategoryTabState();
}
class _CategoryTabState extends State<CategoryTab> {
Future<List<CategoryList>> _future;
int _dataLength = 0;
Future<List<CategoryList>> _getUsers() async {
var data = await http
.get("https://appiconmakers.com/demoMusicPlayer/API/getallcategories");
var jsonData = json.decode(data.body);
List<CategoryList> cat = [];
for (var u in jsonData) {
CategoryList categoryList = CategoryList(
u["category_id"],
u["category_name"],
u["parent_category_id"],
u["category_status"],
u["created_date"]);
cat.add(categoryList);
}
return cat;
}
#override
void initState() {
super.initState();
_future = _getUsers();
_future.then((value) {
print("data length ${value.length}");
WidgetsBinding.instance.addPostFrameCallback((_) {
print("other job");
setState(() {
_dataLength = value.length;
});
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
"Categories",
style: TextStyle(color: Colors.black),
),
backgroundColor: Colors.white,
),
body: Column(
children: [
Text("data length $_dataLength"),
Expanded(
child: Container(
child: FutureBuilder(
future: _future,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data == null) {
return Container(child: Center(child: Text("Loading...")));
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: CircleAvatar(),
title: Text(
"${snapshot.data[index].categoryName}",
// subtitle: Text(snapshot.data[index].categoryId
),
);
},
);
}
},
),
),
),
],
),
);
}
}
class CategoryList {
String categoryId;
String categoryName;
String parentCategoryId;
String categoryStatus;
String createdDate;
CategoryList(this.categoryId, this.categoryName, this.parentCategoryId,
this.categoryStatus, this.createdDate);
}

There are a few workarounds to your question.
You could hoist the futureBuilder up the widget tree, instead of setting state the state will be reset once ConnectionState.done.
You could place the future in a function that is called on init state, then the result of the future you set state on it.

Related

Loading page for ListView builder flutter

Hello I would like to have a loading page before all the list is created. To display this list, I get the request info in php and I create cards one by one. The problem is that when the user come on the application, there is a lot of loading round because I put "return CircularProgressIndicator()", I would like that we are on a waiting page until all the cards are not loaded
I tried to set variables and pass them to false until all the cards are loaded but I can't get on this loading page
Here is the code of my page
Future<String> getDataSearch(text, index) async {
if (index != "ff") {
var theUrl =
Uri.parse("https://xf8s7auoez.preview.infomaniak.website/getdata.php");
var res = await http.post(theUrl, body: {
"index": index.toString(),
"post": "index",
"file": "get_data",
});
Map<String, dynamic> data = jsonDecode(res.body);
if (text == 1) {
return data["type"];
} else if (text == 2) {
return data["taille"];
} else if (text == 3) {
return data["prix"];
} else if (text == 5) {
return data["image"];
} else if (text == 6) {
return data["description"];
}
}
var theUrl =
Uri.parse("https://xf8s7auoez.preview.infomaniak.website/getdata.php");
var res = await http.post(theUrl, body: {
"post": "rows",
"file": "get_data",
});
print(res.body);
return res.body;
}
class Type extends StatelessWidget {
Type(this.index);
final int index;
#override
Widget build(context) {
return FutureBuilder<String>(
// future: getUserType(index),
future: getDataSearch(1, index),
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!);
} else {
return SizedBox(width: 10, height: 10, child: Container());
}
});
}
}
class Description extends StatelessWidget {
Description(this.index);
final int index;
#override
Widget build(context) {
return FutureBuilder<String>(
// future: getUserType(index),
future: getDataSearch(6, index),
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!);
} else {
return SizedBox(width: 10, height: 10, child: Container());
}
});
}
}
class Taille extends StatelessWidget {
Taille(this.index);
final int index;
#override
Widget build(context) {
return FutureBuilder<String>(
// future: getUserType(index),
future: getDataSearch(2, index),
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!);
} else {
return SizedBox(width: 10, height: 10, child: Container());
}
});
}
}
class Prix extends StatelessWidget {
Prix(this.index);
final int index;
#override
Widget build(context) {
return FutureBuilder<String>(
//future: getAge(index),
future: getDataSearch(3, index),
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
String bio = snapshot.data.toString();
return Text(bio);
} else {
return SizedBox(width: 10, height: 10, child: Container());
}
});
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Mediester',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: RefreshIndicatorView(),
);
}
}
class RefreshIndicatorView extends StatefulWidget {
const RefreshIndicatorView({Key? key}) : super(key: key);
#override
_RefreshIndicatorViewState createState() => _RefreshIndicatorViewState();
}
class _RefreshIndicatorViewState extends State<RefreshIndicatorView> {
int initNumber = 1;
int selectedValue = 1;
final GlobalKey<ScaffoldState> _key = GlobalKey();
#override
initState() {
getDataSearch(4, "ff").then((String result) {
if (initNumber != int.parse(result)) {
setState(() {
initNumber = int.parse(result);
});
}
});
}
#override
Widget build(BuildContext context) {
return RefreshIndicator(
displacement: 250,
backgroundColor: Color.fromARGB(255, 110, 155, 191),
color: Colors.white,
strokeWidth: 3,
triggerMode: RefreshIndicatorTriggerMode.onEdge,
onRefresh: () async {
await Future.delayed(Duration(milliseconds: 1500));
setState(() {
int debug = 0;
});
},
child: Scaffold(
key: _key,
backgroundColor: Color.fromARGB(255, 110, 155, 191),
appBar: BaseAppBar(),
drawer: createDrawer(context),
body: _buildCardDesign(),
),
);
}
Widget _buildCardDesign() {
Future.delayed(const Duration(milliseconds: 1500), () {
if (debugg != 1) {
setState(() {});
debugg = 1;
}
});
return Container(
margin: EdgeInsets.all(5),
child: ListView.builder(
itemCount: initNumber,
shrinkWrap: true,
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics()),
scrollDirection: Axis.vertical,
prototypeItem: _buildCardView(1),
itemBuilder: (BuildContext context, int index) {
return _buildCardView(index + 1);
}),
);
}
Widget _buildPhoto(index) {
return Container(
child: FutureBuilder<String>(
future: getDataSearch(5, index),
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return ClipRRect(
borderRadius: BorderRadius.circular(5.0),
child: Image.network(
snapshot.data!,
height: 65.0,
width: 65.0,
fit: BoxFit.cover,
),
);
} else {
return CircularProgressIndicator.adaptive();
}
}));
}
Widget _buildCardView(index) {
return Container(
width: MediaQuery.of(context).size.width,
child: Container(
margin: EdgeInsets.all(10),
height: 100,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(10))),
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ArticlePage(
index, Type(index), Prix(index), Description(index)),
));
},
child: Container(
padding: EdgeInsets.fromLTRB(15, 15, 30, 15),
child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildPhoto(index),
//SizedBox(width: 20,),
DefaultTextStyle(
style: TextStyle(
fontSize: 19,
fontWeight: FontWeight.w800,
color: Color.fromARGB(255, 110, 155, 191)),
child: Container(
padding: EdgeInsets.only(left: 40),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DefaultTextStyle(
child: Container(child: Type(index)),
style: TextStyle(
color: Color.fromARGB(255, 110, 155, 191),
fontSize: 17)),
DefaultTextStyle(
child: Container(
padding: EdgeInsets.only(top: 8),
child: Taille(index)),
style: TextStyle(
color: Color.fromARGB(255, 168, 168, 168)),
),
Container(
padding: EdgeInsets.only(top: 3),
child: Row(
children: [
Prix(index),
Text(" €"),
],
)),
],
),
),
),
],
),
),
),
),
);
}
}
This can be accomplished using a FutureBuilder. In your RefreshIndicatorView class, the _buildCardDesign widget is currently calling your future, and then building the ListView. Instead, try creating one async function that holds the logic for retrieving the data, and a separate class for the widget that is only responsible for building the widget. Then, you can call the future in the FutureBuilder and show your loading page until it has completed.
In the FutureBuilder, pass your function as the argument to future. Then you can use the variable snapshot to check the status of the future. In your builder function, simply check if(snapshot.hasData), and show the loading page or the listView widget accordingly.
Edit: Here's a simple example that retrieves a List<String> and puts one String in each card. Your new function and widget class would look like this:
List<String> _getStrings() async {
//Async logic here
...
}
Widget MyCard extends StatelessWidget {
final String displayText;
myCard(this.displayText);
#override
Widget build(BuildContext context) {
return Text(displayText);
}
}
And in the home page of your MyApp, you can use something like this for the build method:
FutureBuilder<List<String>>(
future: _getStrings,
builder: (BuildContext context, AsyncSnapshot<List<String>> snapshot) {
if(snapshot.hasData) {
return ListView.builder(
itemBuilder: (BuildContext context, int index) => MyCard(snapshot.data[index])
...
);
} else {
return MyLoadingScreen();
}
});

Refresh the page data when you go to this page in the flutter

I'm trying to write a small application in which I collect data through api. I take the data, everything works. I decided to make a navigation bar to switch between pages. But when I try on the pages they are empty. In order for the data to be updated on the page, I need to click "Hot reload". I will be grateful for your help.
My main.dart:
import 'package:flutter/material.dart';
import 'package:flutter_app_seals/model/dataArea_list/JsonDataArea.dart';
import 'package:flutter_app_seals/model/object_list/JsonObject.dart';
import 'package:flutter_app_seals/model/seals_list/JsonSeals.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new HomeScreen());
}
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Журнал пломби'),
),
// body: Seals(),
drawer: Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text("Seals List"),
trailing: Icon(Icons.arrow_back),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Seals()),
);
}
)
],
),
),
);
}
}
class Seals extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home:JsonParseSeals(),
);
}
}
My modul Seals:
import 'package:flutter/material.dart';
import 'package:flutter_app_seals/model/seals_list/SealsListGet.dart';
import 'package:flutter_app_seals/model/seals_list/ServicesSeals.dart';
class JsonParseSeals extends StatefulWidget {
//
JsonParseSeals() : super();
#override
_JsonParseSealsState createState() => _JsonParseSealsState();
}
class _JsonParseSealsState extends State <StatefulWidget> {
//
List<SealList> _seals;
bool _loading;
#override
void initState(){
super.initState();
_loading = true;
Services.getSeals().then((seals) {
_seals =seals;
_loading = false;
}
);
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Список пломби'),
),
body: ListView.builder(
physics: BouncingScrollPhysics(),
padding: EdgeInsets.all(40),
itemCount: null == _seals ? 0 :_seals.length,
itemBuilder: (_,index) => Card(
color: Colors.red[300],
margin: EdgeInsets.symmetric(vertical: 7),
child:ListTile(
title: Text(_seals[index].sealNumber,
style: TextStyle(fontSize: 30),
),
subtitle: Text(
"${_seals[index].used}" ),
leading: Icon(Icons.local_activity,
size: 40,
color: Colors.black87,
),
),
),
),
);
}
}
My code :
Code after change:
Try to wrap your screen with data in FutureBuilder (you can read more about this widget here):
class _JsonParseSealsState extends State <StatefulWidget> {
#override
Widget build(BuildContext context) {
return FutureBuilder<List<SealList>>(
future: Services.getSeals(),
builder: (context, snapshot) {
// Data is loading, you should show progress indicator to a user
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
// Data is loaded, handle it
return ListView.builder(
physics: BouncingScrollPhysics(),
padding: EdgeInsets.all(40),
itemCount: snapshot.data.length,
itemBuilder: (_, index) {
final item = snapshot.data[index];
return Card(
color: Colors.red[300],
margin: EdgeInsets.symmetric(vertical: 7),
child: ListTile(
title: Text(
item.sealNumber,
style: TextStyle(fontSize: 30),
),
subtitle: Text("${item.used}"),
leading: Icon(
Icons.local_activity,
size: 40,
color: Colors.black87,
),
),
);
},
),
}
);
}
}

Flutter: not being able to show all incoming data

I was using shared_preferences plugin in my Flutter application, I set my data when I click the button, but I try to get my data is not coming.
I set my data here and when I print it, I can see the output on my console.
shows.forEach((element) {
showCode=element.showName;
show.setString('showName', showCode);
});
I want to get my data here, above the override method. But when I print here, I just see 1 item, but I need to print 2 items.
String showCode;
Future<String> getCode() async{
final codeValue = await SharedPreferences.getInstance();
setState((){
showyCode =codeValue.getString('showName');
});
print('here $showCode');
return showCode;
}
But I cannot call this future in my function, even if I have 2 data to come but I just see 1 item. I tried to include it in Listview then it fills an entire list with 1 item. Anyone have an idea where I'm doing wrong?
_showProgramData
? Container(
height: MediaQuery.of(context).size.height/2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
children: [
IconButton(icon:Icon(Icons.close) , onPressed:() {Navigator.pushNamed(context, SecondScreen.routeName);}),
Text('Please Select Show',style: TextStyle(fontWeight:FontWeight.bold,fontSize: 24),),
],
),
Text(showCode),
],
),
),
)
You can copy paste run full code below
Step 1: You need to use setStringList and getStringList
Step 2: getCode() return Future<List<String>>
Step 3: In forEach use showCodeList.add(element.showName);
code snippet
void _setData() async {
shows.forEach((element) {
showCodeList.add(element.showName);
});
final codeValue = await SharedPreferences.getInstance();
await codeValue.setStringList('showName', showCodeList);
setState(() {});
}
Future<List<String>> getCode() async {
final codeValue = await SharedPreferences.getInstance();
showCodeList = await codeValue.getStringList('showName');
if (showCodeList == null) {
showCodeList = [];
}
print('here ${showCodeList.toString()}');
return showCodeList;
}
...
FutureBuilder(
future: _future,
builder: (context, AsyncSnapshot<List<String>> snapshot) {
...
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Card(
elevation: 6.0,
child: Padding(
padding: const EdgeInsets.only(
top: 6.0, bottom: 6.0, left: 8.0, right: 8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(snapshot.data[index]),
working demo
full code
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() async{
WidgetsFlutterBinding.ensureInitialized();
final codeValue = await SharedPreferences.getInstance();
await codeValue.setStringList('showName', []);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class Show {
String showName;
Show({this.showName});
}
class _MyHomePageState extends State<MyHomePage> {
Future<List<String>> _future;
List<Show> shows = [Show(showName: "a"), Show(showName: "b")];
String showCode;
List<String> showCodeList = [];
void _setData() async {
shows.forEach((element) {
showCodeList.add(element.showName);
});
final codeValue = await SharedPreferences.getInstance();
await codeValue.setStringList('showName', showCodeList);
setState(() {});
}
Future<List<String>> getCode() async {
final codeValue = await SharedPreferences.getInstance();
showCodeList = await codeValue.getStringList('showName');
if (showCodeList == null) {
showCodeList = [];
}
print('here ${showCodeList.toString()}');
return showCodeList;
}
#override
void initState() {
_future = getCode();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FutureBuilder(
future: _future,
builder: (context, AsyncSnapshot<List<String>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('none');
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
case ConnectionState.active:
return Text('');
case ConnectionState.done:
if (snapshot.hasError) {
return Text(
'${snapshot.error}',
style: TextStyle(color: Colors.red),
);
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Card(
elevation: 6.0,
child: Padding(
padding: const EdgeInsets.only(
top: 6.0, bottom: 6.0, left: 8.0, right: 8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(snapshot.data[index]),
],
),
));
});
}
}
}),
floatingActionButton: FloatingActionButton(
onPressed: _setData,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

Change the color of the container when clicked on it in my flutter app

I fetched some interest(data) through an API and show them using future builders as containers. I want to change the background color of the container when I clicked on it. Here is what I did and it's changing the background color of all the containers when I clicked on one.
I added an if condition to the color of the container to check whether it is clicked or not
color: isClicked? Colors.white : Color(0xFFFFEBE7),
and set the isClicked state to true when clicked.
bool isClicked = false;
FutureBuilder(
future: GetInterests.getInterests(),
builder: (context, snapshot) {
final datalist = snapshot.data;
if (snapshot.connectionState ==
ConnectionState.done) {
return Expanded(
child: SizedBox(
height: 35,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return Wrap(
direction: Axis.vertical,
children: <Widget>[
GestureDetector(
onTap: (){
final inte_id = "${datalist[index]['_id']}";
log(inte_id);
setState(() {
isClicked = true;
});
},
child: new Container(
margin: EdgeInsets.only(right: 7),
height: 30,
width: MediaQuery.of(context)
.size
.width /
5.2,
decoration: BoxDecoration(
color: isClicked? Colors.white : Color(0xFFFFEBE7),
border: Border.all(
color: Color(0xFFE0E0E0)),
borderRadius:
BorderRadius.only(
topLeft:
Radius.circular(
50.0),
topRight:
Radius.circular(
50.0),
bottomRight:
Radius.circular(
50.0),
bottomLeft:
Radius.circular(
0.0))),
child: Center(
child: Text(
"${datalist[index]['iname']}",
style: TextStyle(
fontFamily: 'Montserrat',
color: Color(0xFFFF5E3A),
fontSize: 13),
),
),
),
),
],
);
},
itemCount: datalist.length,
),
),
);
}
return Padding(
padding: const EdgeInsets.only(left: 140.0),
child: Center(
child: CircularProgressIndicator(),
),
);
},
)
I was able to print the interest id in the console which belongs to the container I clicked on. but don't know how to change its color only
Instead of this you can use a variable to store selectedIndex and check if the currentIndex is selected or not and compare if currentIndex is selected or not and style the selected widget.
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
_MyWidgetState createState()=>_MyWidgetState();
}
class _MyWidgetState extends State<MyWidget>{
List _selectedIndexs=[];
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 4,
itemBuilder: (ctx,i){
final _isSelected=_selectedIndexs.contains(i);
return GestureDetector(
onTap:(){
setState((){
if(_isSelected){
_selectedIndexs.remove(i);
}else{
_selectedIndexs.add(i);
}
});
},
child:Container(
color:_isSelected?Colors.red:null,
child:ListTile(title:Text("Khadga")),
),
);
}
);
}
}
modify your listview builder as i have done in above case.
While the accepted answer will work, a much more sophisticated architecture using ChangeNotifier and package provider will produce more loosely coupled, better code, in some folks opinion.
Combining ideas from the following
https://flutter.dev/docs/cookbook/networking/fetch-data
https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple
I was focused on architecture and data flow. Not on widget layout to match the original question's screenshot.
import 'dart:collection';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:provider/provider.dart';
// Model ---------------------------------------------------
class Interest with ChangeNotifier {
final String title;
bool _selected = false;
Interest({
#required this.title,
}) : assert(title != null);
factory Interest.fromMap(final Map<String, dynamic> map) {
return Interest(
title: map['title'],
);
}
bool get selected {
return this._selected;
}
void select() {
this._selected = true;
this.notifyListeners();
}
void toggleSelect() {
this._selected = !this._selected;
this.notifyListeners();
}
}
class Interests extends ChangeNotifier {
final List<Interest> _interests = <Interest>[];
Interests();
factory Interests.fromList(final List<Map<String, dynamic>> list) {
final Interests interests = Interests();
for (final Map<String, dynamic> map in list) {
interests.add(Interest.fromMap(map));
}
return interests;
}
int get length {
return this._interests.length;
}
Interest operator [](final int index) {
return this._interests[index];
}
UnmodifiableListView<Interest> get interests {
return UnmodifiableListView<Interest>(this._interests);
}
void add(final Interest interest) {
this._interests.add(interest);
this.notifyListeners();
}
void selectAll() {
for (final Interest interest in this._interests) {
interest.select();
}
}
}
// Services ------------------------------------------------
Future<Interests> fetchInterests() async {
// Some data source that has a list of objects with titles.
final response = await http.get('https://jsonplaceholder.typicode.com/posts');
if (response.statusCode == 200) {
return Interests.fromList(json.decode(response.body).cast<Map<String, dynamic>>());
} else {
throw Exception('Failed to load post');
}
}
// User Interface ------------------------------------------
void main() {
runApp(InterestsApp());
}
class InterestsApp extends StatelessWidget {
#override
Widget build(final BuildContext context) {
return MaterialApp(
title: 'Interests App',
theme: ThemeData(primarySwatch: Colors.blue),
home: InterestsPage(),
);
}
}
class InterestsPage extends StatelessWidget {
#override
Widget build(final BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Interests')),
body: InterestsBody(),
);
}
}
class InterestsBody extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _InterestsBodyState();
}
}
class _InterestsBodyState extends State<InterestsBody> {
Future<Interests> _futureInterests;
#override
void initState() {
super.initState();
this._futureInterests = fetchInterests();
}
#override
Widget build(final BuildContext context) {
return FutureBuilder<Interests>(
future: this._futureInterests,
builder: (final BuildContext context, final AsyncSnapshot<Interests> snapshot) {
if (snapshot.hasData) {
return ChangeNotifierProvider.value(
value: snapshot.data,
child: InterestsList(),
);
} else if (snapshot.hasError) {
return Center(child: Text("${snapshot.error}"));
}
return Center(child: CircularProgressIndicator());
},
);
}
}
class InterestsList extends StatelessWidget {
#override
Widget build(final BuildContext context) {
return Consumer<Interests>(
builder: (final BuildContext context, final Interests interests, final Widget child) {
return Column(
children: <Widget>[
Center(
child: RaisedButton(
child: Text("Select All"),
onPressed: () {
interests.selectAll();
},
),
),
Expanded(
child: ListView.builder(
itemCount: interests.length,
itemBuilder: (final BuildContext context, final int index) {
return ChangeNotifierProvider<Interest>.value(
value: interests[index],
child: InterestTile(),
);
},
),
),
],
);
},
);
}
}
class InterestTile extends StatelessWidget {
#override
Widget build(final BuildContext context) {
return Consumer<Interest>(
builder: (final BuildContext context, final Interest interest, final Widget child) {
return ListTile(
title: Text(interest.title),
trailing: interest.selected ? Icon(Icons.check) : null,
onTap: () {
interest.toggleSelect();
},
);
},
);
}
}
you also can use flutter chip or ActionChip or ChoiceChip
class MyThreeOptions extends StatefulWidget {
#override
_MyThreeOptionsState createState() => _MyThreeOptionsState();
}
class _MyThreeOptionsState extends State<MyThreeOptions> {
int? _value = 1;
#override
Widget build(BuildContext context) {
return Wrap(
children: List<Widget>.generate(
3,
(int index) {
return ChoiceChip(
label: Text('Item $index'),
selected: _value == index,
onSelected: (bool selected) {
setState(() {
_value = selected ? index : null;
});
},
);
},
).toList(),
);
}
}

Flutter :How to build a infinite list using future builder that display n-record at a time

I am trying to build a list view in flutter that load data base on index and record per page
I am able to display a fix number of record but need some help how get and display the next set of record and so on
Here is my code snippet
Widget build(BuildContext context) {
return SafeArea(
child: Column(
children: <Widget>[
searchBoxWidget(),
Expanded(
child: FutureBuilder(
future: getRecordToDisplay(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.active:
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
case ConnectionState.done:
if (snapshot.hasError) {
return Text('You have some error : ');
} else if (snapshot.data != null) {
return buildListView(snapshot);
} else {
return Text('You have some error : ');
}
}
},
),
),
],
));
}
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {}
});
}
Future<Jobs> getRecordToDisplay() async {
return await getJobs(startPage, recordPerFetch);
}
ListView buildListView(AsyncSnapshot snapshot) {
return ListView.builder(
itemCount: snapshot.data.hits.length,
controller: _scrollController,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(
lobId: snapshot.data.hits[index].lobId,
atsReference: snapshot.data.hits[index].atsReference),
),
);
},
child: Container(
// width: MediaQuery.of(context).size.width,
padding: const EdgeInsets.all(14.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
child: Padding(
padding:
const EdgeInsets.only(top: 8.0, bottom: 8.0),
child: Text(
snapshot.data.hits[index].title,
style: TextStyle(
color: Color(0xff2175D9),
fontSize: 18.0,
),
),
),
),
Icon(
Icons.arrow_forward,
color: Colors.blue,
)
],
),
Text(
snapshot.data.hits[index].jobLocation.city +
" , " +
snapshot
.data.hits[index].jobLocation.stateAbbreviation,
style: TextStyle(
color: Color(0xff0f1941),
fontSize: 16.0,
),
),
SizedBox(
height: 8.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
snapshot.data.hits[index].salary.salaryString,
style: TextStyle(
color: Color(0xff0f1941),
fontSize: 16.0,
),
),
Text(
snapshot.data.hits[index].createdDate,
style: TextStyle(
color: Color(0xff0f1941),
fontSize: 14.0,
),
),
],
),
SizedBox(
height: 8.0,
),
Divider(color: Colors.brown),
],
),
));
});
}
So, it loads the first with n record but I don't know how to load the next set of pages when you reach the bottom of the current record with a future builder.
Thanks for your help
If you're expecting an infinite list to be displayed, won't StreamBuilder be better? Do you have a particular use case for the need to use FutureBuilder specifically? Here's a simple demo that uses Firestore to provide data, and ListView.builder with pagination.
This sample implements snippets from Firebase official doc for Firestore pagination.
There are two ways to load the data on the view in this demo.
Refresh the entire ListView using
RefreshIndicator
Scroll down to hit the bottom of the list to load up the next documents in the ListView.
ScrollController
is used to determine if the user has hit the bottom part of the list.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'DocObj.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Firebase
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var scrollController = ScrollController();
#override
void initState() {
super.initState();
getDocuments();
scrollController.addListener(() {
if (scrollController.position.atEdge) {
if (scrollController.position.pixels == 0)
print('ListView scroll at top');
else {
print('ListView scroll at bottom');
getDocumentsNext(); // Load next documents
}
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: listDocument.length != 0
? RefreshIndicator(
child: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
controller: scrollController,
itemCount: listDocument.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${listDocument[index].documentName}'),
);
},
),
onRefresh: getDocuments, // Refresh entire list
)
: CircularProgressIndicator(),
),
);
}
List<DocObj> listDocument;
QuerySnapshot collectionState;
// Fetch first 15 documents
Future<void> getDocuments() async {
listDocument = List();
var collection = FirebaseFirestore.instance
.collection('sample_pagination')
.orderBy("name")
.limit(15);
print('getDocuments');
fetchDocuments(collection);
}
// Fetch next 5 documents starting from the last document fetched earlier
Future<void> getDocumentsNext() async {
// Get the last visible document
var lastVisible = collectionState.docs[collectionState.docs.length-1];
print('listDocument legnth: ${collectionState.size} last: $lastVisible');
var collection = FirebaseFirestore.instance
.collection('sample_pagination')
.orderBy("name").startAfterDocument(lastVisible).limit(5);
fetchDocuments(collection);
}
fetchDocuments(Query collection){
collection.get().then((value) {
collectionState = value; // store collection state to set where to start next
value.docs.forEach((element) {
print('getDocuments ${element.data()}');
setState(() {
listDocument.add(DocObj(DocObj.setDocDetails(element.data())));
});
});
});
}
}
To parse the data inside the document, you can create a model for your object.
class DocObj {
var documentName;
DocObj(DocObj doc) {
this.documentName = doc.getDocName();
}
dynamic getDocName() => documentName;
DocObj.setDocDetails(Map<dynamic, dynamic> doc)
: documentName = doc['name'];
}
The sample handles this data from Firestore.
Here's how the app looks when running.