I'm implementing this products app where a user can add a product from his wish list and add it to their cart. Once the user clicks the add to cart button, I want to delete the product from the screen and display a "success" Snackbar.
Since the products are loaded and displayed from FireBase Firestore, I delete the item and call setState({}); so that the list on the screen will be updated. The problem is that the SnackBar that comes right after setState({}); isn't shown.
I assume it is because the widget tree is rebuilt so the current state and context are "gone".
I tried finding some information online but haven't managed to find anything useful. I also tried to make a workaround with a bool flag that will be set once the user clicks the "Add to cart" button using and on setState the flag will be true one time to show the SnackBar when the widget tree is rebuilt and then turn the flag back to off but it didn't work as well.
What am I missing? How can I display a SnackBar after calling setState({})?
here is my code: (line in question marked with FIXME:)
slidable package installation
final GlobalKey _repaintBoundaryKey = GlobalKey();
final GlobalKey<ScaffoldState> _scaffoldKeyWishList = new GlobalKey<ScaffoldState>();
final Center _circularProgressIndicator = Center(
child: SizedBox(
width: 60,
height: 60,
child: CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation<Color>(Colors.lightGreen[800]),
)
),
);
#override
Widget build(BuildContext context) {
return Material(
child: FutureBuilder(
future: FirebaseFirestore.instance.collection("Wishlists").doc(FirebaseAuth.instance.currentUser.uid).get(),
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> wishListSnapshot) {
if (wishListSnapshot.connectionState != ConnectionState.done) {
return _circularProgressIndicator;
} else if (!wishListSnapshot.hasData ||
0 == wishListSnapshot.data.data()['Wishlist'].length) {
return globals.emptyListErrorScreen(context, 'Wishlist');
}
int totalProducts = wishListSnapshot.data.data()['Wishlist'].length;
return Scaffold(
key: _scaffoldKeyWishList,
backgroundColor: Colors.lightGreen[800],
body: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.0),
topRight: Radius.circular(20.0),
),
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.white,
child: ListView.builder(
itemCount: totalProducts * 2,
shrinkWrap: true,
padding: const EdgeInsets.all(16),
itemBuilder: (BuildContext _context, int i) {
if (i >= 2 * totalProducts) {
return null;
}
if (i.isOdd) {
return Divider(
color: Colors.green,
thickness: 1.0,
);
}
var wishlistIdData = wishListSnapshot.data.data()['Wishlist'];
String productID = wishlistIdData[i ~/ 2];
return FutureBuilder(
future: FirebaseFirestore.instance.collection("Products").doc(productID).get(),
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> productSnapshot) {
if (wishListSnapshot.connectionState != ConnectionState.done || !productSnapshot.hasData) {
return _circularProgressIndicator;
}
var productData = productSnapshot.data.data()['Product'];
String prodName = productData['name'];
String prodPrice = productData['price'];
String prodDate = productData['date'];
return Slidable(
actionPane: SlidableDrawerActionPane(),
actionExtentRatio: 0.22,
direction: Axis.horizontal,
actions: <Widget>[
//add to cart
IconSlideAction(
caption: 'Add to cart',
color: Colors.transparent,
foregroundColor: Colors
.amberAccent,
icon: Icons.add_shopping_cart,
onTap: () async {
globals.userCart.add(
globals.Product(
productID,
FirebaseAuth.instance.currentUser.uid,
prodName,
double.parse(prodPrice),
prodDate,
[],
"",
"")
);
///removing product from wishlist
List toRemove = [];
toRemove.add(productID);
await FirebaseFirestore.instance
.collection('Wishlists')
.doc(FirebaseAuth.instance.currentUser.uid)
.get()
.then((value) async {
List<dynamic> list = List
.from(value
.data()['Wishlist']);
list
..removeWhere((e) =>
toRemove.contains(e));
await FirebaseFirestore.instance
.collection('Wishlists')
.doc(FirebaseAuth.instance.currentUser.uid)
.update(
{'Wishlist': list});
});
setState(() {
///to update the list on screen
});
//FIXME: snackbar not displayed after setState!
///showing snackbar upon completion
_scaffoldKeyWishList
.currentState
.showSnackBar(
SnackBar(
content: Text(
'Product Successfully Added to Cart!',
style: GoogleFonts
.lato(
fontSize: 13.0,
color: Colors
.white
),
),
behavior: SnackBarBehavior
.floating,
action: SnackBarAction(
label: 'Checkout',
textColor: Colors
.lime,
onPressed: () =>
showDialog(
context: context,
builder: (
BuildContext context) {
return CustomDialogBox();
},
),
),
)
);
},
),
],
child: ListTile(
title: Text(prodName,
style: GoogleFonts.lato(
fontSize: 18.0,
color: Colors.black,
),
),
subtitle: Text(prodPrice + "\$",
style: GoogleFonts.lato(
fontSize: 13.5,
color: Colors.grey,
),
),
visualDensity: VisualDensity
.adaptivePlatformDensity,
),
);
},
);
},
),
)
)
]
)
)
);
}
)
);
}
In my case I was calling setState method before build method complete process of building widgets.
You can face this error if you are showing snack bar or alert dialog before the completion of build method and in many other cases. so in such situation use below call back function.
WidgetsBinding.instance.addPostFrameCallback((_) {
// add your snackbar code here
});
or You can also use SchedulerBinding which does the same.
SchedulerBinding.instance.addPostFrameCallback((_) {
// add your code here of snackbar.
});
or you can try this too (I am not sure about this one)
if(mounted){
//add your code here of snackbar
}
CREDITS
Related
I am working on an attendance application where I assign wages to the workers. I want to store all the wages given to the workers into the database. But the problem is I want to access all the given values on button click. I have no idea how it can be done in flutter. I am a beginner.
I have given all the codes and the image of what output i want.
Image of Emulator
Here is my code...
ATTENDANCE SCREEN
...rest code...
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Upload Patti'),
content: SingleChildScrollView(
child: ListBody(
children: [
TextFormField(
controller: _mainWagesController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "Enter Amount",
prefixIcon: Icon(Icons.wallet, color: Colors.blue),
),
),
],
),
),
actions: <Widget>[
ElevatedButton(
onPressed: () {
Navigator.pop(context);
newWages = _mainWagesController.text;
setState(() {});
},
child: const Text("Assign Wages"),
),
],
);
},
);
},
child: const Icon(Icons.check_circle),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.00),
child: Column(children: [
const SizedBox(
height: 20,
),
Center(
child: Text(
"Date : ${DateFormat.yMMMEd().format(DateTime.parse(widget.attendanceDate.toString()))}",
style: const TextStyle(fontSize: 20),
),
),
const SizedBox(
height: 20,
),
FutureBuilder(
future: SupervisorAttendanceServices.getAttendancesDetailsList(
widget.attendanceId),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
var data = snapshot.data['hamal'];
return ListView.builder(
itemCount: data.length,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return HamalAttendanceWidget(
workerId: data[index]['worker_id'],
name: data[index]['worker_name'],
wages: newWages,
masterAttendanceId: widget.attendanceId,
isPrensent: data[index]
['attendance_worker_presense']
.toString());
});
} else if (snapshot.hasError) {
return const Center(
child: Text("Something went wrong !"),
);
} else {
return const Center(child: LinearProgressIndicator());
}
},
),
]),
),
),
...rest code
widget
Widget build(BuildContext context) {
return Card(
child: Column(children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(
width: 10,
height: 50,
),
const Icon(FeatherIcons.user),
const SizedBox(
width: 20,
),
Text(
widget.name,
style: const TextStyle(fontSize: 18),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
width: 150,
height: 60,
child: TextFormField(
// onChanged: _onChangeHandler,
initialValue: widget.wages.toString(),
decoration: const InputDecoration(
hintText: "Wages",
prefixIcon: Icon(
Icons.wallet,
color: Colors.blue,
)),
)),
],
)
]),
);
}
I suggest you use a StateManager for your application, for example GetX
is a good solution. Create a controller file like the below:
// define this enum outside of class to handle the state of the page for load data
enum AppState { initial, loading, loaded, error, empty, disabled }
Rx<AppState> pageState = AppState.initial.obs;
class AttendanceCntroller extends GetxController{
RxList<dynamic> dataList=RxList<dynamic>();
#override
void onInit() {
//you can write other codes in here to handle data
pageState(AppState.loading);
dataList.value=
SupervisorAttendanceServices.getAttendancesDetailsList(attendanceId);
pageState(AppState.loaded);
super.onInit();
}
}
and in your view(UI) page, handle it in this way:
class AttendanceView extends GetView<AttendanceCntroller>{
#override
Widget body(BuildContext context) {
// TODO: implement body
return Obx( ()=> controller.pageState.value==AppState.loading ? const
Center(child: LinearProgressIndicator()) : ListView.builder(
itemCount: controller.dataList.length,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return HamalAttendanceWidget(
workerId: controller.dataList['worker_id'],
name: controller.dataList['worker_name'],
wages: newWages,
masterAttendanceId: widget.attendanceId,
isPrensent: controller.dataList[index]
['attendance_worker_presense']
.toString());
})
)
}
}
for more data read the GetX link and read clean architecture with the GetX sample repository of my GitHub it have advanced management of states with GetX with dependency injection handling.
If you want to have prefilled value in TextFormField, you can either use initialValue or controller parameter.
The value of controller parameter will help you to get/update the value of TextFormField.
For controller parameter refer below.
TextEditingController controller = TextEditingController(text: 'This is text will be pre-filled in TextFormField');
...
TextFormField(
controller: controller,
);
Create List or Map of those controllers.
List<TextEditingController> listOfControllers = [ controller1, controlle2,...];
Use for loop through this List on onClick() method of Button.
ElevatedButton(
onPressed: () {
for(var controllerItem in listOfControllers) {
print(controllerItem.text); // the value of TextFormField
}
},
)
My app is simple Authentication todo app where I store authenticate user UID in firestore document red mark represents login user UID and blue mark represents tasks of that particular user of below image.
Firestore image
Using QuerySnapshot I am trying to get particular user data but my code is not working dynamically.
I wanted to get particular user content array data and login user uid is same as document id here is my code.
StreamBuilder<QuerySnapshot>(
stream: _firestore.collection('Users').snapshots(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(
'Some thing went wrong! \n Restart your app!',
style: TextStyle(fontSize: 28.0, color: Colors.red),
));
} else if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(
color: Colors.cyan,
));
} else if (snapshot.hasData || snapshot.data != null) {
return ListView.builder(
shrinkWrap: true,
//------ This is static how to do it dynamically where only particular user content length will get?
itemCount: snapshot.data?.docs[1]['content'].length,
itemBuilder: (BuildContext context, int index) {
//------ This is static how to do it dynamically where I know that 1 index user is login that's why I gave index 1 how to do it dynamic?
DocumentSnapshot? documentSnapshot = snapshot.data?.docs[1];
return Padding(
padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 8.0),
child: Card(
color: Colors.black,
elevation: 10,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 20.0, horizontal: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: GestureDetector(
onTap: () async {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Edit Task'),
content: TextFormField(
controller: editTodoController,
decoration: InputDecoration(
hintText: 'Task info.',
errorText: _validate ? "Enter task" : null,
// label: Text("Task info"),
),
),
actions: [
TextButton(
onPressed: () async {
try {
final contentList = _firestore.collection('Users').doc(_user?.uid);
final docSnap = await contentList.get();
List content = docSnap.get('content');
ref.update({
'content': FieldValue.arrayRemove([content[index]]),
});
ref.update({
'content': FieldValue.arrayUnion([editTodoController.text]),
});
setState(() {
if (editTodoController.text.isEmpty) {
_validate = true;
} else {
_validate = false;
Navigator.of(context).pop();
}
editTodoController.clear();
// addTodoController.text.isEmpty ? _validate = true : _validate = false;
});
} catch (e) {
print(e);
}
},
child: Text('UPDATE'),
)
],
),
);
},
child: Text(
'${documentSnapshot!['content'][index]}',
// '${index} \t ${todoList[index]}',
style:
TextStyle(fontSize: 20.0, color: Colors.white),
),
),
),
IconButton(
onPressed: () async {
try {
final contentList = _firestore.collection('Users').doc(_user?.uid);
final docSnap = await contentList.get();
List content = docSnap.get('content');
await ref.update({
'content': FieldValue.arrayRemove([content[index]]),
});
} catch (e){
print(e);
}
},
icon: Icon(Icons.delete, color: Colors.white54, size: 29.0,)
),
],
),
),
),
);
},
);
}
return CircularProgressIndicator();
})
I am retrieving a list of items by querying on input from textfield in firebase and showing it as list in UI in flutter as shown here
Here is my code-
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_news/screens/drawer/news_drawer.dart';
import 'package:flutter_app_news/service/search_service/search_service.dart';
class Watchlist extends StatefulWidget {
#override
_WatchlistState createState() => new _WatchlistState();
}
class _WatchlistState extends State<Watchlist> {
var tempSearchStore = [];
var queryResult = [];
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
CollectionReference users = FirebaseFirestore.instance.collection('Users');
initiateSearch(value) {
if (value.length == 0) {
setState(() {
queryResult = [];
tempSearchStore = [];
});
}
if (queryResult.length == 0 && value.length == 1) {
SearchService().searchByName(value).then((QuerySnapshot snapshot) {
for (int i = 0; i < snapshot.docs.length; i++) {
queryResult.add(snapshot.docs[i].data());
setState(() {
tempSearchStore.add(queryResult[i]);
});
}
});
} else {
tempSearchStore = [];
queryResult.forEach((element) {
if (element['name'].toString().startsWith(value)) {
setState(() {
tempSearchStore.add(element);
});
}
});
}
;
if (tempSearchStore.length == 0 && value.length > 1) {
setState(() {});
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
iconTheme: IconThemeData(color: Colors.black),
backgroundColor: Colors.white,
title: Text(
"Finbox",
style: TextStyle(color: Colors.black),
),
),
drawer: Drawer(child: NewsDrawer()),
body: ListView(children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Your WatchList",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
),
Padding(
padding: const EdgeInsets.all(10.0),
child: TextField(
onChanged: (val) {
initiateSearch(val);
},
decoration: InputDecoration(
prefixIcon: IconButton(
color: Colors.black,
icon: Icon(Icons.arrow_back),
iconSize: 20.0,
onPressed: () {
Navigator.of(context, rootNavigator: true).pop(context);
},
),
contentPadding: EdgeInsets.only(left: 25.0),
hintText: 'Search by name',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0))),
),
),
SizedBox(height: 10.0),
ListView.builder(
shrinkWrap: true,
itemCount: tempSearchStore.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(tempSearchStore[index]['name']),
onTap: () {
users.doc(_firebaseAuth.currentUser.uid).update({
"subscribeTopic": FieldValue.arrayUnion(
[tempSearchStore[index]['name']])
});
Navigator.pop(context);
},
);
}),
SizedBox(
height: 10,
),
StreamBuilder(
stream: FirebaseFirestore.instance
.collection("Users")
.doc(_firebaseAuth.currentUser.uid)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(Colors.black)));
default:
return SingleChildScrollView(
child: GridView.builder(
shrinkWrap: true,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: MediaQuery.of(context)
.size
.width /
(MediaQuery.of(context).size.height / 7),
crossAxisCount: 2),
itemCount: snapshot.data.get('subscribeTopic').length,
itemBuilder: (context, index) {
return GestureDetector(
onLongPress: () {
showAlertDialog(context,
snapshot.data.get('subscribeTopic')[index]);
print(
snapshot.data.get('subscribeTopic')[index]);
},
child: Container(
child: Text(
snapshot.data.get('subscribeTopic')[index],
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500),
),
margin: EdgeInsets.all(15),
decoration: BoxDecoration(
color: Color(0xFF92f7bb),
),
),
);
}),
);
}
})
]));
}
showAlertDialog(BuildContext context, value) {
// set up the buttons
Widget yesButton = ElevatedButton(
child: Text("Yes"),
onPressed: () async {
// await FirebaseMessaging.instance.unsubscribeFromTopic(value);
users.doc(_firebaseAuth.currentUser.uid).update({
"subscribeTopic": FieldValue.arrayRemove([value])
});
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => Watchlist()));
;
},
);
Widget noButton = ElevatedButton(
child: Text("No"),
onPressed: () {
Navigator.of(context).pop();
},
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Text("Finbox"),
content: Text("Do you like to unsubscribe for " + value + " ?"),
actions: [
yesButton,
noButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
}
Here initiateSearch() is function which returns queries in firebase based on input given in textfield and tempSerachStore is list of key value pair. And I am fetching values whose key is "name".
Now the problem I am facing is-
When I write something in textfield and it queries and return a list, list is creating above those green boxes and boxes gets shifted downward as shown in image.
When I tap on one of these items in list or tap on back arrow in textfield , I am poping out to black screen.
What is the output I want-
I want to show this list above these green boxes(whose code is not included here).
When I tap on the item or tap on back arrow , list view should closed.
I will be grateful for help.
Use conditional statement along with Stack. It will work.
Stack(
children: [
// whatever you want to show. Positioned will come on top of it.
data.length == 0 // your search result length
? SizedBox(height: 0)
: Positioned(
top: 0,
width: double.infinity,
child: Container(
// whatever you want to show here
),
]
),
For a good UI experience, it is a good practice to create a separate page for search. On selecting an option, you can bring back the user on your previous page.
I have a map<string, dynamic> ,and want to show it's components in cards by ListView.Builder, but the problem is by getting access to this map's indexes..
When running the app, it return "null" in the card!!
I have tried many solutions that I saw in StackOverFlow for a similar issues, but without a result.
Here where I identified my map:
var _item;
List listCount = [];
Map<String, dynamic> records = {};
String name;
And here where I give the var _item it's value:
MyCard(
colour: Colors.lightBlueAccent,
maker: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
StreamBuilder<int>(
stream: _stopWatchTimer2.rawTime,
initialData: 0,
builder: (context, snap) {
final value = snap.data;
final displayTime = StopWatchTimer.getDisplayTime(
value,
hours: _isHours2);
_item = displayTime;
return Padding(
padding: EdgeInsets.all(5.0),
child: Text(displayTime,
style: TextStyle(
fontSize: 30.0, color: Colors.white)),
);
},
),
],
),
),
And here in the "Save" button I give the 1st parameter of the map and assign in name variable:
createAlertDialog(buildContext, context) {
TextEditingController controller;
return showDialog(
context: context,
// barrierDismissible: false,
builder: (context) {
return AlertDialog(
title: Text(
'Type record name',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18.0),
),
content: TextField(
controller: controller,
onChanged: (value) {
name = value;
}),
actions: [
MaterialButton(
elevation: 5.0,
child: Text('Save'),
onPressed: () {
listCount.add(_item);
print(_item);
records[name] = _item;
print(records);
Navigator.pop(context);
},
),
MaterialButton(
elevation: 5.0,
child: Text('Cancel'),
onPressed: () {
Navigator.pop(context);
},
),
],
);
},
);
}
Finally here where I tried to show it in a card by listview.builder:
Container(
color: Colors.white,
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: records.length,
itemBuilder: (context, index) {
return MyCard(
colour: Colors.cyanAccent,
maker: Container(
width: 250.0,
height: 75.0,
child: Text(
'${records[index]}',
style: TextStyle(fontSize: 25.0),
textAlign: TextAlign.center,
),
),
);
},
),
),
I think the problem with the 2nd parameter of itemBuilder which it (index) because I replaced this keyword with another one randomly, and get the same result when running my app "Null".
This screenShot explain the problem:
You can get map entries as List as such:
final recordsEntries = records.entries.toList()
Then, you have a List of MapEntry and are able to access key and value associated for each item of the collection.
recordsEntries[index].key
recordsEntries[index].value
I'm stuck with making a scrollable list like Google Task app when you reach end of the list if any task is completed it shown in another list with custom header as you can see here, I'm using sliver
Widget showTaskList() {
final todos = Hive.box('todos');
return ValueListenableBuilder(
valueListenable: Hive.box('todos').listenable(),
builder: (context, todoData, _) {
int dataLen = todos.length;
return CustomScrollView(
slivers: <Widget>[
SliverAppBar(
floating: true,
expandedHeight: 100,
flexibleSpace: Container(
padding: EdgeInsets.only(
left: MediaQuery.of(context).size.width / 10,
top: MediaQuery.of(context).size.height / 17),
height: 100,
color: Colors.white,
child: Text(
'My Task',
style: TextStyle(fontSize: 30.0, fontWeight: FontWeight.w600),
),
),
),
SliverList(
delegate:
SliverChildBuilderDelegate((BuildContext context, int index) {
final todoData = todos.getAt(index);
Map todoJson = jsonDecode(todoData);
final data = Todo.fromJson(todoJson);
return MaterialButton(
padding: EdgeInsets.zero,
onPressed: () {},
child: Container(
color: Colors.white,
child: ListTile(
leading: IconButton(
icon: data.done
? Icon(
Icons.done,
color: Colors.red,
)
: Icon(
Icons.done,
),
onPressed: () {
final todoData = Todo(
details: data.details,
title: data.title,
done: data.done ? false : true);
updataTodo(todoData, index);
}),
title: Text(
data.title,
style: TextStyle(
decoration: data.done
? TextDecoration.lineThrough
: TextDecoration.none),
),
subtitle: Text(data.details),
trailing: IconButton(
icon: Icon(Icons.delete_forever),
onPressed: () {
todos.deleteAt(index);
}),
),
),
);
}, childCount: dataLen),
),
],
);
});
}
ShowTaskList is called on
Scaffold(
body: SafeArea(
child: Column(children: <Widget>[
Expanded(
child: showTaskList()
),
]),
),
I tried OffStageSliver to make an widget disappear if no complete todo is present but that did not work and also can not use any other widget on CustomScrollView because that conflict with viewport because it only accept slivers widget.
Here what i have achieved so far
You can try use ScrollController put it on CustomScrollView and listen to it's controller in initState like this :
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
// If it reach end do something here...
}
});
}
I suggest you make bool variable to show your widget, initialize it with false and then after it reach end of controller call setState and make your variable true, which you can't call setState in initState so you have to make another function to make it work like this:
reachEnd() {
setState(() {
end = true;
});
}
Put that function in initState. And make condition based on your bool variabel in your widget
if(end) _yourWidget()
Just like that. I hope you can understand and hopefully this is working the way you want.