I am creating a simple EXPENSE MANAGER app
I have divided screen in two section
Top Section for showing Two card of TotalIncome and TotalExpense and other section is showing All Transactions
Here, I have taken Streambuilder for showing all transaction, and with the help of this stream builder I have created Tow Global Variable totalincome and totalexpense
and showing total income and totalexpense to top section's Card
When I add any transaction, List of transaction refresh properly as it is due to Stream Builder but total income and expense card not refreshing...
here I want the proper way to do it...( like creating a method that fetch records from firebase and store into a List and to use this list for various needs...
here Is my code
Widget headerSummary(Size size) {
return Container(
height: size.height * 0.15,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(30), bottomLeft: Radius.circular(30)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: SummaryCard(
color: Colors.green,
amount: totalincome.toString(),
icondata: Icons.arrow_upward,
title: 'Income',
),
),
Expanded(
child: SummaryCard(
color: Colors.red,
amount: totalexpense.toString(),
icondata: Icons.arrow_downward,
title: 'Expense',
),
),
],
),
);
}
transaction
Widget showTransactions(Size size) {
return Container(
height: size.height * .65,
// color: Colors.red,
child: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('users')
.doc(widget.loggeduser.userid)
.collection('expenses').orderBy("date",descending: true)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.hasData) {
QuerySnapshot querysnapshot =
snapshot.data as QuerySnapshot;
if (querysnapshot.docs.length > 0) {
List<Map<String, dynamic>> transactionlist = [];
for (int x = 0; x < querysnapshot.docs.length; x ) {
Map<String, dynamic> expensemap = querysnapshot.docs[x]
.data() as Map<String, dynamic>;
transactionlist.add(expensemap);
}
var x=transactionlist.where((element) => element['isexpense']==true).toList();
totalexpense=x.fold(0, (previousValue, element) => previousValue element['amount']);
var y=transactionlist.where((element) => element['isexpense']==false).toList();
totalincome=y.fold(0, (previousValue, element) => previousValue element['amount']);
//I have edited this lines...
return ListView.builder(
//reverse: true,
padding: EdgeInsets.symmetric(vertical: 10),
itemCount: transactionlist.length,
itemBuilder: (context, index) {
final trans=TransactionModel.fromjson(transactionlist[index]);
print(trans.toString());
return TransactionCard(
amount: trans.amount.toStringAsFixed(2),
datetime: trans.date.toString(),
paymentby: trans.paymentmode,
category: trans.category.title,
categoryicon: trans.category.iconurl,
isexpense: trans.isexpense,
);
});//listview end
} else {
return Container(
child: Center(
child: Text('No Transaction Found...')));
}
} else {
if (snapshot.hasError) {
return Text('error found');
} else {
return Text('empty..');
}
}
} else {
return Center(child: CircularProgressIndicator());
}
}),
);
}
CodePudding user response:
StreamBuilder will refresh its child UI, not the upper widget.
You can use ValueNotifier
with ValueListenableBuilder
. This snippet will help you to clear the concept.
class MyHomePage extends StatefulWidget {
MyHomePage({
Key? key,
}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
int? myGlobalValue1;
ValueNotifier<int?> globalValueNotifier = ValueNotifier(null);
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("myGlobalValue1 $myGlobalValue1"),
ValueListenableBuilder<int?>(
valueListenable: globalValueNotifier,
builder: (context, value, child) {
return Text("globalValueNotifier $value");
}),
StreamBuilder<int>(
stream: Stream<int>.periodic(const Duration(seconds: 1), (x) => x)
.take(15),
builder: (BuildContext context, AsyncSnapshot snapshot) {
myGlobalValue1 = snapshot.data;
WidgetsBinding.instance.addPostFrameCallback((_) {
globalValueNotifier.value = snapshot.data; // to skip initOn frame build
});
return Column(
children: [
Text(
snapshot.data != null ? snapshot.data.toString() : "0",
),
Text("myGlobalValue1 inside streamB $myGlobalValue1"),
],
);
},
)
],
),
),
);
}
}
CodePudding user response:
You could move StreamBuilder further up in the widget tree, so that showTransactions and headerSummary get built within the Stream Builder.
If you want to keep the layout as it is, then you could look into ValueNotifier to update Income and Expense variables when stream builder has an update.