How to Pull Refresh FutureBuilder<List<Data>> (); page in flutter? - flutter

here im try to use FutureBuilder for my list but I can't refresh by on pullRefresh
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _refreshPhotos, // fatch snapshot.data!
child: FutureBuilder<String>(
future: userId as Future<String>,
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return LayoutBuilder(builder: (context, constraints) {
return ListView(
scrollDirection: Axis.vertical,
children: [
AddBanners(userId: snapshot.data!), // future builder,it fatches data from api
DealsOfTheDay(userId: snapshot.data!), //future builder, , it fatches data from api
]);
});
} else {
return Center(child: JumpingText('Loading...'));
}
}),
);
I want fresh these widgets along with
refreshPhotos()
AddBanners(userId: snapshot.data!),
DealsOfTheDay(userId: snapshot.data!)

If you are looking for pull to refresh. Wrap your widgets with 'RefreshIndicator' widget on your desired screen.
Here is an example of my home screen which has pull to refresh.
#override
Widget build(BuildContext context) {
return Scaffold(
key: _con.scafoldKey,
body: WillPopScope(
onWillPop:() => DeviceUtils.instance.onWillPop(),
child: SafeArea(
child: Container(
color: ColorUtils.themeColor,
child: RefreshIndicator( //Just add this to your screen
color: ColorUtils.themeColor,
key: _con.refreshIndicatorKey,
strokeWidth: 4,
displacement: 80,
onRefresh: _refresh, //this is a function which you need to place under your home view state
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
child: Container(
color: Colors.white,
child: /// some more widgets
),
),
),
),
);
}
After adding the refresh indicator to your widgets, you need to add the _refresh function which will have all your api's that you want to reload.
Future<Null> _refresh() async{
//these two are my api's that i want to reload everytime an user pulls to refresh screen. You have to add your own apis here.
_con.getProfile(context);
_con.getUpcoming(context);
}
Voila. Now your user can reload data in the page and get the new state.
Hope this answers your question.
If the above is not what you want. You can use setState() inside your future builder. See the code below for example:
class _MyHomePageState extends State<MyHomePage> {
Future<List<String>> _myData = _getData(); //<== (1) here is your Future
#override
Widget build(BuildContext context) {
var futureBuilder = new FutureBuilder(
future: _myData; //<== (2) here you provide the variable (as a future)
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return new Text('loading...');
default:
if (snapshot.hasError)
return Column(
children: [
Icon(Icons.error),
Text('Failed to fetch data.'),
RaisedButton(
child: Text('RETRY'),
onPressed: (){
setState(){
_myData = _getData(); //<== (3) that will trigger the UI to rebuild an run the Future again
}
},
),
],
);
else
return createListView(context, snapshot);
}
},
);
return new Scaffold(
appBar: new AppBar(
title: new Text("Home Page"),
),
body: futureBuilder,
);
}
setState() will rebuild the widget with new values.

you can simply use in your main screen
setState((){});
it will rebuild all of the futureBuilder widgets in your screen and retrieve new data

Related

How to update stream.length of FirebaseFirestore data after record addition or deletion?

The code below displays list of records from FirebaseFirestore using AsyncSnapshot with StreamBuilder. It works great, however I want to display the total number of records in the AppBar title and tht works when the app is launched, but doesn't update after any addition or deletion.
Question: How can I update the number of records (and display in Appbar title) after the list has an addition or deletion?
Note that I'm displaying the total number of records in the AppBar title using title: Text('# Waiting: $numberWaiting'),, but I can't figure out how to refresh this after the list changes. Any suggestions are greatly appreciated.
class HomePageState extends State<HomePage> {
Query waitingList = FirebaseFirestore.instance
.collection('waiting')
.orderBy('Time_In');
int numberWaiting = 0; // Starts at 0; updated in StreamBuilder
Future<void> delete(String docID) async {
await FirebaseFirestore.instance.collection('waiting').doc(docID).delete();
// TODO: How to update numberWaiting in AppBar title?
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("# Waiting: ${numberWaiting.toString()}"),
),
body: SizedBox(
width: double.infinity,
child: Center(
child: StreamBuilder(
stream: waitingList.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Column(
...
);
}
else if (snapshot.hasData) {
return ListView.builder (
itemCount: snapshot.data?.docs.length,
itemBuilder: (BuildContext context, index) {
numberWaiting = index + 1;
String name = snapshot.data?.docs[index]['Name'];
return Card(
child: SizedBox(
child:ListTile(
title:
Row(
children: <Widget>[
Text(name),
],
),
onTap: () {
// Create or Update Record
// TODO: Update numberWaiting for title
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context){
return CrudPage(
docId: snapshot.data?.docs[index].id.toString() ?? "",
docSnap: snapshot.data?.docs[index]);
}));
},
onLongPress: () {
// Delete Record
// TODO: Update numberWaiting for title
delete(snapshot.data?.docs[index].id.toString() ?? "");
},
),
),
);
},
);
}
else {
return const Text('No Data');
}
}, // Item Builder
),
),
),
);
}
}
Unfortunately this code only updates the # Waiting: X title once and doesn't refresh when an item is deleted or added.
Thank you for your help!
Simply update value and rebuild on "else if (snapshot.hasData)"
class HomePageState extends State {
Query waitingList = FirebaseFirestore.instance
.collection('waiting')
.orderBy('Time_In');
Future<int> countStream(Stream<QuerySnapshot<Object?>> stream) async =>
stream.length;
#override
Widget build(BuildContext context) {
var numberWaiting = "";
return Scaffold(
appBar: AppBar(
title: Text("# Waiting: $numberWaiting"),
),
body: SizedBox(
width: double.infinity,
child: Center(
child: StreamBuilder(
stream: waitingList.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Column(
...
);
}
else if (snapshot.hasData) {
setState((){
numberWaiting = snapshot.data?.docs.length.toString();
})
return ListView.builder (
itemCount: snapshot.data?.docs.length,
itemBuilder: (BuildContext context, index) {
String name = snapshot.data?.docs[index]['Name'];
return Card(
child: SizedBox(
child:ListTile(
title:
Row(
children: <Widget>[
Text(name),
],
),
),
),
);
},
);
}
else {
return const Text('No Data');
}
}, // Item Builder
),
),
),
);
}
}

How to get ListView Component to scroll up on TextFieldInput

UPDATE 7/15/2021:
I found another similar question that helped me a bit:
Programmatically scrolling to the end of a ListView
As of now I am able to get it somewhat looking like what I want it to, but I am still getting an overflow issue.
Here is the updated Code
home.dart
class _HomePageState extends State<HomePage> {
// added a scroll controller to control my ListView
ScrollController scrollController = ScrollController();
void addEntry() {
setState(() {
entryTextController.text = '';
this.newEntry = !this.newEntry;
});
// on adding an item, I will scroll up in the ListView
Timer(
Duration(milliseconds: 100),
() =>
scrollController.jumpTo(scrollController.position.maxScrollExtent)
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Container(),
// instead of just the EntryList, I added a container
// with a fixed height. I'll probably change this later
Container(
height: 500.0,
child: EntriesList(props)
)
],
),
floatingActionButton: FloatingActionButton(onTap: addEntry)
);
}
}
EntryList.dart
class _EntriesListState extends State<EntriesList> {
List<Widget> getEntries(snapshot) {
myList = entryList.map<Widget>((entry) {
return Entry();
}).toList();
myList.add(Visibility(
visible: widget.newEntry,
child: Card(
child: ListTile(
title: TextFormField(),
))));
// This is the newest change. By adding a Placeholder, I am able to
// bring the screen up on the scrollController bringing this into
// position so that the user can then see the TextFormField
myList.add(Opacity(opacity: 0.0, child: Placeholder()))
return myList;
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _entryStream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
// Async code here ...
return ListView(
children: getEntries(snapshot),
padding: EdgeInsets.all(8.0),
shrinkWrap: true,
);
},
);
}
}
ORIGINAL:
I'm working on adding an entry to my ListView widget. Within my ListView Widget, I have embedded a hidden TextFieldInput that is shown once a user clicks the addEntry button.
My main issue is that once the list gets long enough, my list does not scroll up to allow the user to see their new entry that they are typing.
Here is my current code. In order to keep my question concise, I have removed some unrelated code.
main.dart
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Container(), // top widget here
// Scrollable list of dates
EntriesList(),
],
),
floatingActionButton: FloatingActionButton()
);
}
}
EntriesList.dart
class _EntriesListState extends State<EntriesList> {
List<Widget> getEntries(snapshot) {
myList = entryList.map<Widget>((entry) { // getting my entry widgets here
return Entry();
}).toList();
myList.add(Visibility( // adding my hidden text input widget here
visible: widget.newEntry,
child: Card(
child: ListTile(
title: TextFormField(),
))));
return myList;
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _entryStream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
return ListView( // I added several properties in hopes that it would
children: getEntries(snapshot), // work but to no avail
padding: EdgeInsets.all(8.0),
shrinkWrap: true,
physics:
BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
);
},
);
}
}
Use the SingleChildScrollView widget. Can be used in HomePage or EntriesList by simply wrapping it with the SingleChildScrollView widget. Like this in the home page:
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Container(), // top widget here
// Scrollable list of dates
SingleChildScrollView(child:widgetEntriesList()),
],
),
floatingActionButton: FloatingActionButton()
);
}
}
Or like this in the EntriesList:
class _EntriesListState extends State<EntriesList> {
List<Widget> getEntries(snapshot) {
myList = entryList.map<Widget>((entry) { // getting my entry widgets here
return Entry();
}).toList();
myList.add(Visibility( // adding my hidden text input widget here
visible: widget.newEntry,
child: Card(
child: ListTile(
title: TextFormField(),
))));
return myList;
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _entryStream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
return SingleChildScrollView(
child:ListView(
children: getEntries(snapshot), // work but to no avail
padding: EdgeInsets.all(8.0),
shrinkWrap: true,
physics:BouncingScrollPhysics(),
)
);
},
);
}
}
Instead of wrapping ListView, you could instead wrap the StreamBuilder as well.

StreamBuilder is not showing data from firestore

I am using streambuilder to display snapshot data but it is not displaying. The screen is just blank but When I use the future builder with get() methode it display the data but I want realtime changes. I am new to flutter please help me with this. here is code.
class TalentScreen2 extends StatelessWidget {
final Query _fetchFavUser = FirebaseRepo.instance.fetchFavUsers();
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Column(
children: [
Text('Talent Screen 2(Favourites)'),
Expanded(child: _retrieveData(context))
],
),
),
);
}
Widget _retrieveData(BuildContext context) => StreamBuilder<QuerySnapshot>(
stream: _fetchFavUser.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) return const Text('Something went wrong');
if (!snapshot.hasData) return const Text('Alas! No data found');
if (snapshot.connectionState == ConnectionState.waiting)
return Center(
child: CircularProgressIndicator(
strokeWidth: 2.0,
));
if (snapshot.connectionState == ConnectionState.done)
return theUserInfo(snapshot.data.docs);
return Container();
});
Widget theUserInfo(List<QueryDocumentSnapshot> data) {
return ListView.builder(
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
var uid = data[index]['uid'];
TalentHireFavModel userData = TalentHireFavModel.fromMap(
data[index].data(),
);
return Card(
child: Column(
children: <Widget>[
Text(data[index]['orderBy']),
// Text(userData.name ?? ''),
Text(userData.categories),
Text(userData.skills),
// Text(userData.country ?? ''),
Text(userData.phoneNo),
Text(userData.hourlyRate),
Text(userData.professionalOverview),
Text(userData.skills),
Text(userData.expert),
// Text(userData.createdAt ?? ''),
_iconButton(userData.uid, context),
],
),
);
});
}
Future<DocumentSnapshot> fetch(data) async =>
await FirebaseRepo.instance.fetchWorkerUserData(data);
Widget _iconButton(uid, context) {
return IconButton(
icon: Icon(Icons.favorite),
onPressed: () {
BlocProvider.of<TalentFavCubit>(context).removeTalentFav(uid);
});
}
}
and here is the firestore query methode where I am just applying simple query to fetch all documents and display them. I want real-time changes
Query fetchFavUsers() {
var data = _firestore
.collection('workerField')
.doc(getCurrentUser().uid)
.collection('favourites')
// .where('uid', isNotEqualTo: getCurrentUser().uid)
.orderBy('orderBy', descending: true);
return data;
}
The solution is to just return the function. Get that method out of if statement and place it in just return statement.

How to properly "refresh" Widgets inside FutureBuilder()

I have a Listview.builder() inside a FutureBuilder() that displays data fetched from API. I can retrieve the data successfully. But when I call the refreshData() function, previous data gets appended in the list.. How do I properly 'refresh' the widgets inside a FutureBuilder()?
Note: I'm only using get request here, so it's impossible that the data gets duplicated in the back-end. So the problem actually lies in displaying the data.
Here is my code:
class _MyHomePageState extends State<MyHomePage> {
List<Giver> _givers = [];
Future giversList;
getData() async {
_givers.addAll(await NetworkHelper().fetchGivers());
return _givers;
}
refreshData() {
giversList = getData();
}
#override
void initState() {
super.initState();
giversList = getData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: (){
setState(() {
refreshData();
});
},
child: Text('Refresh'),
),
FutureBuilder(
future: giversList,
builder: (context, snapShot){
switch(snapShot.connectionState) {
case ConnectionState.none:
return Center(child: Text('none'));
case ConnectionState.active:
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
//this is where the listview is created
case ConnectionState.done:
return ListView.builder(
shrinkWrap: true,
itemCount: _givers.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapShot.data[index].name),
subtitle: Text(snapShot.data[index].address),
);
});
default:
return Center(child: Text('Default!'));
}
},
)
],
),
),
);
}
}
As #pskink mentioned in the comment above, I just replaced _givers.addAll(await NetworkHelper().fetchGivers()); with _givers = await NetworkHelper().fetchGivers();
Thanks for the help!

Open new screen when backend response is success

I have a SplashScreen that retrieve information from my Api. When response is success i would like open my second screen but I have a error because after my method to open new screen I need to add some return widget.
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: StreamBuilder(
stream: bloc.appInformation,
builder: (context, AsyncSnapshot<AppInformationModel> snapshot){
return onResponse(snapshot);
},
),
);
}
My response method look like this.
Widget onResponse(AsyncSnapshot<AppInformationModel> snapshot) {
if (snapshot.hasData) {
openMain();
return Center(
child: Text(
snapshot.data.version,
style: TextStyle(color: Colors.white, fontSize: 30.0),
),
);
} else if (snapshot.hasError) {
return Text('Error');
}
return Center(child: CircularProgressIndicator());
}
And openMain() is just
void openMain() {
Navigator.pushNamed(context, 'home');
}
My second screen work perfect.
The error is
setstate or markneedsbuild called during build onTap
I understand is because my return widget is after opanNavigation() but i must return a view.
Thanks