My app uses a Firestore database to store all of its data. I present the data in lists throughout my app by using a FutureBuilder to get the data from Firestore. The PROBLEM is that my app gets slower and slower as you use it. I checked the memory usage and it continues to grow at the same rate that the speed of the app slows down. I 'think' this is because of my usage of FutureBuilder. Does ANYONE know of a more efficient way to write this code so that it doesn't leak memory and slow down?
This is my HomePage
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
TextEditingController _searchController = TextEditingController();
rive.Artboard _artboard;
RefreshController _controller;
bool _searchNotification = false;
bool _addNotification = false;
bool _personNotification = false;
bool splitErrorBool = false;
int sum;
bool phoneMode;
#override
void initState() {
_loadRiveFile();
super.initState();
}
/// Loads a Rive file
void _loadRiveFile() async {
final bytes = await rootBundle.load('assets/space_reload.riv');
final file = rive.RiveFile();
if (file.import(bytes)) {
setState(() => _artboard = file.mainArtboard
..addController(_controller = RefreshController()));
}
}
void _newNotification() {
setState(() {
_searchNotification = true;
_addNotification = true;
_personNotification = true;
});
}
#override
void dispose() {
super.dispose();
}
Widget buildRefreshWidget(
BuildContext context,
RefreshIndicatorMode refreshState,
double pulledExtent,
double refreshTriggerPullDistance,
double refreshIndicatorExtent) {
_controller.refreshState = refreshState;
_controller.pulledExtent = pulledExtent;
_controller.triggerThreshold = refreshTriggerPullDistance;
_controller.refreshIndicatorExtent = refreshIndicatorExtent;
return _artboard != null
? rive.Rive(
artboard: _artboard, fit: BoxFit.cover, alignment: Alignment.center)
: Container();
}
#override
Widget build(BuildContext context) {
double x = MediaQuery.of(context).size.width;
double y = MediaQuery.of(context).size.height;
if (x < 600) {
setState(() {
phoneMode = true;
});
} else {
setState(() {
phoneMode = false;
});
}
Location sharedData = Provider.of<Location>(context, listen: false);
List<String> searchList = _searchController.text.toUpperCase().split(' ');
List<String> endSearchList =
(_searchController.text.toUpperCase() + "\uf8ff").split(' ');
return FutureBuilder(
future: Collection<Report>(
path: '${sharedData.location}/data/reports',
searchText: searchList,
endSearchList: endSearchList)
.getName(),
builder: (BuildContext context, AsyncSnapshot snap) {
if (snap.hasData) {
List<Report> reports = snap.data;
sum = reports
.map((expense) => expense.split)
.fold(0, (prev, amount) => prev + amount);
if (sum != 100) {
splitErrorBool = true;
} else {
splitErrorBool = false;
}
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text(
sharedData.location,
style: TextStyle(
fontFamily: 'Open Sans',
fontWeight: FontWeight.bold,
fontSize: phoneMode ? 23.sp : 20),
),
centerTitle: true,
leading: Padding(
padding: const EdgeInsets.only(left: 15),
child: Image.asset(
'assets/ledger_logo.png',
),
),
actions: [
Visibility(
visible: splitErrorBool,
child: IconButton(
icon: Icon(
FontAwesome5Solid.hand_paper,
color: Colors.red,
size: phoneMode ? 30.sp : null,
),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return StopDialog();
});
}),
),
IconButton(
icon: Icon(
Icons.search,
size: phoneMode ? 30.sp : null,
),
onPressed: () {
showSearch(context: context, delegate: UserSearch());
setState(() {
_searchNotification = false;
});
}),
IconButton(
icon: Icon(
Icons.add,
size: phoneMode ? 30.sp : null,
),
onPressed: () {
Navigator.pushNamed(context, '/createUser');
setState(() {
_addNotification = false;
});
}),
Padding(
padding: EdgeInsets.only(right: phoneMode ? 10.w : 10),
child: IconButton(
icon: Icon(
Icons.person,
size: phoneMode ? 30.sp : null,
),
onPressed: () {
Navigator.pushNamed(context, '/profile');
setState(() {
_personNotification = false;
});
}),
),
],
),
body: NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollEndNotification) {
_controller.reset();
}
return true;
},
child: CustomScrollView(
physics: BouncingScrollPhysics(),
slivers: [
CupertinoSliverRefreshControl(
refreshTriggerPullDistance: 240.0,
refreshIndicatorExtent: 240.0,
builder: buildRefreshWidget,
onRefresh: () {
return Future<void>.delayed(const Duration(seconds: 5))
..then<void>((_) {
if (mounted) {
setState(() {});
}
});
},
),
SliverSafeArea(
top: false,
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
EdgeInsets.only(left: phoneMode ? 5 : 10.0),
child: Column(
children: [
Container(
width: phoneMode ? 160.w : x * .385,
height: phoneMode
? MediaQuery.of(context).size.height -
75.h
: MediaQuery.of(context).size.height -
75,
child: Padding(
padding: const EdgeInsets.only(
bottom: 20, top: 20),
child: ListView(
shrinkWrap: true,
padding:
const EdgeInsets.only(bottom: 10),
primary: false,
children: reports
.map((report) =>
UsersItem(report: report))
.toList(),
),
),
),
],
),
),
Expanded(
flex: 3,
child: Padding(
padding: const EdgeInsets.only(top: 20),
child: Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
InkWell(
onTap: () =>
Navigator.pushReplacementNamed(
context, '/pdf'),
child: SizedBox(
width:
phoneMode ? 105.w : x * .25,
height:
phoneMode ? 110.h : x * .23,
child: Container(
decoration: BoxDecoration(
border: Border.all(
width: phoneMode ? 4 : 5,
color: Colors.blue
.withOpacity(.5)),
borderRadius:
BorderRadius.circular(
15.0),
gradient: RadialGradient(
colors: [
const Color(0xFF5e5e5e),
const Color(0xFF424242),
],
// stops: [0.0, 0.1],
),
),
clipBehavior: Clip.antiAlias,
child: FittedBox(
fit: BoxFit.contain,
child: Padding(
padding:
const EdgeInsets.all(
7.0),
child: Column(
mainAxisAlignment:
MainAxisAlignment
.spaceEvenly,
children: [
Icon(
FlutterIcons
.file_pdf_mco,
color: Colors.blue,
size: phoneMode
? 50.w
: x * .14,
),
Text(
'Pdf',
style: GoogleFonts
.poppins(
fontSize:
phoneMode
? 15
.sp
: 20,
fontWeight:
FontWeight
.w400),
),
Visibility(
visible: true,
child: Text(
'Documents',
style: TextStyle(
fontSize:
phoneMode
? 11.sp
: 15,
fontWeight:
FontWeight
.w300),
),
),
],
),
),
),
),
),
),
InkWell(
onTap: () =>
Navigator.pushReplacementNamed(
context, '/reports'),
child: SizedBox(
width:
phoneMode ? 105.w : x * .25,
height:
phoneMode ? 110.h : x * .23,
child: Container(
decoration: BoxDecoration(
border: Border.all(
width: phoneMode ? 4 : 5,
color: Colors.green
.withOpacity(.5)),
borderRadius:
BorderRadius.circular(
15.0),
gradient: RadialGradient(
colors: [
const Color(0xFF5e5e5e),
const Color(0xFF424242),
],
// stops: [0.0, 0.1],
),
),
clipBehavior: Clip.antiAlias,
child: FittedBox(
fit: BoxFit.contain,
child: Padding(
padding:
const EdgeInsets.all(
7.0),
child: Column(
mainAxisAlignment:
MainAxisAlignment
.spaceEvenly,
children: [
Icon(
FlutterIcons
.chart_bubble_mco,
color: Colors.green,
size: phoneMode
? 50.w
: x * .14,
),
Text(
'Reports',
style: GoogleFonts
.poppins(
fontSize:
phoneMode
? 15
.sp
: 20,
fontWeight:
FontWeight
.w400),
),
Visibility(
visible: true,
child: Text(
'Datasheet',
style: GoogleFonts.poppins(
fontSize:
phoneMode
? 11.sp
: 15,
fontWeight:
FontWeight
.w300),
),
),
],
),
),
),
),
),
),
],
),
SizedBox(
height: 25.h,
),
Padding(
padding: EdgeInsets.only(
left: phoneMode ? 5.w : 10,
right: phoneMode ? 5.w : 10),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
phoneMode ? 20.w : 30.0),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
const Color(0xFF424242),
const Color(0xFF424242),
],
// stops: [0.0, 0.1],
),
),
child: Column(
children: [
Padding(
padding: EdgeInsets.only(
top: phoneMode ? 10.h : 20,
left: phoneMode ? 5.w : 5,
),
child: Row(children: [
SizedBox(
width:
phoneMode ? 5.w : 5),
Icon(
Icons.history,
color: Colors.white
.withOpacity(.8),
size: phoneMode ? 20.w : 25,
),
SizedBox(width: 5),
Text('Transaction History',
style: TextStyle(
fontSize: phoneMode
? 13.sp
: 20,
color: Colors.white
.withOpacity(
.8))),
]),
),
SizedBox(
height: phoneMode
? 510.h
: MediaQuery.of(context)
.size
.height *
.64,
child: Padding(
padding: EdgeInsets.only(
top: phoneMode
? 15.h
: 20,
left: 0,
right: 0,
bottom: phoneMode
? 10.h
: 30),
child:
SingleChildScrollView(
child: Column(
children: [
FutureBuilder(
future: Collection<
History>(
path:
'${sharedData.location}/data/history')
.getHistory(),
builder:
(BuildContext
context,
AsyncSnapshot
snap) {
if (snap
.hasData) {
List<History>
historyList =
snap.data;
return ListView(
shrinkWrap:
true,
primary:
false,
children:
historyList
.map((history) =>
HistoryItem(
history: history,
))
.toList(),
);
} else {
return Loader();
}
},
),
],
),
),
)),
],
),
),
),
],
),
),
),
],
);
}, childCount: 1),
),
),
],
),
),
);
} else {
return LoadingScreen();
}
});
}
}
That is just one page of many that uses FutureBuilder, and each time I click on a new page the memory usage gets larger. Any help is apprecieted!
Make sure to anyController.dispose() any controllers like RefreshController and TextEditingController in void dispose() {} to avoid memory leaks.
One problem with your code is that it calls setState() within the build() method. This is a problem because setState() will trigger a Widget rebuild, hence build() will be called again, which will in turn call setState() again, etc.
Related
IM trying to call on Tap method, what I want now is change it to a future method because on Tap the method is not ready because im calling a firebase query . So heres first my method
where the onTap function is
child: InkWell(
onTap: () async {
setState(() {
israting = true;
});
},
child: israting
? buildBody(videos.data()['likes'], videos.data()['id'])
: Icon(
Icons.star,
size: 37,
color: videos.data()['likes'].contains(uid)
? Colors.yellow
: Colors.white,
),
),
So im giving the buildBody 2 parameters from a streambuilder
and then thats the function
buildBody(videoid, video) async {
if (videoid.contains(uid)) {
await FirebaseFirestore.instance
.collection("videos")
.doc(video)
.collection("uservotes")
.doc(uid)
.get()
.then((value) {
votefromfirebase = value.data()["rating"];
});
return Container(
child: Stack(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(3, 7, 0, 0),
child: Align(
alignment: Alignment.topLeft,
child: RatingBarIndicator(
rating: votefromfirebase,
itemBuilder: (context, index) => InkWell(
child: Icon(
Icons.star,
color: Colors.amber,
),
),
itemCount: 5,
itemSize: 31.0,
direction: Axis.horizontal,
),
),
),
Align(
alignment: Alignment.topRight,
child: TextButton(
onPressed: () {
print(video);
letuservoting = true;
setState(() {
_userRating = 0.0;
israting = false;
});
dislike(idovvideo, _userRating);
},
child: Text(
"Clear",
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
),
],
));
} else {
return Container(
child: Stack(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(3, 7, 0, 0),
child: Align(
alignment: Alignment.topLeft,
child: RatingBarIndicator(
rating: _userRating,
itemBuilder: (context, index) => InkWell(
onTap: () {
setState(() {
_userRating = index + 1.toDouble();
});
likevideo(video, _userRating);
},
child: Icon(
Icons.star,
color: Colors.amber,
),
),
itemCount: 5,
itemSize: 31.0,
direction: Axis.horizontal,
),
),
),
Align(
alignment: Alignment.topRight,
child: TextButton(
onPressed: () {
letuservoting = true;
setState(() {
_userRating = 0.0;
israting = false;
});
dislike(idovvideo, _userRating);
},
child: Text(
"Clear",
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
),
],
),
);
}
}
buildprofile(String url) {
return Container(
width: 50,
height: 50,
child: Stack(
children: [
Positioned(
left: (50 / 2) - (50 / 2),
child: Container(
width: 50,
height: 50,
padding: EdgeInsets.all(1),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(25),
),
child: Container(
child: ClipRRect(
borderRadius: BorderRadius.circular(60),
child: Container(
height: 110,
width: 110,
decoration: BoxDecoration(
color: Colors.white,
),
child: url != null && url != "profilepictureer"
? Image.network(
url,
fit: BoxFit.cover,
)
: Image.asset(
'assets/profilepictureer.png') // Your widget is here when image is no available.
),
),
decoration: new BoxDecoration(
shape: BoxShape.circle,
border: new Border.all(color: Colors.black, width: 4)),
),
),
),
],
),
);
}
}
At the moment im getting this error
The following _TypeError was thrown building:
type 'Future<dynamic>' is not a subtype of type 'Widget?'
When the exception was thrown, this was the stack
#0 _VideopageofcurrentuserState.build.<anonymous closure>.<anonymous closure> (package:wichtigdenyady/taking%20videos/currentuservideos.dart:310:47)
#1 SliverChildBuilderDelegate.build
package:flutter/…/widgets/sliver.dart:455
#2 SliverMultiBoxAdaptorElement._build
package:flutter/…/widgets/sliver.dart:1201
#3 SliverMultiBoxAdaptorElement.performRebuild.processElement
package:flutter/…/widgets/sliver.dart:1145
#4 Iterable.forEach (dart:core/iterable.dart:257:30)
The error throws in the onTap function
The buildBody function cannot be an async function.
You should move the call to Firebase out of this function and use the result of it in state, instead.
An async function always returns a Future. Because the return type isn't defined, it assumes it will return a Future<dynamic>. If you defined the return type as Widget buildBody(videoid, video) async, the IDE would show an error.
In your onTap function, you can make the call:
onTap: () async {
setState(() {
israting = true;
});
if(!videos.data()['likes'].contains(uid)) return; // don't call firebase if uid is not in videoid
final value = await FirebaseFirestore.instance
.collection("videos")
.doc(video)
.collection("uservotes")
.doc(uid)
.get();
setState(() {
votefromfirebase = value.data()["rating"];
});
},
Next, change the buildBody function to a non-async function and check if votefromfirebase is in the state
Widget buildBody(videoid, video) {
if (votefromfirebase == null) {
return Container(); // return empty container, progress indicator, or anything else
}
// else return the normal body
return Container(
child: Stack(
// rest of this widget
I am getting this error in the poll post Page after added the voting feature to it:
The new method I added is handleTotalVotePosts to handle the count of userPostPolls(in line number 178).
I think the error is getting the userId or something which i am not Sure .
I am not sure what is actually wrong here
The method '[]' was called on null.
Receiver: null
Tried calling: []("0tm2JqPY0oNq5vSM74BqOufhGao1")
But code seems to be correct
This my **pollPost.dart**
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:justpoll/libmodels/user_model.dart';
import 'package:justpoll/screens/chat/chat_data.dart';
import 'package:justpoll/widgets/progress.dart';
import 'package:justpoll/widgets/toast_text.dart';
import '../Constants.dart';
import 'package:justpoll/widgets/custom_image.dart';
import 'package:animated_button/animated_button.dart';
class PollPost extends StatefulWidget {
final String pollPostId;
final String mediaUrl;
final String ownerId;
final String username;
final String caption;
final String category;
final String colorTheme;
final Timestamp duration;
final String question;
final dynamic votesTotal;
final String optText1;
final String optText2;
final String optText3;
final String optText4;
final String optemo1;
final String optemo2;
final String optemo3;
final String optemo4;
final String optCount1;
final String optCount2;
final String optCount3;
final String optCount4;
PollPost({
this.pollPostId,
this.mediaUrl,
this.ownerId,
this.username,
this.caption,
this.category,
this.colorTheme,
this.duration,
this.question,
this.votesTotal,
this.optText1,
this.optText2,
this.optText3,
this.optText4,
this.optemo1,
this.optemo2,
this.optemo3,
this.optemo4,
this.optCount1,
this.optCount2,
this.optCount3,
this.optCount4,
});
factory PollPost.fromDocument(DocumentSnapshot doc) {
return PollPost(
pollPostId: doc['pollPostid'],
mediaUrl: doc['mediaUrl'],
ownerId: doc['ownerId'],
username: doc['username'],
caption: doc['caption'],
category: doc['category'],
colorTheme: doc['colorTheme'],
duration: doc['Duration'],
question: doc['Question'],
votesTotal: doc['votesTotal'],
optText1: doc["options"]["1"]["optionText1"],
optText2: doc["options"]["2"]["optionText2"],
optText3: doc["options"]["3"]["optionText3"],
optText4: doc["options"]["4"]["optionText4"],
optemo1: doc["options"]["1"]["optionEmoji1"],
optemo2: doc["options"]["2"]["optionEmoji2"],
optemo3: doc["options"]["3"]["optionEmoji3"],
optemo4: doc["options"]["4"]["optionEmoji4"],
// optCount1: doc["options"]["1"]["optionCount1"],
// optCount2: doc["options"]["2"]["optionCount2"],
// optCount3: doc["options"]["3"]["optionCount3"],
// optCount4: doc["options"]["4"]["optionCount4"],
);
}
int getreactionsTotalCount(votesTotal) {
if (votesTotal == null) {
return 0;
}
int count = 0;
votesTotal.values.forEach((val) {
if (val = true) {
count += 1;
}
});
return count;
}
#override
_PollPostState createState() => _PollPostState(
pollPostId: this.pollPostId,
mediaUrl: this.mediaUrl,
ownerId: this.ownerId,
username: this.username,
caption: this.caption,
category: this.category,
colorTheme: this.colorTheme,
duration: this.duration,
question: this.question,
optText1: this.optText1,
optText2: this.optText2,
optText3: this.optText3,
optText4: this.optText4,
optemo1: this.optemo1,
optemo2: this.optemo2,
optemo3: this.optemo3,
optemo4: this.optemo4,
votesTotalCount: getreactionsTotalCount(this.votesTotal),
);
}
class _PollPostState extends State<PollPost> {
GlobalKey floatingKey = LabeledGlobalKey("Floating");
bool isFloatingOpen = false;
OverlayEntry floating;
static FirebaseAuth auth = FirebaseAuth.instance;
final userRef = FirebaseFirestore.instance.collection("users");
final pollPostsRef = FirebaseFirestore.instance.collection("pollPosts");
final String currentUserId = auth.currentUser?.uid;
final String pollPostId;
final String mediaUrl;
final String ownerId;
final String username;
final String caption;
final String category;
final String colorTheme;
final Timestamp duration;
final String question;
final String optText1;
final String optText2;
final String optText3;
final String optText4;
final String optemo1;
final String optemo2;
final String optemo3;
final String optemo4;
int votesTotalCount;
Map votesTotal;
bool isVoted;
_PollPostState({
this.pollPostId,
this.mediaUrl,
this.ownerId,
this.username,
this.caption,
this.category,
this.colorTheme,
this.duration,
this.question,
this.votesTotal,
this.votesTotalCount,
this.optText1,
this.optText2,
this.optText3,
this.optText4,
this.optemo1,
this.optemo2,
this.optemo3,
this.optemo4,
});
handleTotalVotePosts() {
bool _isVoted = votesTotal[currentUserId] == true;
if (_isVoted) {
pollPostsRef
.doc(ownerId)
.collection('usersPollPosts')
.doc(pollPostId)
.update({'votesTotal.$currentUserId': false});
setState(() {
votesTotalCount -= 1;
isVoted = false;
votesTotal[currentUserId] = false;
});
} else if (!_isVoted) {
pollPostsRef
.doc(ownerId)
.collection('usersPollPosts')
.doc(pollPostId)
.update({'votesTotal.$currentUserId': true});
setState(() {
votesTotalCount += 1;
isVoted = true;
votesTotal[currentUserId] = true;
});
}
}
OverlayEntry createFloating() {
RenderBox renderBox = floatingKey.currentContext.findRenderObject();
Offset offset = renderBox.localToGlobal(Offset.zero);
return OverlayEntry(builder: (context) {
return Positioned(
left: offset.dx,
top: offset.dy - 70,
child: Material(
color: Colors.transparent,
elevation: 20,
child: ClipRRect(
borderRadius: BorderRadius.circular(40.0),
child: Container(
padding: EdgeInsets.all(5.0),
height: 50,
color: MyColors.black,
child: Row(
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: AnimatedButton(
onPressed: () {
handleTotalVotePosts();
toastMessage("You voted for $optText1 $optemo1");
setState(() {
if (isFloatingOpen)
floating.remove();
else {
floating = createFloating();
Overlay.of(context).insert(floating);
}
isFloatingOpen = !isFloatingOpen;
});
},
duration: 20,
enabled: true,
width: 30,
height: 30,
color: MyColors.black,
shadowDegree: ShadowDegree.light,
child:
Text(optemo1, style: TextStyle(fontSize: 20.0)))),
Padding(
padding: const EdgeInsets.all(5.0),
child: AnimatedButton(
onPressed: () {
toastMessage("You voted for $optText2 $optemo2");
setState(() {
handleTotalVotePosts();
if (isFloatingOpen)
floating.remove();
else {
floating = createFloating();
Overlay.of(context).insert(floating);
}
isFloatingOpen = !isFloatingOpen;
});
},
duration: 20,
enabled: true,
width: 30,
height: 30,
color: MyColors.black,
shadowDegree: ShadowDegree.light,
child:
Text(optemo2, style: TextStyle(fontSize: 20.0)))),
optText3.isEmpty
? Text("")
: Padding(
padding: const EdgeInsets.all(5.0),
child: AnimatedButton(
onPressed: () {
handleTotalVotePosts();
toastMessage(
"You voted for $optText3 $optemo3");
setState(() {
if (isFloatingOpen)
floating.remove();
else {
floating = createFloating();
Overlay.of(context).insert(floating);
}
isFloatingOpen = !isFloatingOpen;
});
},
duration: 20,
enabled: true,
width: 30,
height: 30,
color: MyColors.black,
shadowDegree: ShadowDegree.light,
child: Text(optemo3,
style: TextStyle(fontSize: 20.0)))),
optText4.isEmpty
? Text("")
: Padding(
padding: const EdgeInsets.all(5.0),
child: AnimatedButton(
onPressed: () {
handleTotalVotePosts();
toastMessage(
"You voted for $optText4 $optemo4");
setState(() {
if (isFloatingOpen)
floating.remove();
else {
floating = createFloating();
Overlay.of(context).insert(floating);
}
isFloatingOpen = !isFloatingOpen;
});
},
duration: 20,
enabled: true,
width: 30,
height: 30,
color: MyColors.black,
shadowDegree: ShadowDegree.light,
child: Text(optemo4,
style: TextStyle(fontSize: 20.0)))),
],
),
),
),
),
);
});
}
buildPollPostHeader() {
return FutureBuilder(
future: userRef.doc(ownerId).get(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return circularProgress();
}
UserModel user = UserModel.fromMap(snapshot.data.data());
return ListTile(
leading: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(user.photoUrl),
backgroundColor: Colors.grey,
),
title: GestureDetector(
onTap: () => toastMessage("showing profile"),
child: Text(
user.username,
style: TextStyle(
color: MyColors.black,
fontWeight: FontWeight.bold,
),
),
),
subtitle: Text(category),
trailing: IconButton(
onPressed: () => toastMessage("pop up menu"),
icon: Icon(Icons.more_vert),
),
);
},
);
}
buildPollPostCenter() {
return Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
question,
style: TextType.boldHeading,
),
),
mediaUrl == null
? Center(
child: Container(
height: 30.0,
))
: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: chachedNetworkImage(
mediaUrl,
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Column(
children: [
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
optText1,
style: TextStyle(
color: MyColors.offBlack,
fontSize: 16.0,
),
),
Text(optemo1),
SizedBox(
width: 25.0,
),
Text(
optText2,
style: TextStyle(
color: MyColors.offBlack,
fontSize: 16.0,
),
),
Text(optemo2),
],
),
],
),
SizedBox(height: 13.0),
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
optText3,
style: TextStyle(
color: MyColors.offBlack,
fontSize: 16.0,
),
),
Text(optemo3),
SizedBox(
width: 25.0,
),
Text(
optText4,
style: TextStyle(
color: MyColors.offBlack,
fontSize: 16.0,
),
),
Text(optemo4),
],
),
],
),
],
),
),
],
);
}
buildPollPostFooter() {
return Column(
children: [
Align(
alignment: Alignment.bottomLeft,
child: Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(padding: EdgeInsets.only(top: 40.0, left: 20.0)),
isVoted
? GestureDetector(
key: floatingKey,
onTap: () {
setState(() {
if (isFloatingOpen)
floating.remove();
else {
floating = createFloating();
Overlay.of(context).insert(floating);
}
isFloatingOpen = !isFloatingOpen;
});
},
child: Icon(
Icons.poll,
color: MyColors.offBlack,
size: 28.0,
),
)
: Icon(Icons.check_circle),
Padding(padding: EdgeInsets.only(right: 20.0)),
GestureDetector(
onTap: () => toastMessage("show comments"),
child: Icon(
Icons.chat,
color: MyColors.offBlack,
size: 28.0,
),
),
Padding(padding: EdgeInsets.only(right: 20.0)),
GestureDetector(
onTap: () => toastMessage("saved successfully"),
child: Icon(
Icons.bookmark,
color: MyColors.offBlack,
size: 28.0,
),
),
Padding(padding: EdgeInsets.only(right: 20.0)),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
GestureDetector(
onTap: () => toastMessage("sharing with friends"),
child: Align(
alignment: Alignment.topRight,
child: Icon(
Icons.send,
color: MyColors.offBlack,
size: 28.0,
),
),
),
Padding(padding: EdgeInsets.only(right: 25.0)),
],
),
),
],
),
),
),
Row(
children: [
Container(
margin: EdgeInsets.only(left: 20.0, top: 5.0),
child: Text(
"$votesTotalCount votes",
style: TextStyle(
color: MyColors.black,
fontWeight: FontWeight.bold,
),
),
),
Container(
margin: EdgeInsets.only(left: 20.0, top: 5.0),
child: Text(
"expiring in 4 days",
style: TextStyle(
color: MyColors.black,
fontWeight: FontWeight.bold,
),
),
)
],
),
Padding(
padding: const EdgeInsets.only(top: 5.0, bottom: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.only(left: 20.0),
child: Text(
"$username ",
style: TextStyle(
color: MyColors.black,
fontWeight: FontWeight.bold,
),
),
),
Expanded(
child: Text(caption),
),
],
),
)
],
);
}
#override
Widget build(BuildContext context) {
isVoted = (votesTotal[currentUserId] == true);
return Card(
elevation: 1.0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
buildPollPostHeader(),
buildPollPostCenter(),
buildPollPostFooter(),
],
),
);
}
}
Please help me to get out of this bug.
Thank you :)
Please write the below code inside your handleTotalVotePosts method:
if(votesTotal == null){
return;
}
My every textfield is getting validated. But if the image is not selected and I press submit. It successfully gets uploaded to firestore without an image. But I want to make it stop incase image is null. When i load image it gets display in buildGridView. I guess i need to apply logic somewhere here. That if buildGridView is null. Stop or something. How can i achieve it. Thanks
Widget AddPost() {
return Form(
key: _key,
autovalidate: _validate,
child: Padding(
padding: const EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 0.0),
child: Column(
children: <Widget>[
_getPropertyTypeDropDown(),
_getPropertyTypeDetailDropDown(),
UploadPropertyImages(),
Container(
margin: EdgeInsets.only(left: 7),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
width: 200,
height: MediaQuery.of(context).size.height / 4,
//color: Colors.green,
child: buildGridView(),
),
RaisedButton(
child: Text("Submit"),
onPressed: () async {
if (_key.currentState.validate()) {
_key.currentState.save();
Alert(
context: context,
style: alertStyle,
type: AlertType.info,
title: "YEY !!",
desc: "Your Ad will be displayed soon.",
buttons: [
DialogButton(
child: Text(
"Thankyou",
style: TextStyle(color: Colors.white, fontSize: 20),
),
// onPressed: () => Navigator.pop(context),
color: Color.fromRGBO(0, 179, 134, 1.0),
radius: BorderRadius.circular(0.0),
),
],
).show();
await runMyFutureGetImagesReference();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RoleCheck()));
} else {
setState(() {
_validate = true;
});
}
},
textColor: Colors.black,
padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
splashColor: Colors.grey,
),
],
),
),
//_showSubmitButton(),
],
)),
);
}
Widget buildGridView() {
return GridView.count(
crossAxisCount: 3,
children: List.generate(images.length, (index) {
Asset asset = images[index];
print(asset.getByteData(quality: 100));
return Padding(
padding: EdgeInsets.all(8.0),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15)),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.blueAccent, width: 2)),
child: AssetThumb(
asset: asset,
width: 300,
height: 300,
),
),
),
// ),
);
}),
);
}
Widget UploadPropertyImages() {
return Container(
child: Center(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
NiceButton(
width: 250,
elevation: 8.0,
radius: 52.0,
text: "Select Images",
background: Colors.blueAccent,
onPressed: () async {
List<Asset> asst = await loadAssets();
if (asst.length == 0) {
showInSnackBar("No images selected");
}
// SizedBox(height: 10,);
else {
showInSnackBar('Images Successfully loaded');
}
}),
],
),
)));
}
Widget build(BuildContext context) {
return Scaffold(
// backgroundColor: Colors.grey[600],
key: _scaffoldKey,
body: Container(
padding: EdgeInsets.all(16.0),
child: Form(
child: ListView(
shrinkWrap: true,
children: <Widget>[
Center(
child: Text(
"Post New Ad",
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
),
),
AddPost(),
Padding(
padding: EdgeInsets.fromLTRB(0, 0, 0, 16),
),
], //:TODO: implement upload pictures
),
),
),
);
}
Future<List<Asset>> loadAssets() async {
List<Asset> resultList = List<Asset>();
String error = "No error Detected";
try {
resultList = await MultiImagePicker.pickImages(
maxImages: 10,
enableCamera: true,
selectedAssets: images,
cupertinoOptions: CupertinoOptions(takePhotoIcon: "chat"),
materialOptions: MaterialOptions(
actionBarColor: "#abcdef",
actionBarTitle: "Upload Image",
allViewTitle: "All Photos",
useDetailsView: false,
selectCircleStrokeColor: "#000000",
),
);
print(resultList.length.toString() + "it is result list");
/* print((await resultList[0].getThumbByteData(122, 100)));
print((await resultList[0].getByteData()));
print((await resultList[0].metadata));*/
print("loadAssets is called");
} on Exception catch (e) {
error = e.toString();
print(error.toString() + "on catch of load assest");
}
By default Image Picker field is not a form element. You can pick a plugin to do so for your application. Here I am adding one. Please include it and it will give you the scope to validate the picked image as your requirements.
https://pub.dev/packages/image_picker_form_field
You will have to include it in pubspec.yaml and you are ready to use the widget it provides. Just use that like below:
ImagePickerFormField(
child: Container(
height: 40,
child: Center(child: Text("Select Photo")),
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8)),
border: Border.all(
color: Theme.of(context).disabledColor, width: 1)),
),
previewEnabled: true,
autovalidate: true,
context: context,
onSaved: (File value) {
print("on saved called");
},
validator: (File value) {
if (value == null)
return "Please select a photo!";
else return null; },
initialValue: null, //File("some source")
)
I am using Firestore as a database for a Chat in one part of the application that I am building. I have a collection on my Firestore that looks like this:
messages(collection)
userId
userId-otherUserId(sub-collection)
randomId
content : String,
timestamp : Date
...
and I would like to retrieve firstly all of the userIs-otherUserId in a ListView and then to retrieve the lastMessage by retrieving the last randomId(and in that randomId the last message is the last element with key 'content') and I would like it to look something like this.
Prototype
where the Loading represents last message but the name and profilePicture I can get from my API.
The pictures of my Firestore database:
First Image
Second Image
Third Image
Is there any chance that I could save the name and Profile picture on the database in the userId-otherUserId(collection)?
The code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:dio/dio.dart';
import 'package:disby_app/const.dart';
import 'package:disby_app/data/request_model.dart';
import 'package:disby_app/global_widgets/loading_indicator.dart';
import 'package:disby_app/main_screens/main_chat_and_request_screens/chat.dart';
import 'package:disby_app/main_screens/main_chat_and_request_screens/profile_details_screen.dart';
import 'package:disby_app/public/keys.dart';
import 'package:disby_app/services/json_requests.dart';
import 'package:flutter/material.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../global_widgets/chat_and_requests_placeholder.dart';
class MainChatScreen extends StatefulWidget {
#override
_MainChatScreenState createState() => _MainChatScreenState();
}
class _MainChatScreenState extends State<MainChatScreen> {
bool _areThereChats = false;
bool _isLoading = true;
String groupChatId;
String userId = '1';
List<String> listOfLastMessages = [];
List<String> listOfIds = [];
List<ArrayOfRequestsModel> arrayOfRequests = [];
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
Future readLocal() async {
var prefs = await SharedPreferences.getInstance();
userId = prefs.getString(kUserId) ?? '';
print(userId);
setState(() {});
}
Future<void> _getRequests() async {
final prefs = await SharedPreferences.getInstance();
final userId = prefs.getString(kUserId);
try {
await Dio().post(baseUrl + acceptedUsers + "/" + userId).then((snapshot) {
print(snapshot);
final Map<String, dynamic> response = snapshot.data;
print(response);
if (response['response'] == 'success') {
List<dynamic> arrayFromServer = response['acceptedUsers'];
if (arrayFromServer.isNotEmpty) {
arrayFromServer.forEach((userData) {
arrayOfRequests.add(ArrayOfRequestsModel.fromJson(userData));
listOfIds.add(userData['userId']);
print(userData['userId']);
});
_areThereChats = true;
_isLoading = false;
getMessages(userId);
} else {
_areThereChats = false;
_isLoading = false;
}
setState(() {});
} else {
_areThereChats = false;
_isLoading = false;
setState(() {});
}
});
} catch (e) {
print(e);
}
}
getMessages(String userId) {
int i = 0;
print(listOfIds.length);
print(listOfIds);
listOfIds.forEach((element) {
Firestore.instance
.collection('messages')
.document('$userId')
.collection('$userId-$element')
.orderBy('timestamp', descending: true)
.limit(3)
.getDocuments()
.then((QuerySnapshot snapshot) {
print(snapshot.documents[0].data['content']);
snapshot.documents.forEach((f) {
print(f.data['content']);
listOfLastMessages.add(f.data['content']);
setState(() {});
i++;
});
if (i == listOfIds.length) {
setState(() {});
print(listOfLastMessages);
}
});
});
}
#override
void initState() {
readLocal();
_getRequests();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Container(
width: double.infinity,
height: double.infinity,
child: SingleChildScrollView(
// child: (userId != '1')
// ? StreamBuilder(
// stream: Firestore.instance
// .collection('messages')
// .document('5ef4d83175b07cf074525c08')
// .snapshots(),
// builder: (context, snapshot) {
// // if (snapshot.hasData) {
// if (!snapshot.hasData) {
// return Center(
// child: CircularProgressIndicator(
// valueColor: AlwaysStoppedAnimation<Color>(themeColor),
// ),
// );
// } else {
// return Container(
// width: 100,
// height: 200,
// child: ListView.builder(
// padding: EdgeInsets.all(10.0),
// itemBuilder: (context, index) =>
// buildItem(context, snapshot.data.documents),
// itemCount: snapshot.data.documents.length,
// ),
// );
// }
// },
// )
// : LoadingIndicator(
// loading: _isLoading,
// ),
// ),
child: Column(
children: <Widget>[
(_isLoading == false)
? (_areThereChats == true)
? Container(
child: AnimatedList(
key: _listKey,
shrinkWrap: true,
initialItemCount: arrayOfRequests.length,
itemBuilder:
(BuildContext context, index, animation) {
return _buildItem(context, arrayOfRequests[index],
animation, index);
},
),
)
: ChatAndRequestPlaceholder(
text: translate('when_someone_sends_you_a_request'))
: LoadingIndicator(
loading: _isLoading,
),
],
),
),
),
);
}
Widget buildItem(BuildContext context, DocumentSnapshot document) {
if (document['id'] == userId) {
return Container();
} else {
return Container(
child: FlatButton(
child: Row(
children: <Widget>[
Flexible(
child: Container(
child: Column(
children: <Widget>[
Container(
child: Text(
'Nickname: ${document['content']}',
style: TextStyle(color: primaryColor),
),
alignment: Alignment.centerLeft,
margin: EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 5.0),
),
Container(
child: Text(
'About me: ${document['content'] ?? 'Not available'}',
style: TextStyle(color: primaryColor),
),
alignment: Alignment.centerLeft,
margin: EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 0.0),
)
],
),
margin: EdgeInsets.only(left: 20.0),
),
),
],
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Chat(
peerId: document.documentID,
peerAvatar: document['photoUrl'],
),
),
);
},
color: greyColor2,
padding: EdgeInsets.fromLTRB(25.0, 10.0, 25.0, 10.0),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
),
margin: EdgeInsets.only(bottom: 10.0, left: 5.0, right: 5.0),
);
}
}
_buildItem(BuildContext context, ArrayOfRequestsModel arrayOfSentRequests,
Animation animation, int index) {
return GestureDetector(
onTap: () {
print(arrayOfSentRequests.profilePictureUrl);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Chat(
peerId: arrayOfSentRequests.userId,
peerAvatar: arrayOfSentRequests.profilePictureUrl,
),
),
);
},
child: Container(
height: 82,
child: ScaleTransition(
scale: animation,
child: Padding(
padding: EdgeInsets.fromLTRB(0, 10, 0, 0),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Color.fromRGBO(0, 0, 0, 0.07),
offset: Offset(0, 5),
blurRadius: 11,
),
],
),
width: double.infinity,
height: 78,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Container(
height: double.infinity,
child: GestureDetector(
onTap: () {
Navigator.of(context, rootNavigator: true).push(
MaterialPageRoute(
builder: (context) => ProfileDetailsScreen(),
),
);
},
child: Row(
children: <Widget>[
SizedBox(width: 24),
ClipRRect(
borderRadius: BorderRadius.circular(25),
child: Image(
image: NetworkImage(
arrayOfSentRequests.profilePictureUrl,
),
width: 48,
),
),
SizedBox(width: 10),
Expanded(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
arrayOfSentRequests.nameAndSurname,
style: TextStyle(
color: Colors.black,
fontFamily: 'Avenir',
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
Text(
'11/11/2020', //arrayOfSentRequests.nameAndSurname
style: TextStyle(
color: Colors.grey,
fontFamily: 'Avenir',
fontWeight: FontWeight.w500,
fontSize: 15,
),
),
],
),
),
Container(
child: Text(
// (arrayOfSentRequests.lastMessage !=
// null)
// ? arrayOfSentRequests.lastMessage
// : 'Loading..',
(listOfLastMessages.isNotEmpty)
? listOfLastMessages[index]
.toString()
: 'Loading...',
maxLines: 2,
softWrap: true,
style: TextStyle(
color: Colors.black54,
fontFamily: 'Avenir',
fontSize: 15,
),
),
),
],
),
),
),
SizedBox(width: 10),
],
),
),
),
),
Container(
child: Center(
child: Icon(
Icons.keyboard_arrow_right,
size: 22,
color: Colors.black54,
),
),
),
SizedBox(width: 10),
],
),
),
),
),
),
);
}
}
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();
}