Firebase List view is not displayed in real-time while adding data - firebase

I am storing Contact information on Firebase real-time DB after picking a contact from the phone.
When I add the data which is displayed using ListView and DataSnapshot the data is not refreshed in real-time until I restart the Activity. The ListView loads and displays all the data without any problem. How do I resolve this issue?
My screen looks like this:
Code for Screen:
import 'package:epicare/CaregiverClass.dart';
import 'package:epicare/Homepage.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_switch/flutter_switch.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:contact_picker/contact_picker.dart';
//Check contacts permission
Future<PermissionStatus> _getPermission() async {
final PermissionStatus permission = await Permission.contacts.status;
if (permission != PermissionStatus.granted &&
permission != PermissionStatus.denied) {
final Map<Permission, PermissionStatus> permissionStatus =
await [Permission.contacts].request();
return permissionStatus[Permission.contacts] ??
PermissionStatus.undetermined;
} else {
return permission;
}
}
class CaregiverScreen extends StatefulWidget {
#override
_CaregiverScreenState createState() => _CaregiverScreenState();
}
class _CaregiverScreenState extends State<CaregiverScreen> {
//Firebase
FirebaseAuth firebaseAuth = FirebaseAuth.instance;
final ref = FirebaseDatabase.instance.reference();
User cuser = FirebaseAuth.instance.currentUser;
final fb = FirebaseDatabase.instance.reference().child("User_data");
List <CaregiverList> list = List();
// Adding data into Firebase
void addData(String name, String number) {
print("Saving data to firebase");
ref.child('User_data').child(cuser.uid).child("caregivers").push().set({'Caregiver_Name': name, 'Caregiver_Number': number});
}
// Get location
Position _currentPosition;
String _currentAddress;
final Geolocator geolocator = Geolocator()..forceAndroidLocationManager;
#override
void initState() {
// Firebase
fb.child(cuser.uid).child("caregivers").once().then((DataSnapshot snapshot)
{
var data = snapshot.value;
list.clear();
data.forEach((key,value){
print(value['Caregiver_Name']);
CaregiverList contact_list = new CaregiverList(
name: value['Caregiver_Name'],
phone_number: value['Caregiver_Number'],
isActive: true,
key: key,
);
list.add(contact_list);
});
setState(() {
});
}
);
super.initState();
}
final ContactPicker _contactPicker = new ContactPicker();
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
//Size size = MediaQuery.of(context).size;
return Scaffold(
key: _scaffoldKey,
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: const Color(0xffE5E0A1),
elevation: 0,
centerTitle: true,
title: Text(
"Add Caregiver",
style: TextStyle(
fontSize: 15.0,
color: Colors.black,
fontFamily: 'Montserrat',
fontWeight: FontWeight.normal,
),
),
leading: IconButton(
icon: Icon(
Icons.arrow_back,
color: Colors.black,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return Homepage();
},
),
);
},
),
),
body: SingleChildScrollView(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 40),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
MaterialButton(
onPressed: () async {
final PermissionStatus permissionStatus =
await _getPermission();
if (permissionStatus == PermissionStatus.granted) {
//We can now access our contacts here
Contact contact = await _contactPicker.selectContact();
print("Contact name: ${contact.fullName}");
print("Contact number: ${contact.phoneNumber}");
List<String> num =
contact.phoneNumber.toString().split('(Work)');
print(num);
print(num[0]);
final pattern = RegExp('\\s+');
num[0].replaceAll(pattern, '');
num[0].replaceAll(new RegExp('-'),'');
addData(contact.fullName.toString(),num[0]);
} else {
//If permissions have been denied show standard cupertino alert dialog
showDialog(
context: context,
builder: (BuildContext context) =>
CupertinoAlertDialog(
title: Text('Permissions error'),
content: Text('Please enable contacts access '
'permission in system settings'),
actions: <Widget>[
CupertinoDialogAction(
child: Text('OK'),
onPressed: () =>
Navigator.of(context).pop(),
)
],
));
}
},
color: const Color(0xffd4d411),
textColor: Colors.white,
child: Icon(
Icons.add,
size: 32,
),
padding: EdgeInsets.all(3),
shape: CircleBorder(),
),
Text(
'Add a Caregiver',
style: TextStyle(
fontFamily: 'Montserrat',
fontSize: 13,
color: const Color(0xff000000),
height: 1.5384615384615385,
fontWeight: FontWeight.w600),
textHeightBehavior:
TextHeightBehavior(applyHeightToFirstAscent: false),
textAlign: TextAlign.left,
)
],
),
list.length == 0 ? Center(child: Text("Please add Caregivers"))
: ListView.separated(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
primary: false,
itemBuilder: (context, index) {
return _caregiverWidget(list[index].name,list[index].phone_number,list[index].isActive,list[index].key
);
},
separatorBuilder: (_, __) => Container(),
itemCount: list.length),
// _contact == null ? Container() : CaregiversList(_contact.fullName),
],
),
),
),
);
}
Widget _caregiverWidget( String name, String phone_number, bool isActive, String key
) {
print(name);
var c = name.split(' ');
print(c[0]);
var caregiver = c[0];
var output = getInitials(string: caregiver, limitTo: 1);
print(output);
return GestureDetector(
onLongPress: (){},
onTap: (){},
child: Card(
elevation: 0,
child: Container(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 27),
child: Row(
//crossAxisAlignment: CrossAxisAlignment.center,
//mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
CircleAvatar(
radius: 24,
backgroundColor: const Color(0xffd4d411),
child: CircleAvatar(
radius: 22,
backgroundColor: Colors.white,
child: Text(
output,
style: TextStyle(
fontFamily: 'Segoe UI',
fontSize: 20,
color: const Color(0xff000000),
),
),
),
),
SizedBox(width: 19),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
caregiver,
style: TextStyle(
fontFamily: 'Montserrat',
fontSize: 13,
color: const Color(0xff000000),
height: 1.5384615384615385,
fontWeight: FontWeight.w600),
textHeightBehavior:
TextHeightBehavior(applyHeightToFirstAscent: false),
textAlign: TextAlign.left,
),
isActive
? Text(
"Activated",
style: TextStyle(
fontFamily: 'Montserrat',
fontSize: 10,
color: const Color(0x80232425),
fontWeight: FontWeight.w500),
textAlign: TextAlign.left,
)
: Text(
"Disabled",
style: TextStyle(
fontFamily: 'Montserrat',
fontSize: 10,
color: const Color(0x80232425),
fontWeight: FontWeight.w500),
textAlign: TextAlign.left,
),
],
),
SizedBox(width: 143),
FlutterSwitch(
width: 40.0,
height: 20.0,
value: isActive,
toggleSize: 15,
borderRadius: 40.0,
padding: 2.0,
showOnOff: false,
activeColor: const Color(0xffd4d411),
activeToggleColor: Colors.white,
inactiveColor: const Color(0xffDDDBAF),
inactiveToggleColor: Colors.white,
onToggle: (value) {
print(value);
setState(() {
isActive = value;
// _careList.removeAt(index);
// _careList.insert(index, _caregiver);
});
},
),
],
),
),
),
);
}
String getInitials({String string, int limitTo}) {
var buffer = StringBuffer();
var split = string.split(' ');
for (var i = 0; i < (limitTo ?? split.length); i++) {
buffer.write(split[i][0]);
}
return buffer.toString();
}
}

The issue is that this part:
fb.child(cuser.uid).child("caregivers").once()..
is only getting the value once.
To se realtime updates you should either use a listener like here:
CollectionReference reference = Firestore.instance.collection('caregivers');
reference.snapshots().listen((querySnapshot) {
querySnapshot.documentChanges.forEach((change) {
// Do something with change
});
});
or a StreamBuilder like here:
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('caregivers').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return new Text('Loading...');
return new ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
return new ListTile(
title: new Text(document['name']),
);
}).toList(),
);
},
);
for the RTDB the StreamBuilder would look like this:
StreamBuilder(
stream: _database.reference().child('caregivers').onValue,
builder: (context, event) {
if (event.hasData &&
!event.hasError &&
event.data.snapshot.value != null) {
DataSnapshot snapshot = event.data.snapshot;
}
})
the listener would be like:
_database.reference().child('caregivers').onValue.listen((event){
})
onValue will give you everytime the whole list so you need to empty it before you add again every item:
var data = event.snapshot.value;
list= List(); //Clear the list here
data.forEach((key, value) {
print(value['Caregiver_Name']);
CaregiverList contact_list = new CaregiverList(
name: value['Caregiver_Name'],
phone_number: value['Caregiver_Number'],
isActive: true,
key: key,
);
list.add(contact_list);
});

Related

read number of children from the firebase realtime database using flutter

I'm new to flutter and really need help with my problem regarding reading/calculating the number of children from the firebase real-time database.
The database I use has several categories.
Each category has several cases.
What I want, is to extract the information from the database, how many cases each category has and to show this information in a list. That means - to show the name of the category AND how many children (cases) this category has (totalCases)...
Here is my code, I'm struggling with:
import '../components/category_list_tile.dart';
import 'package:firebase_database/ui/firebase_animated_list.dart';
import 'package:flutter/material.dart';
import 'package:modal_progress_hud/modal_progress_hud.dart';
import '../constants.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_database/firebase_database.dart';
import 'dart:async';
class ScreenCategoryList extends StatefulWidget {
static String id = 'screen_category_list';
final FirebaseApp app;
ScreenCategoryList({this.app});
#override
_ScreenCategoryListState createState() => _ScreenCategoryListState();
}
class _ScreenCategoryListState extends State<ScreenCategoryList> {
final referenceDatabase = FirebaseDatabase.instance;
final _dbRef = FirebaseDatabase.instance.reference().child("de");
static int number = 100;
bool showSpinner = false;
DatabaseReference _databaseReference;
#override
void initState() {
final FirebaseDatabase database = FirebaseDatabase(app: widget.app);
_databaseReference = database.reference().child("de");
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.white, Colors.white],
),
image: const DecorationImage(
image: AssetImage("images/background.png"), fit: BoxFit.cover),
),
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
toolbarHeight: 60.0,
elevation: 0.0,
backgroundColor: Colors.black12,
leading: Padding(
padding: EdgeInsets.only(left: 12.0, top: 12.0, bottom: 12.0),
child: Image(image: AssetImage('images/lexlogo_black.png'))),
title: Center(
child: Column(
children: [
Text(
'Kategorien',
style: TextStyle(
color: kMainDarkColor,
fontFamily: 'Roboto',
fontSize: 21.0,
fontWeight: FontWeight.bold),
),
],
),
),
actions: [
Padding(
padding: EdgeInsets.only(right: 8.0),
child: IconButton(
icon: Icon(Icons.more_vert_rounded),
iconSize: 30.0,
color: kMainDarkColor,
onPressed: () {},
//onPressed: onPressMenuButton,
),
),
],
),
body: ModalProgressHUD(
inAsyncCall: showSpinner,
child: FirebaseAnimatedList(
query: _databaseReference.child('category'),
itemBuilder: (
BuildContext context,
DataSnapshot snapshot,
Animation<double> animation,
int index,
) {
Future<int> getNumberOfNodes() async {
final response = await FirebaseDatabase.instance
.reference()
.child('de')
.child('category')
.child('$index')
.child('cases')
.once();
var nodes = [];
response.value.forEach((v) => nodes.add(v));
return nodes.length;
}
var myNumber = getNumberOfNodes();
int myInt = 99;
myNumber.then((value) {
myInt = value;
});
number = myInt;
return CategoryListTile(
title: snapshot.value['name'].toString(),
successfulCases: 1,
totalCases: number,
onTitleClick: () {},
onInfoButtonClick: () {},
);
},
reverse: false,
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
),
),
),
);
}
}
Since you declare Future<int> getNumberOfNodes() async, you need to FutureBuilder to display that value.
Something like this:
child: FutureBuilder<int>(
future: FirebaseDatabase.instance
.reference()
.child('de')
.child('category')
.child('$index')
.child('cases')
.once();
var nodes = [];
response.value.forEach((v) => nodes.add(v));
return nodes.length;
}
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
List<Widget> children;
if (snapshot.hasData) {
return Text("Case count: "+snapshot.data);
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}'),
} else {
return CircularProgressIndicator();
}
},
)
I did not compile or run this code, so please treat it as a pseudo-code. If you get any errors while using this, try to fix them by searching for the error message before reporting back.
So the future is the code that determines the value, and then the builder renders the correct UI based on whether the value is available yet. You'll want to replace the Text("Case count: "+snapshot.data) with your own UI, so the CategoryListTile(...).
Thank you #Frank van Puffelen for your suggestion. Finally could read the number of children of at least one category.
The code had to be changed like this:
class _ScreenCategoryListState extends State<ScreenCategoryList> {
final referenceDatabase = FirebaseDatabase.instance;
bool showSpinner = false;
DatabaseReference _databaseReference;
#override
void initState() {
final FirebaseDatabase database = FirebaseDatabase(app: widget.app);
_databaseReference = database.reference().child("de");
super.initState();
}
Future<Map<int, int>> getNumberOfNodes() async {
Map<int, int> caseNumbers = new Map<int, int>();
// read number of category nodes
final categoriesNumbersResponse = await FirebaseDatabase.instance
.reference()
.child('de')
.child('category')
// .child('0')
// .child('cases')
.once();
var categoryNodes = [];
categoriesNumbersResponse.value.forEach((v) => categoryNodes.add(v));
int numberOfCategories = categoryNodes.length;
//read number of cases in category
for (int i = 0; i < numberOfCategories; i++) {
final caseResponse = await FirebaseDatabase.instance
.reference()
.child('de')
.child('category')
.child('$i')
.child('cases')
.once();
var caseNodes = [];
caseResponse.value.forEach((v) => caseNodes.add(v));
int numberOfCases = caseNodes.length;
caseNumbers[i] = numberOfCases;
}
return caseNumbers;
}
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.white, Colors.white],
),
image: const DecorationImage(
image: AssetImage("images/background.png"), fit: BoxFit.cover),
),
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
toolbarHeight: 60.0,
elevation: 0.0,
backgroundColor: Colors.black12,
leading: Padding(
padding: EdgeInsets.only(left: 12.0, top: 12.0, bottom: 12.0),
child: Image(image: AssetImage('images/lexlogo_black.png'))),
title: Center(
child: Column(
children: [
Text(
'Kategorien',
style: TextStyle(
color: kMainDarkColor,
fontFamily: 'Roboto',
fontSize: 21.0,
fontWeight: FontWeight.bold),
),
],
),
),
actions: [
Padding(
padding: EdgeInsets.only(right: 8.0),
child: IconButton(
icon: Icon(Icons.more_vert_rounded),
iconSize: 30.0,
color: kMainDarkColor,
onPressed: () {},
//onPressed: onPressMenuButton,
),
),
],
),
body: FutureBuilder<Map<int, int>>(
future: getNumberOfNodes(),
builder: (BuildContext context,
AsyncSnapshot<Map<int, int>> casesSnapshot) {
if (casesSnapshot.hasData) {
return FirebaseAnimatedList(
reverse: false,
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
query: _databaseReference.child('category'),
itemBuilder: (
BuildContext context,
DataSnapshot categorySnapshot,
Animation<double> animation,
int index,
) {
int numberOfCases = casesSnapshot.data[index];
//print('number of cases $_counter, $numberOfCases');
return CategoryListTile(
title: categorySnapshot.value['name'].toString(),
successfulCases: 10,
totalCases: numberOfCases,
onTitleClick: () {},
onInfoButtonClick: () {},
);
},
);
} else if (casesSnapshot.hasError) {
return Center(
child: Column(
children: <Widget>[
Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${casesSnapshot.error}'),
)
],
),
);
} else {
return Center(
child: Column(
children: <Widget>[
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting result...'),
)
],
),
);
}
},
),
),
);
}
}

How to change element color on Stream Builder Flutter

I have a problem with StreamBuilder. I using FirebaseFirestore and I want to make a quiz app but I don't change my element's color. I used the setState method but it didn't work. How to change element color on Stream Builder?
My codes:
https://paste.tc/dtckRClOTL
import 'package:audioplayers/audio_cache.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:dots_indicator/dots_indicator.dart';
import 'package:exampills/screens/tests/resultScreen.dart';
import 'package:exampills/providers/testProvider.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:random_color/random_color.dart';
class TestScreen extends StatefulWidget {
final String categoryId;
final String subcategoryId;
final String title;
final String testId;
final int questPiece;
const TestScreen(
{Key key,
this.categoryId,
this.subcategoryId,
this.testId,
this.title,
this.questPiece})
: super(key: key);
#override
_TestScreenState createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> {
int position;
AudioCache audioCache = AudioCache();
AudioPlayer audioPlayer = AudioPlayer();
RandomColor randomColor = RandomColor();
var controller = Get.put(TestProvider());
PageController pageController = PageController();
int testQuestPiece = 0;
Future nextPage() {
pageController.animateToPage(pageController.page.toInt() + 1,
duration: Duration(milliseconds: 400), curve: Curves.easeIn);
}
void previousPage() {
pageController.animateToPage(pageController.page.toInt() - 1,
duration: Duration(milliseconds: 400), curve: Curves.easeIn);
}
getDocs() async {
await FirebaseFirestore.instance
.collection(
"categories/${widget.categoryId}/subcategories/${widget.subcategoryId}/tests/${widget.testId}/questions")
.get()
.then((value) {
setState(() {
testQuestPiece = value.docs.length;
});
});
}
#override
void initState() {
print(controller.position);
controller.updatePosition(0);
getDocs();
super.initState();
}
#override
Widget build(BuildContext context) {
Color color = randomColor.randomColor();
int selectedItem;
bool selectedBool = false;
return Scaffold(
backgroundColor: color,
bottomNavigationBar: Container(
child: DotsIndicator(
dotsCount: testQuestPiece,
position: controller.position,
axis: Axis.horizontal,
reversed: false,
decorator: DotsDecorator(
color: Colors.white,
activeColor: randomColor.randomColor(),
size: const Size.square(9.0),
activeShape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0)),
activeSize: const Size(18.0, 18.0),
),
mainAxisAlignment: MainAxisAlignment.center,
)),
appBar: AppBar(
centerTitle: true,
title: Text(
widget.title,
style: TextStyle(fontWeight: FontWeight.bold),
),
leading: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: Icon(Icons.arrow_back_ios),
color: Colors.white,
),
backgroundColor: Colors.transparent,
actions: [
IconButton(
icon: Icon(Icons.timer),
onPressed: () {},
)
]),
body: Stack(
fit: StackFit.expand,
children: [
StreamBuilder(
stream: FirebaseFirestore.instance
.collection(
"categories/${widget.categoryId}/subcategories/${widget.subcategoryId}/tests/${widget.testId}/questions")
.snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
return PageView(
physics: NeverScrollableScrollPhysics(),
allowImplicitScrolling: false,
scrollDirection: Axis.horizontal,
controller: pageController,
onPageChanged: controller.updatePosition,
children: snapshot.data.docs.map<Widget>((data) {
var datas = data.data();
return Stack(
fit: StackFit.expand,
children: [
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 350,
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30)),
height: 200,
child: Text(datas['questionTitle'],
style: TextStyle(
color: Colors.black, fontSize: 20))),
SizedBox(
height: 15,
),
Expanded(
child: ListView.builder(
itemCount: datas['answers'].length,
itemBuilder: (context, index) {
return Column(
children: [
InkWell(
onTap: () async {
if (datas['trueAnswer'] == index) {
audioCache
.play("sounds/correct.wav");
} else {
audioCache.play("sounds/wrong.wav");
setState(() {
selectedItem =
datas['trueAnswer'];
selectedBool = true;
});
}
await Future.delayed(
Duration(seconds: 2))
.then((value) {
print(pageController.page.toInt());
print(testQuestPiece);
if ((pageController.page.toInt() +
1) ==
testQuestPiece) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) =>
ResultScreen()));
} else {
nextPage();
}
});
},
child: Container(
width: 350,
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
color: selectedBool == true
? (selectedItem == index
? Colors.green
: Colors.red)
: Colors.white,
borderRadius:
BorderRadius.circular(30)),
height: 80,
child: Text(
"${index + 1}. " +
datas['answers'][index],
style: TextStyle(
color: Colors.black,
fontSize: 20)),
),
),
SizedBox(
height: 15,
),
],
);
},
),
),
],
),
),
],
);
}).toList(),
);
},
),
],
),
);
}
}

Flutter Firebase Iterator Error with FutureBuilder

Running my App on Android and iOS I get the Error shown below. What I want to do, give every Data that get's added a Author, the Author should be the Mail Adress the User Registers with, so by Inviting other Users get Access to this Data.
Another Problem is that in Line 302 the String "user" is unused.
Do i really need to use this String?
Error
════════ Exception caught by widgets library ═══════════════════════════════════
The following NoSuchMethodError was thrown building FutureBuilder<FirebaseUser>(dirty, state: _FutureBuilderState<FirebaseUser>#7f7ed):
The getter 'iterator' was called on null.
Receiver: null
Tried calling: iterator
The relevant error-causing widget was
FutureBuilder<FirebaseUser>
lib/…/MealPlan/mealTile.dart:92
When the exception was thrown, this was the stack
Code
import 'package:flutter/material.dart';
import 'package:mealapp/models/Widgets/whenAndWhatToEat.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:intl/intl.dart';
import 'package:mealapp/models/global.dart';
import 'package:status_alert/status_alert.dart';
import 'package:firebase_auth/firebase_auth.dart';
class MealTile extends StatefulWidget {
final MealsAndWhen mealsAndWhen;
MealTile({this.mealsAndWhen});
#override
MealTileState createState() {
return MealTileState();
}
}
class MealTileState extends State<MealTile> {
String id;
final db = Firestore.instance;
String mail;
List<String> authors = [];
DateTime selectedDate = DateTime.now();
Future pickDate() async {
DateTime datepick = await showDatePicker(
context: context,
initialDate: new DateTime.now(),
firstDate: new DateTime.now().add(Duration(days: 0)),
lastDate: new DateTime.now().add(Duration(days: 365)));
if (datepick != null)
setState(() {
selectedDate = datepick;
});
}
Future<String> inputData() async {
final FirebaseUser user = await FirebaseAuth.instance.currentUser();
return user != null ? user.uid : null;
}
Future<String> inputDataMail() async {
final FirebaseUser user = await FirebaseAuth.instance.currentUser();
return user != null ? user.email : null;
}
String userId;
void _getUserId() {
inputData().then((value) => setState(() {
userId = value;
}));
}
String currentMail;
void _getMail(doc) {
inputDataMail().then((value) => setState(() {
currentMail = value;
}));
}
/*void _getAuthors(DocumentSnapshot doc) async {
authors = [];
//if (await FirebaseAuth.instance.currentUser() != null) {
authors = List.from(doc.data['Authors']);
print(doc.data['authors']);
//authors.insert(0, currentMail);
//}
}*/
Widget buildItem(DocumentSnapshot doc) {
DateTime now = doc.data['Date'].toDate();
DateFormat formatter = DateFormat('dd-MM-yyyy');
String formatted = formatter.format(now);
_getUserId();
_getMail(doc);
if (doc.data['Authors'] != null) {
//_getAuthors(doc);
//print('Current mail: ' + currentMail + authors.toString() + doc.data['Author'] + doc.data['Meal']);
}
if (now.day == DateTime.now().day) { // If the Date of the meal is today
deleteData(doc, false); // Delete it!
}
// You could also change ".day" to ".hour".
// Example: if (now.day == DateTime.now().day && now.hour == DateTime.hour())
// So, if a meal is set for 2PM, it will delete at 2PM
return FutureBuilder<FirebaseUser>(
future: FirebaseAuth.instance.currentUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.hasData && snapshot != null) {
return Container(
margin: const EdgeInsets.all(8.0),
child: currentMail == doc.data['Author'] || // If the current mail is the author
List.from(doc.data['Authors']).contains(currentMail) // Or if the current mail is part of the authors
? Column( // then if true, show a Column
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
'Meal:',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white),
textAlign: TextAlign.center,
),
Text(
'${doc.data['Meal']}',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white),
textAlign: TextAlign.center,
),
SizedBox(height: 20),
Text(
'When:',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white),
textAlign: TextAlign.center,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(
onPressed: () => updateData(doc),
color: lightBlueColor,
icon: Icon(Icons.calendar_today,
color: Colors.white),
tooltip: 'Update Date',
),
Text(
formatted,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white),
textAlign: TextAlign.center,
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
SizedBox(width: 8),
FlatButton(
color: Colors.red,
onPressed: () => deleteData(doc, true),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadiusDirectional.circular(12)),
child: Row(children: <Widget>[
Text('Delete',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white)),
Icon(Icons.delete_forever, color: Colors.white),
]),
),
SizedBox(width: 8),
FlatButton(
color: Colors.blue,
onPressed: () => [
showDialog(
context: context,
builder: (BuildContext context) {
return Dialog(
child: invite(doc),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(12)),
),
);
})
],
shape: RoundedRectangleBorder(
borderRadius:
BorderRadiusDirectional.circular(12)),
child: Row(children: <Widget>[
Text('Invite',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white)),
Icon(Icons.share, color: Colors.white),
]),
),
],
),
],
)
: Text(''), // if false, show an empty text widget
decoration: BoxDecoration(
color: lightBlueColor,
borderRadius: BorderRadius.all(Radius.circular(12)),
),
);
}
else {
return CircularProgressIndicator();
}
/*Navigator.pop(context);
return HomePage();*/
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: darkGreyColor,
body: ListView(
padding: EdgeInsets.only(top: 220),
children: <Widget>[
StreamBuilder<QuerySnapshot>(
stream: db
.collection('mealList')
.orderBy('Date', descending: false) // Order by Date, not descending
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
children: snapshot.data.documents
.map((doc) => buildItem(doc))
.toList());
} else {
return Container();
}
},
),
],
),
);
}
/*share(BuildContext context, DocumentSnapshot doc) {
final RenderBox box = context.findRenderObject();
final dynamic date = timeago.format(doc['Date'].toDate());
Share.share(
"${doc['Meal']} - $date",
subject: doc['Meal'],
sharePositionOrigin: box.localToGlobal(Offset.zero) & box.size,
);
}*/
Widget invite(DocumentSnapshot doc) {
final _formKey = GlobalKey<FormState>();
return Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Center(
child: Text(
"Invite someone by mail",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
)),
SizedBox(
height: 24,
),
TextFormField(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12))),
labelText: 'Enter the email address'),
validator: (value) {
if (value.isEmpty) {
return 'Please enter an email address';
}
return null;
},
onSaved: (value) => mail = value,
),
FlatButton(
onPressed: () async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
saveInviteToFirestore(doc, mail);
}
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12))),
child: Text("Save"),
color: redColor,
textColor: Colors.white,
),
]),
),
);
}
Future<String> getCurrentUser() async {
return await FirebaseAuth.instance.currentUser().then((value) => value.uid);
}
void saveInviteToFirestore(DocumentSnapshot doc, String email) async {
final String user = await getCurrentUser();
var list = List<String>();
list.add(email);
Firestore.instance
.collection('mealList')
.document(doc.documentID)
.updateData({"Authors": FieldValue.arrayUnion(list)});
//setState(() => id = doc.documentID);
StatusAlert.show(
context,
duration: Duration(seconds: 2),
title: 'Added',
subtitle: 'You have Added your and the Date to your List',
configuration: IconConfiguration(icon: Icons.done),
);
//Navigator.pop(context);
}
void deleteData(DocumentSnapshot doc, bool showMessage) async {
await db.collection('mealList').document(doc.documentID).delete();
setState(() => id = null);
if (showMessage) {
StatusAlert.show(
context,
duration: Duration(seconds: 2),
title: 'Deleted',
subtitle: 'You have Deleted your Meal',
configuration: IconConfiguration(icon: Icons.delete),
);
}
}
void updateData(DocumentSnapshot doc) async {
await pickDate();
await db
.collection('mealList')
.document(doc.documentID)
.updateData({'Date': selectedDate});
StatusAlert.show(
context,
duration: Duration(seconds: 2),
title: 'Updated',
subtitle: 'You have updated your Meal Date',
configuration: IconConfiguration(icon: Icons.done),
);
}
}

Flutter & Firebase: Error with FutureBilder

Currently i develop a Meal and Shopping App. In this App you can Add what you want to Eat next and have the secound Tab, Shopping where you can Add your Items you want to buy next. Created is that a User can invite another User to edit together the List.
I get the Error shown below. I can't figure out how to return the Container. At the void saveInviteToFirestore the user is not used do I need that it used?
Code
import 'package:flutter/material.dart';
import 'package:mealapp/models/Widgets/whenAndWhatToEat.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:intl/intl.dart';
import 'package:mealapp/models/global.dart';
import 'package:status_alert/status_alert.dart';
import 'package:firebase_auth/firebase_auth.dart';
class MealTile extends StatefulWidget {
final MealsAndWhen mealsAndWhen;
MealTile({this.mealsAndWhen});
#override
MealTileState createState() {
return MealTileState();
}
}
class MealTileState extends State<MealTile> {
String id;
final db = Firestore.instance;
String mail;
List<String> authors = [];
DateTime selectedDate = DateTime.now();
Future pickDate() async {
DateTime datepick = await showDatePicker(
context: context,
initialDate: new DateTime.now(),
firstDate: new DateTime.now().add(Duration(days: -0)),
lastDate: new DateTime.now().add(Duration(days: 365)));
if (datepick != null)
setState(() {
selectedDate = datepick;
});
}
Future<String> inputData() async {
final FirebaseUser user = await FirebaseAuth.instance.currentUser();
return user != null ? user.uid : null;
}
Future<String> inputDataMail() async {
final FirebaseUser user = await FirebaseAuth.instance.currentUser();
return user != null ? user.email : null;
}
String userId;
void _getUserId() {
inputData().then((value) => setState(() {
userId = value;
}));
}
String currentMail;
void _getMail(doc) {
inputDataMail().then((value) => setState(() {
currentMail = value;
}));
}
/*void _getAuthors(DocumentSnapshot doc) async {
authors = [];
//if (await FirebaseAuth.instance.currentUser() != null) {
authors = List.from(doc.data['Authors']);
print(doc.data['authors']);
//authors.insert(0, currentMail);
//}
}*/
Widget buildItem(DocumentSnapshot doc) {
DateTime now = doc.data['Date'].toDate();
DateFormat formatter = DateFormat('dd-MM-yyyy');
String formatted = formatter.format(now);
_getUserId();
_getMail(doc);
if (doc.data['Authors'] != null) {
//_getAuthors(doc);
//print('Current mail: ' + currentMail + authors.toString() + doc.data['Author'] + doc.data['Meal']);
}
if (now.day == DateTime.now().day) { // If the Date of the meal is today
deleteData(doc, false); // Delete it!
}
// You could also change ".day" to ".hour".
// Example: if (now.day == DateTime.now().day && now.hour == DateTime.hour())
// So, if a meal is set for 2PM, it will delete at 2PM
return FutureBuilder<FirebaseUser>(
future: FirebaseAuth.instance.currentUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.hasData && snapshot != null) {
return Container(
margin: const EdgeInsets.all(8.0),
child: currentMail == doc.data['Author'] || // If the current mail is the author
List.from(doc.data['Authors']).contains(currentMail) // Or if the current mail is part of the authors
? Column( // then if true, show a Column
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
'Meal:',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white),
textAlign: TextAlign.center,
),
Text(
'${doc.data['Meal']}',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white),
textAlign: TextAlign.center,
),
SizedBox(height: 20),
Text(
'When:',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white),
textAlign: TextAlign.center,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(
onPressed: () => updateData(doc),
color: lightBlueColor,
icon: Icon(Icons.calendar_today,
color: Colors.white),
tooltip: 'Update Date',
),
Text(
formatted,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white),
textAlign: TextAlign.center,
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
SizedBox(width: 8),
FlatButton(
color: Colors.red,
onPressed: () => deleteData(doc, true),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadiusDirectional.circular(12)),
child: Row(children: <Widget>[
Text('Delete',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white)),
Icon(Icons.delete_forever, color: Colors.white),
]),
),
SizedBox(width: 8),
FlatButton(
color: Colors.blue,
onPressed: () => [
showDialog(
context: context,
builder: (BuildContext context) {
return Dialog(
child: invite(doc),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(12)),
),
);
})
],
shape: RoundedRectangleBorder(
borderRadius:
BorderRadiusDirectional.circular(12)),
child: Row(children: <Widget>[
Text('Invite',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white)),
Icon(Icons.share, color: Colors.white),
]),
),
],
),
],
)
: Text(''), // if false, show an empty text widget
decoration: BoxDecoration(
color: lightBlueColor,
borderRadius: BorderRadius.all(Radius.circular(12)),
),
);
}
/*Navigator.pop(context);
return HomePage();*/
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: darkGreyColor,
body: ListView(
padding: EdgeInsets.only(top: 220),
children: <Widget>[
StreamBuilder<QuerySnapshot>(
stream: db
.collection('mealList')
.orderBy('Date', descending: false) // Order by Date, not descending
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
children: snapshot.data.documents
.map((doc) => buildItem(doc))
.toList());
} else {
return Container();
}
},
),
],
),
);
}
/*share(BuildContext context, DocumentSnapshot doc) {
final RenderBox box = context.findRenderObject();
final dynamic date = timeago.format(doc['Date'].toDate());
Share.share(
"${doc['Meal']} - $date",
subject: doc['Meal'],
sharePositionOrigin: box.localToGlobal(Offset.zero) & box.size,
);
}*/
Widget invite(DocumentSnapshot doc) {
final _formKey = GlobalKey<FormState>();
return Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Center(
child: Text(
"Invite someone by mail",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
)),
SizedBox(
height: 24,
),
TextFormField(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12))),
labelText: 'Enter the email address'),
validator: (value) {
if (value.isEmpty) {
return 'Please enter an email address';
}
return null;
},
onSaved: (value) => mail = value,
),
FlatButton(
onPressed: () async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
saveInviteToFirestore(doc, mail);
}
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12))),
child: Text("Save"),
color: redColor,
textColor: Colors.white,
),
]),
),
);
}
Future<String> getCurrentUser() async {
return await FirebaseAuth.instance.currentUser().then((value) => value.uid);
}
void saveInviteToFirestore(DocumentSnapshot doc, String email) async {
final String user = await getCurrentUser();
var list = List<String>();
list.add(email);
Firestore.instance
.collection('mealList')
.document(doc.documentID)
.updateData({"Authors": FieldValue.arrayUnion(list)});
//setState(() => id = doc.documentID);
StatusAlert.show(
context,
duration: Duration(seconds: 2),
title: 'Added',
subtitle: 'You have Added your and the Date to your List',
configuration: IconConfiguration(icon: Icons.done),
);
//Navigator.pop(context);
}
void deleteData(DocumentSnapshot doc, bool showMessage) async {
await db.collection('mealList').document(doc.documentID).delete();
setState(() => id = null);
if (showMessage) {
StatusAlert.show(
context,
duration: Duration(seconds: 2),
title: 'Deleted',
subtitle: 'You have Deleted your Meal',
configuration: IconConfiguration(icon: Icons.delete),
);
}
}
void updateData(DocumentSnapshot doc) async {
await pickDate();
await db
.collection('mealList')
.document(doc.documentID)
.updateData({'Date': selectedDate});
StatusAlert.show(
context,
duration: Duration(seconds: 2),
title: 'Updated',
subtitle: 'You have updated your Meal Date',
configuration: IconConfiguration(icon: Icons.done),
);
}
}
Error
The following assertion was thrown building FutureBuilder<FirebaseUser>(dirty, state: _FutureBuilderState<FirebaseUser>#a4504):
A build function returned null.
The offending widget is: FutureBuilder<FirebaseUser>
Build functions must never return null.
To return an empty space that causes the building widget to fill available room, return "Container()". To return an empty space that takes as little room as possible, return "Container(width: 0.0, height: 0.0)".
The relevant error-causing widget was
FutureBuilder<FirebaseUser>
lib/…/MealPlan/mealTile.dart:92
When the exception was thrown, this was the stack
#0 debugWidgetBuilderValue.<anonymous closure>
package:flutter/…/widgets/debug.dart:276
In your FutureBuilder you are not returning anything when the Future hasn't completed yet. A widget always needs to be returned whether there is data or not.
Example fix for your code:
return FutureBuilder<FirebaseUser>(
future: FirebaseAuth.instance.currentUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.hasData && snapshot != null) {
return Container(
...
);
}
//ADDED ELSE BLOCK
else {
return Container();
}
}
);
Or as #stacker suggested, you can return a CircularProgressIndicator().

How to perform CRUD operations with BLoC pattern?

I was learning on flutter how to perform CRUD operations using BLoC pattern, I saw a tutorial online and try to apply it. C,R,U are OK, but deletion is not working. this is the source code.
Please Help !
file todo_bloc.dart
import 'package:zencartos/models/todo.dart';
import 'package:zencartos/repository/todo_repository.dart';
class TodoBloc {
//Get instance of the Repository
final _todoRepository = TodoRepository();
final _todoController = StreamController<List<Todo>>.broadcast();
get todos => _todoController.stream;
TodoBloc() {
getTodos();
}
getTodos({String query}) async {
_todoController.sink.add(await _todoRepository.getAllTodos(query: query));
}
addTodo(Todo todo) async {
await _todoRepository.insertTodo(todo);
getTodos();
}
updateTodo(Todo todo) async {
await _todoRepository.updateTodo(todo);
getTodos();
}
deleteTodoById(int id) async {
_todoRepository.deleteTodoById(id);
getTodos();
}
dispose() {
_todoController.close();
}
}
DAO Page todo_dao.dart
import 'package:zencartos/database/database.dart';
import 'package:zencartos/models/todo.dart';
class TodoDao {
final dbProvider = DatabaseProvider.dbProvider;
Future<int> createTodo(Todo todo) async{
final db = await dbProvider.database;
var result = db.insert(todoTABLE, todo.toDatabaseJson());
return result;
}
Future<List<Todo>> getTodos({List<String> columns, String query})
async{
final db = await dbProvider.database;
List<Map<String, dynamic>> result;
if (query != null){
if (query.isNotEmpty)
result = await db.query(todoTABLE, columns: columns, where: 'description LIKE ?', whereArgs: ["%$query%"]);
} else {
result = await db.query(todoTABLE, columns: columns);
}
List<Todo> todos = result.isNotEmpty
? result.map((item)=> Todo.fromDatabaseJson(item)).toList()
: [];
return todos;
}
//Update Todo record
Future<int> updateTodo(Todo todo) async{
final db = await dbProvider.database;
var result = await db.update(todoTABLE, todo.toDatabaseJson(),
where: "id = ?", whereArgs: [todo.id]);
return result;
}
//Delete Todo records
Future<int> deleteTodo(int id) async{
final db = await dbProvider.database;
var result = await db.delete(todoTABLE, where: 'id = ?', whereArgs: [id]);
return result;
}
}
Database Page database.dart
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
final todoTABLE = 'Todo';
class DatabaseProvider {
static final DatabaseProvider dbProvider = DatabaseProvider();
Database _database;
Future<Database> get database async {
if (_database != null) return _database;
_database = await createDatabase();
return _database;
}
createDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "ReactiveTodo");
var database = await openDatabase(path, version: 1, onCreate: initDB, onUpgrade: onUpgrade);
return database;
}
void onUpgrade(Database database, int oldVersion, int newVersion){
if (newVersion > oldVersion){}
}
void initDB(Database database, int version) async{
await database.execute("CREATE TABLE $todoTABLE ("
"id INTEGER PRIMARY KEY, "
"description TEXT, "
"is_done INTEGER "
")");
}
}
Model page todo.dart
class Todo{
int id;
String description;
bool isDone = false;
Todo({this.id, this.description, this.isDone = false});
factory Todo.fromDatabaseJson(Map<String, dynamic> data) => Todo(
id: data['data'],
description: data['description'],
isDone: data['is_done'] == 0 ? false : true,
);
Map<String, dynamic> toDatabaseJson() => {
"id": this.id,
"description": this.description,
"is_done": this.isDone == false ? 0 : 1,
};
}
View page home_Page2.dart
import 'package:flutter/services.dart';
import 'package:zencartos/bloc/todo_bloc.dart';
import 'package:zencartos/models/todo.dart';
class HomePage2 extends StatelessWidget {
HomePage2({Key key, this.title}) : super(key: key);
final TodoBloc todoBloc = TodoBloc();
final String title;
//Allows Todo card to be dismissable horizontally
final DismissDirection _dismissDirection = DismissDirection.horizontal;
#override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark.copyWith(
statusBarColor: Colors.white,
systemNavigationBarColor: Colors.white,
systemNavigationBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.dark));
return Scaffold(
resizeToAvoidBottomPadding: false,
body: SafeArea(
child: Container(
color: Colors.white,
padding:
const EdgeInsets.only(left: 2.0, right: 2.0, bottom: 2.0),
child: Container(
//This is where the magic starts
child: getTodosWidget()))),
bottomNavigationBar: BottomAppBar(
color: Colors.white,
child: Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: Colors.grey, width: 0.3),
)),
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
IconButton(
icon: Icon(
Icons.menu,
color: Colors.indigoAccent,
size: 28,
),
onPressed: () {
//just re-pull UI for testing purposes
todoBloc.getTodos();
}),
Expanded(
child: Text(
"Todo",
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
fontFamily: 'RobotoMono',
fontStyle: FontStyle.normal,
fontSize: 19),
),
),
Wrap(children: <Widget>[
IconButton(
icon: Icon(
Icons.search,
size: 28,
color: Colors.indigoAccent,
),
onPressed: () {
_showTodoSearchSheet(context);
},
),
Padding(
padding: EdgeInsets.only(right: 5),
)
])
],
),
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: Padding(
padding: EdgeInsets.only(bottom: 25),
child: FloatingActionButton(
elevation: 5.0,
onPressed: () {
_showAddTodoSheet(context);
},
backgroundColor: Colors.white,
child: Icon(
Icons.add,
size: 32,
color: Colors.indigoAccent,
),
),
));
}
void _showAddTodoSheet(BuildContext context) {
final _todoDescriptionFormController = TextEditingController();
showModalBottomSheet(
context: context,
builder: (builder) {
return new Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
child: new Container(
color: Colors.transparent,
child: new Container(
height: 230,
decoration: new BoxDecoration(
color: Colors.white,
borderRadius: new BorderRadius.only(
topLeft: const Radius.circular(10.0),
topRight: const Radius.circular(10.0))),
child: Padding(
padding: EdgeInsets.only(
left: 15, top: 25.0, right: 15, bottom: 30),
child: ListView(
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextFormField(
controller: _todoDescriptionFormController,
textInputAction: TextInputAction.newline,
maxLines: 4,
style: TextStyle(
fontSize: 21, fontWeight: FontWeight.w400),
autofocus: true,
decoration: const InputDecoration(
hintText: 'I have to...',
labelText: 'New Todo',
labelStyle: TextStyle(
color: Colors.indigoAccent,
fontWeight: FontWeight.w500)),
validator: (String value) {
if (value.isEmpty) {
return 'Empty description!';
}
return value.contains('')
? 'Do not use the # char.'
: null;
},
),
),
Padding(
padding: EdgeInsets.only(left: 5, top: 15),
child: CircleAvatar(
backgroundColor: Colors.indigoAccent,
radius: 18,
child: IconButton(
icon: Icon(
Icons.save,
size: 22,
color: Colors.white,
),
onPressed: () {
final newTodo = Todo(
description:
_todoDescriptionFormController
.value.text);
if (newTodo.description.isNotEmpty) {
/*Create new Todo object and make sure
the Todo description is not empty,
because what's the point of saving empty
Todo
*/
todoBloc.addTodo(newTodo);
//dismisses the bottomsheet
Navigator.pop(context);
}
},
),
),
)
],
),
],
),
),
),
),
);
});
}
void _showTodoSearchSheet(BuildContext context) {
final _todoSearchDescriptionFormController = TextEditingController();
showModalBottomSheet(
context: context,
builder: (builder) {
return new Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
child: new Container(
color: Colors.transparent,
child: new Container(
height: 230,
decoration: new BoxDecoration(
color: Colors.white,
borderRadius: new BorderRadius.only(
topLeft: const Radius.circular(10.0),
topRight: const Radius.circular(10.0))),
child: Padding(
padding: EdgeInsets.only(
left: 15, top: 25.0, right: 15, bottom: 30),
child: ListView(
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextFormField(
controller: _todoSearchDescriptionFormController,
textInputAction: TextInputAction.newline,
maxLines: 4,
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.w400),
autofocus: true,
decoration: const InputDecoration(
hintText: 'Search for todo...',
labelText: 'Search *',
labelStyle: TextStyle(
color: Colors.indigoAccent,
fontWeight: FontWeight.w500),
),
validator: (String value) {
return value.contains('#')
? 'Do not use the # char.'
: null;
},
),
),
Padding(
padding: EdgeInsets.only(left: 5, top: 15),
child: CircleAvatar(
backgroundColor: Colors.indigoAccent,
radius: 18,
child: IconButton(
icon: Icon(
Icons.search,
size: 22,
color: Colors.white,
),
onPressed: () {
/*This will get all todos
that contains similar string
in the textform
*/
todoBloc.getTodos(
query:
_todoSearchDescriptionFormController
.value.text);
//dismisses the bottomsheet
Navigator.pop(context);
},
),
),
)
],
),
],
),
),
),
),
);
});
}
Widget getTodosWidget() {
return StreamBuilder(
stream: todoBloc.todos,
builder: (BuildContext context, AsyncSnapshot<List<Todo>> snapshot) {
return getTodoCardWidget(snapshot);
},
);
}
Widget getTodoCardWidget(AsyncSnapshot<List<Todo>> snapshot) {
if (snapshot.hasData) {
return snapshot.data.length != 0
? ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, itemPosition) {
Todo todo = snapshot.data[itemPosition];
final Widget dismissibleCard = new Dismissible(
background: Container(
child: Padding(
padding: EdgeInsets.only(left: 10),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
"Deleting",
style: TextStyle(color: Colors.white),
),
),
),
color: Colors.redAccent,
),
onDismissed: (direction) {
todoBloc.deleteTodoById(todo.id);
},
direction: _dismissDirection,
key: new ObjectKey(todo),
child: Card(
shape: RoundedRectangleBorder(
side: BorderSide(color: Colors.grey[200], width: 0.5),
borderRadius: BorderRadius.circular(5),
),
color: Colors.white,
child: ListTile(
leading: InkWell(
onTap: () {
//Reverse the value
todo.isDone = !todo.isDone;
todoBloc.updateTodo(todo);
},
child: Container(
//decoration: BoxDecoration(),
child: Padding(
padding: const EdgeInsets.all(15.0),
child: todo.isDone
? Icon(
Icons.done,
size: 26.0,
color: Colors.indigoAccent,
)
: Icon(
Icons.check_box_outline_blank,
size: 26.0,
color: Colors.tealAccent,
),
),
),
),
title: Text(
todo.description,
style: TextStyle(
fontSize: 16.5,
fontFamily: 'RobotoMono',
fontWeight: FontWeight.w500,
decoration: todo.isDone
? TextDecoration.lineThrough
: TextDecoration.none),
),
)),
);
return dismissibleCard;
},
)
: Container(
child: Center(
//this is used whenever there 0 Todo
//in the data base
child: noTodoMessageWidget(),
));
} else {
return Center(
child: loadingData(),
);
}
}
Widget loadingData() {
todoBloc.getTodos();
return Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(),
Text("Loading...",
style: TextStyle(fontSize: 19, fontWeight: FontWeight.w500))
],
),
),
);
}
Widget noTodoMessageWidget() {
return Container(
child: Text(
"Start adding Todo...",
style: TextStyle(fontSize: 19, fontWeight: FontWeight.w500),
),
);
}
dispose() {
todoBloc.dispose();
}
}
repository page todo_repository.dart
import 'package:zencartos/models/todo.dart';
class TodoRepository {
final todoDao = TodoDao();
Future getAllTodos({String query}) => todoDao.getTodos(query: query);
Future insertTodo(Todo todo) => todoDao.createTodo(todo);
Future updateTodo(Todo todo) => todoDao.updateTodo(todo);
Future deleteTodoById(int id) => todoDao.deleteTodo(id);
//We are not going to use this in the demo
//Future deleteAllTodos() => todoDao.deleteAllTodos();
}
In your todo_bloc.dart, your delete method should be:
deleteTodoById(int id) async {
await _todoRepository.deleteTodoById(id);
getTodos();
}

Resources