Update Appbar after stream builder is done - flutter

After stream builder is done, it has the numitems as number of valid items. This numitems should show on a cart icon in AppBar. Right now the it is showing no number on cart icon as the UI is built before the stream builder is fully done.
The stream builder is inside the body widget as it also displays a list of items on the screen. Thats why I dont prefer to havestream builder within AppBar. I was wondering what is the suitable way to update AppBar with numitems after stream builder is finished.
Here is the code:
class BasketPage extends StatefulWidget {
String title;
UserAccount useraccount;
BasketPage({required this.title, required this.useraccount});
#override
BasketPageState createState() {
return BasketPageState(title: this.title, useraccount: this.useraccount);
}
}
class BasketPageState extends State<BasketPage> {
int numitems = 0;
Stream<QuerySnapshot>? fooditemsStream;
CollectionReference? fooditems;
BasketPageState(){
this.fooditems = FirebaseFirestore.instance.collection('fooditems');
this.fooditemsStream = this.fooditems!
.where("receiever", isEqualTo: this.useraccount.uname)
.where("taken", isNull: true)
.snapshots();
}
BasketBloc? basketBloc;
List<FoodItem> foodItemsList = [];
#override
Widget build(BuildContext context) {
basketBloc = BlocProvider.of<BasketBloc>(context);
return Scaffold(
appBar: AppBar(
actions: [
Stack(
children: <Widget>[
const IconButton(
icon: Icon(
Icons.shopping_cart_sharp,
color: Colors.white,
),
onPressed: null,
),
this.numitems==0 // if numitems is 0 then return empty container else display numitems value
? Container()
: Positioned(
top: 0,
right: 0,
child: Stack(
children: <Widget>[
Container(
height: 20.0,
width: 20.0,
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: const Center(
child: Text(
"10",
style: TextStyle(
color: Colors.white,
fontSize: 11.0,
fontWeight: FontWeight.bold,
),),),),],),),],),],),
body: SingleChildScrollView(
child: SafeArea(
child: Column(
children: <Widget>[
BlocListener<BasketBloc,BasketState>(
listener: (context,state){},
child: BlocBuilder<BasketBloc,BasketState>(
builder: (context,state) {
return Container();
}),),
Flexible(
child:
Column(
children: [
SizedBox(
child: StreamBuilder<QuerySnapshot>(
stream: fooditemsStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
if (snapshot.data!.docs.length == 0){
return Text("Currently there are no food items in the list");
}
// else return list and update numitems when building list
return Column(
children: [
ListView(
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data = document.data()! as Map<String, dynamic>;
final bool isExpired = data['expired'];
if (isExpired) return Container();
// update numitems here
numitems= numitems+1;
return ListTile(
contentPadding:const EdgeInsets.only(top: 10.0, left: 15.0, right: 15.0, bottom:5.0),
title: Padding(
padding: const EdgeInsets.only(bottom:8.0),
child: Text(capitalize(data['item_name']),
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
),
);
}).toList(),
),],);},),),],)),],),),);}}
Thanks!

The easiest solution is just to add setState() after you update numitems = numitems + 1. This will make your whole BasketPage to rebuild.
But it's not the best solution, because your it causes your whole page to rebuild, when it is unnecessary. Better solution would be to use ValueNotifier. Wrap your AppBar with ValueListenableBuilder and create ValueNotifier.
As a side note you don't need to pass anything to your BasketPageState. If you need to access them use widget.title

Related

FutureBuilder shows [instance] instead of actual data

I want to download a list from firestore and display as a list in flutter widget. The data is successfully downloaded (proved by the print(cp.data()).
However, the result shown is [Instance of '_JsonQueryDocumentSnapshot'] instead of the actual carpark data.
Could anyone pls help point out what the bug is.
Thanks
class DownloadDataScreen extends StatefulWidget {
#override
_DownloadDataScreen createState() => _DownloadDataScreen();
}
class _DownloadDataScreen extends State<DownloadDataScreen> {
List<DocumentSnapshot> carparkList = []; //List for storing carparks
void initState() {
super.initState();
readFromFirebase();
}
Future readFromFirebase() async {
// await FirebaseFirestore.instance
await FirebaseFirestore.instance
.collection('carpark')
.get()
.then((QuerySnapshot snapshot) {
snapshot.docs.forEach(
(DocumentSnapshot cp) {
carparkList.add(cp);
print('printing cp');
print(cp.data());
},
);
});
**return carparkList;**
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: new Text(
'Car Park',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
centerTitle: true,
),
body: FutureBuilder(
future: readFromFirebase(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
List<Widget> children;
if (snapshot.hasData) {
children = <Widget>[
const Icon(
Icons.check_circle_outline,
color: Colors.green,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Column(
children: [
Text('Result: ${snapshot.data}'),
],
),
)
];
} else if (snapshot.hasError) {
children = <Widget>[
const Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'),
)
];
} else {
children = const <Widget>[
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting result...'),
)
];
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
),
);
},
),
);
}
}
First, you don't need to call the function from the init because you already use the FutureBuilder.
Also, you don't need to cast it because when the future completes, the async snapshot already would provide you a list of DocumentSnapshot in the data and the .doc propertie.
Like this:
FutureBuilder<QuerySnapshot>(
builder:(context,snapshot){
if(snapshot.hasData){
/// here your data
snapshot.data.docs;
}
)

Flutter List View Builder Using API

I am able to fetch data from API and I can show it in the List View, but i want to show the first card as the latest updated data.
I tried to use the Reverse property of List View Builder, it some how solve my problem but gave rise to another problem. Now i need to scroll upward in order to see latest updated data.
Help me through it
I just want to show the latest updated data at the top and then scroll down to see another data.
Please Help Me.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class PromotersDetails extends StatefulWidget {
final String url,title;
PromotersDetails({Key key, #required this.url, #required this.title}) : super(key: key);
#override
_PromotersDetailsState createState() => _PromotersDetailsState(url,title);
}
class _PromotersDetailsState extends State<PromotersDetails> {
fetchSelfies() async {
var url1;
url1 = await http.get(Uri.parse(
url));
var res = json.decode(url1.body);
print(res);
return json.decode(url1.body)['selfies'];
}
String url,title;
_PromotersDetailsState(this.url, this.title);
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width * 0.6;
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
centerTitle: false,
title: Text(
title,
style: TextStyle(fontSize: 25.0, color: Colors.white),
),
elevation: 0.0,
backgroundColor: Colors.green,
),
body: FutureBuilder(
future: fetchSelfies(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(snapshot.error.toString()),
);
}
if (snapshot.hasData) {
return ListView.builder( **// List View Builder**
reverse: false,
cacheExtent: 10000.00,
itemCount: snapshot.data.length,
padding: EdgeInsets.all(8),
itemBuilder: (BuildContext context, int index) {
return Row(
children: [
Container(
height: 120,
alignment: Alignment.center,
child: Container(
height: 120,
width: 120,
child: Card(
child: Image.network(snapshot.data[index]['image']),
),
),
),
Expanded(
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 10,
),
Row(
children: [
Text(
"Time: ",
style: TextStyle(color: Colors.black),
),
Text(
snapshot.data[index]['time'],
style: TextStyle(color: Color(0xff868597)),
),
],
),
Container(
height: 50,
child: Text(
snapshot.data[index]['location'],
style: TextStyle(color: Color(0xff868597)),
),
),
],
),
),
),
],
);
},
);
}
return Center(
child: CircularProgressIndicator(),
);
})
);
}
}
If you just need to sort the API response backwards, you can use the reverse property of ListView.
ListView.builder(
reverse: true,
...
),
If the way you're populating the ListView is by appending new data, then it's better to store the data on a List then do sort/reversed. Though the sorting should be best done at the backend instead of passing the burden to the client app.

Disabling FloatingActionButton after list view and showing a message that the limit is exceeded

How can I disable a button, in this case a FAB(Floating Action Button), and prevent the user adding more items to the list because he is exceeding the limit and showing a message? I'm using a FutureBuilder and a ListView.builder, after the user adds 3 item to the list, I want to disable the FAB and prevent the user to add more items to the list and showing some sort of notification/message/floating over button message to the user that he is exceeding the limit of 3 items per list. Here is the code:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'screens/add_items_screen.dart';
import 'components/items_list.dart';
class ItemScreen extends StatelessWidget {
static const String id = 'item_screen';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
iconTheme: IconThemeData(
color: Colors.grey[600],
),
),
/// I want to disable this button after the limit of 3 items in the list view
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.grey,
child: Icon(Icons.add),
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
child: AddItemScreen(),
)));
},
),
body: SafeArea(
child: SingleChildScrollView(
child: Container(
padding: EdgeInsets.all(20.0),
color: Colors.grey[100],
child: Container(
height: 800,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Item List Display',
style: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.w700,
),
),
SizedBox(
height: 20.0,
),
Expanded(
child: Column(
children: [
/// Here is where the items are rendered as a list
Expanded(child: ItemList()),
],
),
),
],
),
),
),
),
),
);
}
}
The Item list component:
import 'package:flutter/material.dart';
import 'common/api/api_caller.dart';
import 'utilities/item_api_helper.dart';
import 'models/item.dart';
import 'stores/item_store.dart';
import 'dart:async';
import 'package:provider/provider.dart';
class ItemList extends StatefulWidget {
#override
_ItemList createState() => _ItemList();
}
class _ItemList extends State<ItemList> {
Future<HTTPResponse<List<ItemData>>> _getItemList() async {
var _itemData = await APICaller.instance.getItems();
var provider = Provider.of<ItemDatStore>(context, listen: false);
provider.setItemList(_itemData.data, notify: false);
return _itemData;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: FutureBuilder<HTTPResponse<List<ItemData>>>(
future: _getItemList(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Consumer<ItemDataStore>(
builder: (context, itemData, child) {
return ListView.builder(
itemCount: itemData.itemList.length,
itemBuilder: (context, index) {
ItemData items =
itemData.getItemByIndex(index);
return Card(
child: Container(
padding: EdgeInsets.all(10.0),
child: Text('items.title'),
),
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return Container();
},
),
),
);
}
}
I've tried using an if statement where I can define if(itemData.itemList.length <3) it should return those three items, else {Text('Item limit of 3 is exceeded')} but I'm not sure if this is the right way or if I defined it right since I put it in the Item List component and I need to put it in the Item Screen. Thanks in advance for you help!
You can wrap your Floating action button with Visibility it hides and not woking anymore :)
Visibility(
visible: items.length > 3,
child : FAB here,
),
You can disable the FAB like this
onPressed: items.length < 3? addItemToList : null;
and addItemToList is
void addItemToList(){
// Do Something here
}

How to properly display all data from a Sqlite database into a ListView.builder?

I am attempting to access all of the the Data I have persisted into a sqlite db. The database is full of Quarter objects. I want to display them all using a ListView.Builder. Only issue it that I'm not exactly understanding the proper way how in my case. What I currently have errors out with the error type 'QueryRow' is not a subtype of 'List<dynamic>' I will mark where the IDE says this error is coming from. Any help is appreciated with showing me what I'm doing wrong and correcting me! Thanks!
This is what I currently have. I've marked where the error I detailed above is supposedly coming from
class ChangeQuarter extends StatefulWidget {
#override
_ChangeQuarterState createState() => _ChangeQuarterState();
}
class _ChangeQuarterState extends State<ChangeQuarter> {
Future<void> _future;
static const Color greycolor = Color.fromRGBO(220, 220, 220, 10);
#override
initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[200],
appBar: PreferredSize(
preferredSize: Size.fromHeight(95.0),
child: AppBar(
automaticallyImplyLeading: false, // hides leading widget
flexibleSpace: QuarterAppBar(),
),
),
body: FutureBuilder<dynamic>(
future: QuarterDBProvider.quarterDB.getQuarter(),
initialData: List(),
builder: (context, 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) {
print(
'${snapshot.error}',
);
}
}
=> ERROR LOCATION List quarter = snapshot.data;
return ListView.builder(
itemCount: snapshot.data.length,
shrinkWrap: true,
itemBuilder: (context, int index) {
final quarter = snapshot.data[index];
return Padding(
padding: EdgeInsets.symmetric(
vertical: 1.0, horizontal: 4.0),
child: Card(
color: (index % 2 == 0) ? greycolor : Colors.white,
child: Container(
height: 60,
padding: EdgeInsets.fromLTRB(0, 20, 0, 0),
child: InkWell(
onTap: () => Navigator.pop(context),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
margin: EdgeInsets.only(right: 5),
child: Text(snapshot.data[index].quarter,
style: TextStyle(
fontSize: 20,
fontFamily: 'Montserrat',
color: Colors.blue),
textAlign: TextAlign.left),
),
],
),
),
),
),
);
},
);
}));
}
}
The error is basically saying you are trying to use a List<dynamic> where you should be using a QueryRow. The QuarterDBProvider.quarterDB.getQuarter() returns a QueryRow and you are declaring the Future builder like this: FutureBuilder<List<dynamic>> whereas it should be FutureBuilder<QueryRow>. Also you should correct the List quarter = snapshot.data; into QueryRow quarter = snapshot.data;.
If you need any further help please let me know on what to do with the QueryRow but I assume you already know, because you are using SQlite

Flutter - Returning to previous page from AppBar is not refreshing the page, with Navigator.pop(context)

I was trying to get the list page refreshed if a method was run on another page. I do pass the context using the push navigation.
I tried to follow these 3 answers Answer 1 Answer 2 and Answer 3 and I am not able to manage the states here.
This is the first list page which needs to be refreshed. It calls a class
class _PageLocalState extends State<PageLocal> {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: SafeArea(
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: widget.allLocal.length,
//padding: const EdgeInsets.only(top: 10.0),
itemBuilder: (context, index) {
return LocalCard(widget.allLocal[index]);
},
)),
)
],
),
);
}
}
The next class:
class LocalCardState extends State<LocalCard> {
FavData localdet;
LocalCardState(this.localdet);
ListTile makeListTile() => ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0),
title: Text(
localdet.name,
style: TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(localdet.loc),
trailing: Icon(Icons.keyboard_arrow_right, size: 30.0),
onTap: () => navigateToDetail(localdet),
);
Widget get localCard {
return new Card(
elevation: 4.0,
margin: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
child: Container(
child: makeListTile(),
));
}
#override
Widget build(BuildContext context) {
return new Container(
child: localCard,
);
}
navigateToDetail(FavData localdet) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FavouriteDetailPage(
mndet: localdet,
)));
setState(() {});
}
}
Now this is routing to the final detail page:
class _FavouriteDetailPageState extends State<FavouriteDetailPage> {
bool isFav = false;
FavData mndet;
_FavouriteDetailPageState(this.mndet);
// reference to our single class that manages the database
final dbHelper = DatabaseHelper.instance;
#override
Widget build(BuildContext context) {
Widget heading = new Container(...);
Widget middleSection = new Expanded(...);
Widget bottomBanner = new Container(...);
Widget body = new Column(...);
final makeBottom = Container(
height: 55.0,
child: BottomAppBar(
color: Color.fromRGBO(36, 36, 36, 1.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new FavIconWidget(mndet),
],
),
),
);
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('The Details'),
backgroundColor: Color.fromRGBO(36, 36, 36, 1.0),
),
body: Container(
child: Card(
elevation: 5.0,
shape: RoundedRectangleBorder(
side: BorderSide(color: Colors.white70, width: 1),
borderRadius: BorderRadius.circular(10),
),
margin: EdgeInsets.all(20.0),
child: Padding(
padding: new EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
child: body,
),
),
),
bottomNavigationBar: makeBottom,
);
}
void share(BuildContext context, FavData mndet) {
final RenderBox box = context.findRenderObject();
final String shareText = "${mndet.name} - ${mndet.desc}";
Share.share(shareText,
subject: mndet.loc,
sharePositionOrigin: box.localToGlobal(Offset.zero) & box.size);
}
}
class FavIconWidget extends StatefulWidget {
final FavData mnforIcon;
FavIconWidget(this.mnforIcon);
#override
_FavIconWidgetState createState() => _FavIconWidgetState();
}
class _FavIconWidgetState extends State<FavIconWidget> {
final dbHelper = DatabaseHelper.instance;
Future<bool> get isFav async {
final rowsPresent = await dbHelper.queryForFav(widget.mnforIcon.id);
if (rowsPresent > 0) {
print('Card Loaded - Its Favourite already');
return false;
} else {
print('Card Loaded - It is not favourite yet');
return true;
}
}
void _insert() async {...}
void _delete() async {...}
#override
Widget build(BuildContext context) {
return FutureBuilder<bool>(
future: isFav,
initialData:
false, // you can define an initial value while the db returns the real value
builder: (context, snapshot) {
if (snapshot.hasError)
return const Icon(Icons.error,
color: Colors.red); //just in case the db return an error
if (snapshot.hasData)
return IconButton(
icon: snapshot.data
? const Icon(Icons.favorite_border, color: Colors.white)
: Icon(Icons.favorite, color: Colors.red),
onPressed: () => setState(() {
if (!snapshot.data) {
print('Its favourite so deleting it.');
_delete();
} else {
print('Wasnt fav in the first place so inserting.');
_insert();
}
}));
return CircularProgressIndicator(); //if there is no initial value and the future is not yet complete
});
}
}
I am sure this is just some silly coding I have done but just not able to find out. Where.
I tried adding Navigator.pop(context); in different sections of the detail page and it fails.
Currently, I have to navigate back to the Favourites list page and then HomePage and then back to Favourites ListPage to refresh the list.
try this.. Anywhere you are using Navigator.pop or Navigator.push .. Instead of this use this:
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (BuildContext context) => Password())
);
//instead of Password use the name of the page(the second page you want to go to)