flutter: Widgets are rebuilding even though `provider` is used - android

I am using provider for flutter state management. Below is my code
Home
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
//backgroundColor: Color.fromRGBO(0, 72, 100, 10),
backgroundColor: Color.fromRGBO(25, 72, 114, 10),
title: Text("something"),
bottom: TabBar(
indicatorColor: Colors.white70,
labelStyle: TextStyle(
fontFamily: 'Roboto-Regular',
fontSize: 16.0,
letterSpacing: 0.15),
labelColor: Colors.white,
labelPadding: EdgeInsets.all(5),
tabs: <Widget>[
Tab(
text: "Fresh Products",
),
Tab(
text: "Frozen Products",
),
],
),
),
body: Center(child: Home()),
),
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Home build methof");
return TabBarView(
children: <Widget>[
Container(
height: double.infinity,
child: FutureBuilder(
future: Provider.of<ProductSpeciesImpl>(context, listen: false)
.getAllSpecies(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(),
Container(
height: 10,
),
Text(
"Loading Data... Please Wait",
style: Theme.of(context).textTheme.body1,
)
],
),
),
);
} else {
if (snapshot.hasError) {
return Center(
child: Text("An error Occured"),
);
} else {
return Consumer<ProductSpeciesImpl>(
builder: (context, data, child) => GridView.builder(
physics:
ScrollPhysics(), // to disable GridView's scrolling
shrinkWrap: true,
itemCount: Provider.of<ProductSpeciesImpl>(context)
.productList
.length,
gridDelegate:
new SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio:
(MediaQuery.of(context).size.width *
.5 /
190),
crossAxisCount: 2),
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
//print("sdsdsdsd");
// print(snapshot.data[index].name + " " + snapshot.data[index].idProductSpecies.toString() + " "+ snapshot.data[index].photo);
Navigator.pushNamed(context, "/products",
arguments: {
"name": Provider.of<ProductSpeciesImpl>(
context,
listen: false)
.productList[index]
.name,
"id": Provider.of<ProductSpeciesImpl>(
context,
listen: false)
.productList[index]
.idProductSpecies,
"photo": Provider.of<ProductSpeciesImpl>(
context,
listen: false)
.productList[index]
.photo
});
},
child: Card(
elevation: 4.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0)),
clipBehavior: Clip.antiAlias,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
Provider.of<ProductSpeciesImpl>(context,
listen: false)
.productList[index]
.name,
),
)
],
),
),
);
}));
}
}
},
),
),
ProductSpeciesImpl
class ProductSpeciesImpl
with ChangeNotifier
implements ProductSpeciesInterface {
NavLinks navLinks = NavLinks();
List<ProductSpecies> productList = [];
#override
Future<void> getAllSpecies() async {
var data = await http.get(navLinks.getAllProductSpecies());
var jsonData = convert.json.decode(data.body).cast<Map<String, dynamic>>();
try {
productList = jsonData
.map<ProductSpecies>((json) => new ProductSpecies.fromJson(json))
.toList();
print("Product sIZE: " + productList.length.toString());
notifyListeners();
} catch (error) {
throw error;
}
}
}
The code works fine. The issue is every time I visit another page and come back to this page, the UI gets reloaded. I have used consumer, as far as I understood, consumer will only load the relevant part when it is called. That means I don't have to run my product loading code inside init as well. So, I do not understand why this is happening.
Appreciate your support to fix this issue.

You are initiating an HTTP request in your build() function here by calling getAllSpecies(). You are not supposed to do that.
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
//...
child: FutureBuilder(
future: Provider.of<ProductSpeciesImpl>(context, listen: false)
.getAllSpecies(),
build() functions are supposed to have no side effects. Please convert the widget to StatefulWidget and do your loading in initState(). Alternatively, make a parent StatefulWidget do the loading in its initState() and hand the data to this widget in its constructor.

Related

How to access dynamic input fields values on button click in flutter

I am working on an attendance application where I assign wages to the workers. I want to store all the wages given to the workers into the database. But the problem is I want to access all the given values on button click. I have no idea how it can be done in flutter. I am a beginner.
I have given all the codes and the image of what output i want.
Image of Emulator
Here is my code...
ATTENDANCE SCREEN
...rest code...
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Upload Patti'),
content: SingleChildScrollView(
child: ListBody(
children: [
TextFormField(
controller: _mainWagesController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "Enter Amount",
prefixIcon: Icon(Icons.wallet, color: Colors.blue),
),
),
],
),
),
actions: <Widget>[
ElevatedButton(
onPressed: () {
Navigator.pop(context);
newWages = _mainWagesController.text;
setState(() {});
},
child: const Text("Assign Wages"),
),
],
);
},
);
},
child: const Icon(Icons.check_circle),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.00),
child: Column(children: [
const SizedBox(
height: 20,
),
Center(
child: Text(
"Date : ${DateFormat.yMMMEd().format(DateTime.parse(widget.attendanceDate.toString()))}",
style: const TextStyle(fontSize: 20),
),
),
const SizedBox(
height: 20,
),
FutureBuilder(
future: SupervisorAttendanceServices.getAttendancesDetailsList(
widget.attendanceId),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
var data = snapshot.data['hamal'];
return ListView.builder(
itemCount: data.length,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return HamalAttendanceWidget(
workerId: data[index]['worker_id'],
name: data[index]['worker_name'],
wages: newWages,
masterAttendanceId: widget.attendanceId,
isPrensent: data[index]
['attendance_worker_presense']
.toString());
});
} else if (snapshot.hasError) {
return const Center(
child: Text("Something went wrong !"),
);
} else {
return const Center(child: LinearProgressIndicator());
}
},
),
]),
),
),
...rest code
widget
Widget build(BuildContext context) {
return Card(
child: Column(children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(
width: 10,
height: 50,
),
const Icon(FeatherIcons.user),
const SizedBox(
width: 20,
),
Text(
widget.name,
style: const TextStyle(fontSize: 18),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
width: 150,
height: 60,
child: TextFormField(
// onChanged: _onChangeHandler,
initialValue: widget.wages.toString(),
decoration: const InputDecoration(
hintText: "Wages",
prefixIcon: Icon(
Icons.wallet,
color: Colors.blue,
)),
)),
],
)
]),
);
}
I suggest you use a StateManager for your application, for example GetX
is a good solution. Create a controller file like the below:
// define this enum outside of class to handle the state of the page for load data
enum AppState { initial, loading, loaded, error, empty, disabled }
Rx<AppState> pageState = AppState.initial.obs;
class AttendanceCntroller extends GetxController{
RxList<dynamic> dataList=RxList<dynamic>();
#override
void onInit() {
//you can write other codes in here to handle data
pageState(AppState.loading);
dataList.value=
SupervisorAttendanceServices.getAttendancesDetailsList(attendanceId);
pageState(AppState.loaded);
super.onInit();
}
}
and in your view(UI) page, handle it in this way:
class AttendanceView extends GetView<AttendanceCntroller>{
#override
Widget body(BuildContext context) {
// TODO: implement body
return Obx( ()=> controller.pageState.value==AppState.loading ? const
Center(child: LinearProgressIndicator()) : ListView.builder(
itemCount: controller.dataList.length,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return HamalAttendanceWidget(
workerId: controller.dataList['worker_id'],
name: controller.dataList['worker_name'],
wages: newWages,
masterAttendanceId: widget.attendanceId,
isPrensent: controller.dataList[index]
['attendance_worker_presense']
.toString());
})
)
}
}
for more data read the GetX link and read clean architecture with the GetX sample repository of my GitHub it have advanced management of states with GetX with dependency injection handling.
If you want to have prefilled value in TextFormField, you can either use initialValue or controller parameter.
The value of controller parameter will help you to get/update the value of TextFormField.
For controller parameter refer below.
TextEditingController controller = TextEditingController(text: 'This is text will be pre-filled in TextFormField');
...
TextFormField(
controller: controller,
);
Create List or Map of those controllers.
List<TextEditingController> listOfControllers = [ controller1, controlle2,...];
Use for loop through this List on onClick() method of Button.
ElevatedButton(
onPressed: () {
for(var controllerItem in listOfControllers) {
print(controllerItem.text); // the value of TextFormField
}
},
)

How to use Streambuilder in flutter

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
void main() async {
//Run this first
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Smart Bin',
home: new HomePageWidget(),
);
}
}
class HomePageWidget extends StatefulWidget {
const HomePageWidget({Key key}) : super(key: key);
#override
_HomePageWidgetState createState() => _HomePageWidgetState();
}
class _HomePageWidgetState extends State<HomePageWidget> {
final scaffoldKey = GlobalKey<ScaffoldState>();
final currentBinRecord = FirebaseFirestore.instance.collection("current_bin");
#override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
appBar: AppBar(
title: Text(
'SmartBin',
),
),
body: SafeArea(
child: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: StreamBuilder<List<CurrentBinRecord>>(
stream: queryCurrentBinRecord(
queryBuilder: (currentBinRecord) =>
currentBinRecord.orderBy('level', descending: true),
),
builder: (context, snapshot) {
// Customize what your widget looks like when it's loading.
if (!snapshot.hasData) {
return Center(
child: SizedBox(
width: 50,
height: 50,
child: CircularProgressIndicator(),
),
);
}
List<CurrentBinRecord> listViewCurrentBinRecordList =
snapshot.data;
return ListView.builder(
padding: EdgeInsets.zero,
scrollDirection: Axis.vertical,
itemCount: listViewCurrentBinRecordList.length,
itemBuilder: (context, listViewIndex) {
final listViewCurrentBinRecord =
listViewCurrentBinRecordList[listViewIndex];
return Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
listViewCurrentBinRecord.area,
),
Text(
listViewCurrentBinRecord.level.toString(),
),
],
);
},
);
},
),
),
],
),
),
),
);
}
}
This is the error
First error is on:
child: StreamBuilder<List<CurrentBinRecord>>
The name 'CurrentBinRecord' isn't a type so it can't be used as a type argument.
Try correcting the name to an existing type, or defining a type named 'CurrentBinRecord'.
Second error is on:
stream: queryCurrentBinRecord
The method 'queryCurrentBinRecord' isn't defined for the type '_HomePageWidgetState'.
Try correcting the name to the name of an existing method, or defining a method named 'queryCurrentBinRecord'.
Third error is on:
List<CurrentBinRecord> listViewCurrentBinRecordList =
snapshot.data;
The name 'CurrentBinRecord' isn't a type so it can't be used as a type argument.
Try correcting the name to an existing type, or defining a type named 'CurrentBinRecord'.
These is the syntax try -
return StreamBuilder(
stream: theStreamSource, // Eg a firebase query
builder: (context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, int index) {
return Text(snapshot.data.documents[index]['title']);
}
);
},
);
Hope it helps.
I think the best way to use StreamBuilder is to create separate controller class that can handle all your business logic and update UI.
// your widget class
class UIClass extends StatefulWidget {
const UIClass({Key key}) : super(key: key);
#override
_UIClassState createState() => _UIClassState();
}
class _UIClassState extends State<UIClass> {
UIClassController<List<CurrentBinRecord>> _uiController;
#override
void initState() {
_uiController = UIClassController(StreamController<List<CurrentBinRecord>>());
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Text("Hello Everyone"),
StreamBuilder<List<CurrentBinRecord>>(
stream: _uiController.uiStream,
builder: (context, snapshot) {
if(snapshot.hasError){
return ErrorWidget();
}
else if(snapshot.connectionState == ConnectionState.waiting){
return WaitingWidget();
}
else if(snapshot.hasData){
return ListView.builder(
padding: EdgeInsets.zero,
scrollDirection: Axis.vertical,
itemCount: snapshot.data.length,
itemBuilder: (context, listViewIndex) {
final listViewCurrentBinRecord =
snapshot.data[listViewIndex];
return Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
listViewCurrentBinRecord.area,
),
Text(
listViewCurrentBinRecord.level.toString(),
),
],
);
},
);
}
else{
return SizedBox();
}
}
),
],
);
}
#override
void dispose() {
super.dispose();
_uiController.dispose();
}
}
// controller class to handle all business logic
// you can also split it into multiple sub controllers
class UIClassController<T> {
final StreamController<T> _controller;
// If you are using this on multiple widgets then use asBroadcastStream()
Stream<T> get uiStream => _controller.stream;
UIClassController(this._controller);
void updateMyUI([dynamic params]){
T t;
// your logic //
//------------//
_controller.sink.add(t);
}
void dispose(){
_controller.close();
}
}
Code I'm Using for StreamBuilder
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import '../door/widgets/Navbar.dart';
import '../door/widgets/sidenav.dart';
import 'package:get/get.dart';
FirebaseAuth auth = FirebaseAuth.instance;
class CartPage extends StatefulWidget {
#override
_CartPageState createState() => _CartPageState();
}
class _CartPageState extends State<CartPage> {
final FirebaseFirestore fb = FirebaseFirestore.instance;
int up = 1;
bool loading = false;
final ScrollController _scrollController = ScrollController();
#override
void initState() {
super.initState();
getCartData();
}
void addMore(qty, documentID) {
int new_qty = qty + 1;
Get.snackbar('Qty! ', new_qty.toString());
String collection_name = "cart_${auth.currentUser?.email}";
FirebaseFirestore.instance
.collection(collection_name)
.doc(documentID)
.update({
'qty': new_qty,
'updated_at': Timestamp.now(),
}) // <-- Your data
.then((_) => print('Added'))
.catchError((error) => Get.snackbar('Failed!', 'Error: $error'));
}
void minusMore(qty, documentID) {
if (qty > 1) {
int new_qty = qty - 1;
String collection_name = "cart_${auth.currentUser?.email}";
FirebaseFirestore.instance
.collection(collection_name)
.doc(documentID)
.update({
'qty': new_qty,
'updated_at': Timestamp.now(),
}) // <-- Your data
.then((_) => print('Subtracted'))
.catchError((error) => Get.snackbar('Failed!', 'Error: $error'));
}
}
Stream<QuerySnapshot> getCartData() {
String collection_name = "cart_${auth.currentUser?.email}";
return FirebaseFirestore.instance
.collection(collection_name)
.orderBy("created_at", descending: false)
.snapshots();
}
final ButtonStyle style = ElevatedButton.styleFrom(
minimumSize: Size(50, 30),
backgroundColor: Color(0xFFFFB61A),
elevation: 6,
textStyle: const TextStyle(fontSize: 11),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(20),
)));
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: Navbar.navbar(),
drawer: Sidenav.sidenav(),
body: Container(
padding: EdgeInsets.all(10.0),
child: StreamBuilder<QuerySnapshot>(
stream: getCartData(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
if (snapshot.hasError) {
return buildText('Something Went Wrong Try later');
} else {
if (!snapshot.hasData) {
return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 100,
minHeight: 190,
maxWidth: 200,
maxHeight: 200,
),
child: Icon(Icons.shopping_cart, size: 45),
),
title: Text('No Food!'),
subtitle: const Text('Your cart is empty!'),
),
],
),
);
} else {
return ListView.builder(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
itemCount: snapshot.data?.docs.length,
itemBuilder: (BuildContext context, int index) {
return SizedBox(
height: 90.0,
child: Card(
elevation: 10,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
const SizedBox(
width: 20,
),
_buildImg('assets/logo.png', '60', '60'),
const SizedBox(
width: 14,
),
SizedBox(
width:
MediaQuery.of(context).size.width *
0.33,
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(
height: 20,
),
Text(
snapshot.data?.docs[index]
["name"],
maxLines: 2,
style: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 14)),
const SizedBox(
height: 5,
),
Text(
"₹${snapshot.data?.docs[index]["price"]}",
style: const TextStyle(
fontWeight: FontWeight.w400,
fontSize: 12)),
],
),
),
const SizedBox(
width: 10,
),
Container(
margin: const EdgeInsets.only(
top: 20.0, bottom: 10.0),
decoration: BoxDecoration(
color: const Color(0xFFFFB61A),
// color: Color(0xFF0A2031),
borderRadius:
BorderRadius.circular(17)),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment:
CrossAxisAlignment.end,
children: <Widget>[
SizedBox(
height: 40,
child: IconButton(
onPressed: () {
minusMore(
snapshot.data
?.docs[index]
["qty"],
snapshot
.data
?.docs[index]
.reference
.id);
},
color: Colors.white,
icon:
const Icon(Icons.remove)),
),
const SizedBox(
width: 5,
),
Container(
margin: const EdgeInsets.only(
bottom: 10.0),
child: Text(
"${snapshot.data?.docs[index]["qty"]}",
style: const TextStyle(
fontWeight:
FontWeight.w400,
color: Colors.white,
fontSize: 16)),
),
const SizedBox(
width: 5,
),
SizedBox(
height: 40,
child: IconButton(
color: Colors.white,
onPressed: () {
addMore(
snapshot.data
?.docs[index]
["qty"],
snapshot
.data
?.docs[index]
.reference
.id);
},
icon: const Icon(Icons.add)),
),
],
),
),
const SizedBox(
width: 10,
),
],
),
),
);
});
}
}
}
},
),
),
);
}
Widget buildText(String text) => Center(
child: Text(
text,
style: TextStyle(fontSize: 24, color: Colors.black),
),
);
_buildImg(img, hei, wid) {
return Container(
alignment: Alignment.center, // use aligment
child: Image.asset(
img,
height: double.parse(hei),
width: double.parse(wid),
fit: BoxFit.cover,
),
);
}
}

Using FutureBuilder with TabBarView in Flutter

In this application, I'm using TabBar to divide content into categories. Information of each content is coming from a local database. For listing items from that local database to screen, I'm using FutureBuilder and calling some database helpers to fetch data in the future.
As you can see in the screenshot, I have 6 different tabs on top, and in each tab have fetched data with FutureBuilder. (This was what should normally happen.)
I did not encounter with any debug error but only the first FutureBuilder lists the data to the screen. The second tab is not showing anything. (And also the other tabs have the same issue but I just put a placeholder to make code shorter).
Here is the code:
import 'package:cascade_travel_guide/data/database_helper.dart';
import 'package:cascade_travel_guide/model/Categories.dart';
import 'package:cascade_travel_guide/model/HistoricalPlaces.dart';
import 'package:cascade_travel_guide/model/Parks.dart';
import 'package:flutter/material.dart';
class CategoryScreen extends StatefulWidget {
final townid;
CategoryScreen({Key key, this.townid}) : super(key: key);
#override
_CategoryScreenState createState() => _CategoryScreenState(townid: this.townid);
}
class _CategoryScreenState extends State<CategoryScreen> with TickerProviderStateMixin {
var townid;
_CategoryScreenState({this.townid});
var dbHelper = DbHelper();
ScrollController _scrollController;
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(length: 6, vsync: this);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).canvasColor,
body: CustomScrollView(
physics: NeverScrollableScrollPhysics(),
controller: _scrollController,
slivers: <Widget>[
SliverAppBar(
bottom: PreferredSize(
child: Container(),
preferredSize: Size(0, 110),
),
pinned: true,
automaticallyImplyLeading: false,
primary: false,
flexibleSpace: Stack(
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Image.network(
"https://i.pinimg.com/originals/6b/47/b9/6b47b97543abed38a12d8c2b4643e870.jpg",
fit: BoxFit.cover,
),
),
Positioned(
child: Column(
children: [
TabBar(
controller: _tabController,
isScrollable: true,
labelColor: Colors.white,
labelPadding: EdgeInsets.symmetric(horizontal: 15),
tabs: [
Tab(text: "Historical Places"),
Tab(text: "Parks"),
Tab(text: "Shopping Malls"),
Tab(text: "Restaurants"),
Tab(text: "Hospitals"),
Tab(text: "Police Stations"),
]),
Container(
decoration: BoxDecoration(
color: Theme.of(context).canvasColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(40),
topRight: Radius.circular(40),
),
),
),
],
),
bottom: -1,
left: 0,
right: 0,
),
],
),
),
SliverFillRemaining(
child: Container(
child: TabBarView(
controller: _tabController,
children: [
FutureBuilder<List<Categories>>(
future: dbHelper.getCategories(townid),
builder: (context, snapshot) {
if (snapshot.hasData) {
List catid = [];
for (int i = 0; i < snapshot.data.length; i++) {
if (catid.length < snapshot.data.length)
catid.add(snapshot.data[i].categoriesId);
}
print(catid);
return Container(
child: FutureBuilder<List<HistoricalPlaces>>(
future: dbHelper.getHist(catid[0]),
builder: (context, snapshot) {
if (snapshot.hasData)
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
"${snapshot.data[index].histName}"),
leading: CircleAvatar(),
);
});
else
return Center(
child: CircularProgressIndicator());
}),
);
} else
return Center(
child: CircularProgressIndicator(),
);
}),
FutureBuilder<List<Categories>>(
future: dbHelper.getCategories(townid),
builder: (context, snapshot) {
if (snapshot.hasData) {
List catid = [];
for (int i = 0; i < snapshot.data.length; i++) {
if (catid.length < snapshot.data.length)
catid.add(snapshot.data[i].categoriesId);
}
print(catid);
return Container(
child: FutureBuilder<List<Parks>>(
future: dbHelper.getPark(catid[0]),
builder: (context, snapshot) {
if (snapshot.hasData)
return ListView.builder(
padding: EdgeInsets.zero,
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
"${snapshot.data[index].parkName}"),
);
});
else
return Center(
child: CircularProgressIndicator());
}),
);
} else
return Center(
child: CircularProgressIndicator(),
);
}),
Center(
child: Text("Test"),
),
Center(
child: Text("Test"),
),
Center(
child: Text("Test"),
),
Center(
child: Text("Test"),
),
],
))),
],
),
);
}
}
P.S.: I also tried to add IndexedStack to fetch all lists together but nothing changed :(

Firebase Voting App to only allow users vote only once in android studio

In the code below, I seek to make users vote only once in my Voting App.
At the moment users can vote more than once. I have created hasVoted field(a map with the UID of users and true as a value to indicate the user has voted) in the item being voted for as shown in my Firestore backend, however this does not seem to work as i want it to. What could be wrong. Does anyone here know a way around this?
Please i am new to flutter and dart so kindly forgive petty mistake that i make in posting this question
Below is my code
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:compuvote/models/finance_model.dart';
import 'package:compuvote/routes/home_page.dart';
import 'package:compuvote/routes/transitionroute.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
class FinanceResult extends StatefulWidget {
#override
_FinanceResultState createState() {
return _FinanceResultState();
}
}
class _FinanceResultState extends State<FinanceResult> {
List<charts.Series<Record, String>> _seriesBarData;
List<Record> mydata;
_generateData(mydata) {
_seriesBarData = List<charts.Series<Record, String>>();
_seriesBarData.add(
charts.Series(
domainFn: (Record record, _) => record.name.toString(),
measureFn: (Record record, _) => record.totalVotes,
//colorFn: (Record record, _) => record.color,
id: 'Record',
data: mydata,
// Set a label accessor to control the text of the arc label.
labelAccessorFn: (Record row, _) => '${row.name}: ${row.totalVotes}',
colorFn: (_, __) => charts.MaterialPalette.cyan.shadeDefault,
fillColorFn: (_, __) => charts.MaterialPalette.transparent,
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Finance Result'),
leading: IconButton(
icon: Icon(Icons.home),
onPressed: () {
Navigator.push(context, TransitionPageRoute(widget: HomePage()));
},
),
),
body: Container(
color: Colors.grey.shade100,
child: _buildBody(context),
));
}
/// ****** This code is suppose to build the body ***********/
Widget _buildBody(BuildContext context) {
return Column(
children: <Widget>[
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('finance').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: LinearProgressIndicator(
valueColor: AlwaysStoppedAnimation(
Theme.of(context).primaryColor,
),
),
);
} else {
List<Record> finance = snapshot.data.documents
.map((documentSnapshot) =>
Record.fromMap(documentSnapshot.data))
.toList();
return _buildChart(context, finance);
}
},
),
],
);
}
Widget _buildChart(BuildContext context, List<Record> recorddata) {
mydata = recorddata;
_generateData(mydata);
return Padding(
padding: EdgeInsets.all(8.0),
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height / 1.2,
child: Center(
child: Column(
children: <Widget>[
SizedBox(
height: 10.0,
),
Expanded(
child: charts.PieChart(
_seriesBarData,
animate: true,
animationDuration: Duration(seconds: 3),
//For adding labels to the chart
defaultRenderer: new charts.ArcRendererConfig(
strokeWidthPx: 2.0,
arcWidth: 100,
arcRendererDecorators: [
// <-- add this to the code
charts.ArcLabelDecorator(
labelPosition: charts.ArcLabelPosition.auto,
labelPadding: 3,
showLeaderLines: true,
insideLabelStyleSpec: charts.TextStyleSpec(
color: charts.Color.white,
fontSize: 12,
),
outsideLabelStyleSpec: charts.TextStyleSpec(
color: charts.Color.black,
fontSize: 12,
),
),
]),
),
),
Container(
width: MediaQuery.of(context).size.width / 3.5,
height: 1,
color: Colors.black38,
),
Expanded(
child: Scaffold(
backgroundColor: Colors.grey.shade100,
body: _castVote(context),
),
),
],
),
),
),
);
}
/// ****** This code is suppose to link to the Firestore collection ***********/
Widget _castVote(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('finance').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData)
return Center(
child: LinearProgressIndicator(
valueColor: AlwaysStoppedAnimation(
Theme.of(context).primaryColor,
),
),
);
return _buildList(context, snapshot.data.documents);
},
);
}
/// ****** This code is suppose to build the List ***********/
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
return ListView(
padding: const EdgeInsets.only(top: 20.0),
// ignore: missing_return
children: snapshot.map((data) => _buildListItem(context, data)).toList(),
);
}
/// ****** This code is suppose to build the List Items ***********/
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
return Padding(
key: ValueKey(record.name),
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Card(
elevation: 2,
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade100),
borderRadius: BorderRadius.circular(5.0),
),
child: ListTile(
title: Text(record.name),
trailing: Text(record.totalVotes.toString()),
onTap: () => {
_checkHasVoted(context, data),
}
//onTap: () => {record.reference.updateData({'votes': record.votes + 1}),
),
),
),
);
}
/// ****** This code is suppose to force users to vote only once ***********/
Widget _checkHasVoted(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
StreamSubscription<DocumentSnapshot> subscription;
final DocumentReference documentReference =
Firestore.instance.collection("finance").document() as DocumentReference;
#override
void initState() {
super.initState();
subscription = documentReference.snapshots().listen((datasnapshot) {
if ((datasnapshot.data
.containsKey(FirebaseAuth.instance.currentUser()))) {
setState(() {
return Text("Sorry you have voted in this category already");
});
}
else if (!datasnapshot.data.containsKey(FirebaseAuth.instance.currentUser())){
setState(() {
record.reference.updateData({'votes':record.totalVotes + 1});
});
}
});
}
}
}

Failed Assertion: boolean expression must not be null. Initially the loading screen is called

My code is as follows:
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
List<CategoryModel> categories = new List<CategoryModel>();
List<ArticleModel> articles = new List<ArticleModel>();
bool _loading = true;
getNews() async{
News newsClass = News();
await newsClass.getNews();
articles = newsClass.news;
setState(() {
_loading = false;
});
}
#override
void initState() {
getNews();
categories = getCategories();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("News"),
Text("App", style: TextStyle(
color: Colors.blueAccent
),)
],
),
centerTitle: true,
elevation: 0.0,
),
body: _loading ? Center(
child: CircularProgressIndicator(),
) : SingleChildScrollView(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: <Widget>[
/// Categories
Container(
height: 70,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: categories.length,
itemBuilder: (context, index){
return CategoryTile(
imageUrl: categories[index].imageUrl,
categoryName: categories[index].categoryName,
);
}),
),
/// Blog
Container(
padding: EdgeInsets.only(top: 16),
child: ListView.builder(
itemCount: articles.length,
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemBuilder: (context, index){
return BlogTile(
imageUrl: articles[index].urlToImage,
title: articles[index].title,
desc: articles[index].description,
);
}),
)
],
),
),
),
);
}
}
class CategoryTile extends StatelessWidget {
final imageUrl, categoryName;
CategoryTile({this.imageUrl, this.categoryName});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
},
child: Container(
margin: EdgeInsets.only(right: 16),
child: Stack(
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.circular(6),
child: CachedNetworkImage(
imageUrl: imageUrl, width: 120, height: 60, fit: BoxFit.cover,)
),
Container(
alignment: Alignment.center,
width: 120, height: 60,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: Colors.black26,
),
child: Text(categoryName, style: TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w500
),),
)
],
),
),
);
}
}
class BlogTile extends StatelessWidget {
final String imageUrl, title, desc;
BlogTile({#required this.imageUrl,#required this.title,#required this.desc});
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.circular(6),
child: Image.network(imageUrl)
),
SizedBox(height: 8,),
Text(title, style: TextStyle(
fontSize: 17, color: Colors.black87, fontWeight: FontWeight.w500
),),
SizedBox(height: 8,),
Text(desc, style: TextStyle(
color: Colors.black54
),)
],
),
);
}
}
When I run the code it shows the loading screen which is not going off, the content which i want to display is not showing. Please help by providing your valuable answer. Thank you in advance.
The error I am getting is as follows:
E/flutter ( 5339): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: Failed assertion: boolean expression must not be null
Since you are initialising your _loading variable inside the first statement of the initState. You can as well initialise it directly when declaring it.
Like this:
bool _loading = true;
Try the code below:
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
List<CategoryModel> categories = new List<CategoryModel>();
List<ArticleModel> articles = new List<ArticleModel>();
bool _loading = true;
getNews() async{
News newsClass = News();
await newsClass.getNews();
articles = newsClass.news;
}
#override
void initState() {
getNews();
categories = getCategories();
setState(() {
_loading = false;
});
super.initState();
}
I hope this helps.

Categories

Resources