Hi I wanted to remove the Data from my List during onTap but I am unable to do so.
This is the code:
Widget buildUser(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index){
DocumentSnapshot user = snapshot.data.documents[index];
final style = _selectedBusStop.contains(ListClass(user.data['BusstopName'], user.data['location'].latitude.toString(), user.data['location'].longitude.toString()))
? TextStyle(
fontSize: 18,
color: Colors.blueAccent,
fontWeight: FontWeight.bold,
): TextStyle(fontSize: 18);
return ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(
user.data['image']
),
),
title: Text(user.data['BusstopName'], style: style),
subtitle: Text('Operating Hour: ' + user.data['OperatingHour'], style: style),
trailing:
_selectedBusStop.contains((ListClass(user.data['BusstopName'], user.data['location'].latitude.toString(), user.data['location'].longitude.toString()))) ? Icon(Icons.check, color: Colors.blueAccent, size: 26) : null,
onTap: (){
if(_selectedBusStop.contains(ListClass(user.data['BusstopName'], user.data['location'].latitude.toString(), user.data['location'].longitude.toString()))){
setState(() {
_selectedBusStop.removeWhere((val) => val == ListClass(user.data['BusstopName'], user.data['location'].latitude.toString(), user.data['location'].longitude.toString()));
print (_selectedBusStop);
});
}
},
onLongPress: (){
setState(() {
_selectedBusStop.add(ListClass(user.data['BusstopName'], user.data['location'].latitude.toString(), user.data['location'].longitude.toString()));
print(_selectedBusStop);
});
}
);
},
);
}
This is the Class:
class ListClass{
String Bname;
String Blat;
String Blng;
ListClass(this.Bname, this.Blat, this.Blng);
#override
String toString(){
return '{${this.Bname}, ${this.Blat}, ${this.Blng}}';
}
}
Any idea where went wrong? Thank you in advance.
Update
List _selectedBusStop = [];
_selectedBusStop is empty List and upon LongPress it will add data into the List and upon onPress it will remove the data if the data already exist in the List.
#randomstudent the issue is the instance, when you comaparing two values with different instance but even value is same, it returns false,
for example to understand simply.
void main() {
final a = IntTestWrapper(3);
final b = IntTestWrapper(3);
print(a==b);
}
class IntTestWrapper {
IntTestWrapper(this.a);
final int a;
}
Output: false
If you want to compare, compare using equatable
if you change like this
class IntTestWrapper extends Equatable {
IntTestWrapper(this.a);
final int a;
#override
List<Object> get props => [a];
}
then for this
void main() {
final a = IntTestWrapper(3);
final b = IntTestWrapper(3);
print(a==b);
}
Output will be true.
To print you can override toString
void main() {
final a = IntTestWrapper(3);
final b = IntTestWrapper(3);
print(a);
}
class IntTestWrapper extends Equatable {
IntTestWrapper(this.a);
final int a;
#override
List<Object> get props => [a];
#override
String toString() => 'The value of a is :$a';
}
Output: The value of a is :3
Related
I am looking at my code and wondering for 2 hours now without luck so I will ask for help here.
I have a button, when I press it, it displays a random item from the list view. The problem is I also have a check box on the list view with each item. I do not want it to (Shuffle through the items with the checkbox ticked) only to shuffle through the Task in the list view that are unchecked/unticked/are not done.
Here is my code
class TaskData extends ChangeNotifier {
List<Task> _tasks = [
Task(name: "item1"),
Task(name: "item2"),
Task(name: "item3"),
];
UnmodifiableListView<Task> get tasks {
return UnmodifiableListView(_tasks);
}
int get taskCount {
return _tasks.length;
}
// <<Here is the code that shuffles through list
Future<String> rann() async {
return (_tasks.toList()..shuffle()).first.name;
}
void addTask(String newTaskTitle) {
final task = Task(name: newTaskTitle);
_tasks.add(task);
notifyListeners();
}
void updateTask(Task task) {
task.toggleDone();
notifyListeners();
}
In another script I have this one
class Task {
final String name;
bool isDone;
Task({required this.name, this.isDone = false});
void toggleDone() {
isDone = !isDone;
}
}
In another script file I have this code
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 0),
child: FutureBuilder(
future: Provider.of<TaskData>(context).rann(),
builder: (context, snapshot) {
return Align(
alignment: Alignment.center,
child: Text(
"${snapshot.data}",
//softWrap: true,
textAlign: TextAlign.center,
//textWidthBasis: TextWidthBasis.longestLine,
style: TextStyle(
color: Colors.white,
fontSize: 30,
fontWeight: FontWeight.w700),
),
);
},
),
),
In another script I have this one
class TasksList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<TaskData>(
builder: (context, taskData, child) {
return ListView.builder(
itemBuilder: (context, index) {
final task = taskData.tasks[index];
return TaskTile(
taskTitle: task.name,
isChecked: task.isDone,
checkboxCallback: (checkboxState) {
taskData.updateTask(task);
},
);
},
itemCount: taskData.taskCount,
);
},
);
}
}
Any help would be appreciated!
Edit : I also forgot to include this part of code
class TaskTile extends StatelessWidget {
final bool isChecked;
final String taskTitle;
final Function(bool?) checkboxCallback;
final VoidCallback longPressCallback;
TaskTile(
{required this.isChecked,
required this.taskTitle,
required this.checkboxCallback,
required this.longPressCallback});
#override
Widget build(BuildContext context) {
return ListTile(
onLongPress: longPressCallback,
title: Text(
taskTitle,
// at the bottom, it sets decoration of text if isChecked is true, if its not its null
style: TextStyle(
decoration: isChecked ? TextDecoration.lineThrough : null),
),
trailing: Checkbox(
activeColor: Colors.blue,
value: isChecked,
onChanged: checkboxCallback,
),
);
}
}
updated:
class TaskData extends ChangeNotifier {
List<Task> _undoneTasksShuffled = []
// you don't need anymore the rann method() instead you should iterate over this listView
UnmodifiableListView<Task> get undoneTasksShuffled => UnmodifiableListView<Task>(_undoneTasksShuffled);
#override
void notifyListeners() {
//this updates _undoneTasksShuffled every time you call notifyListeners
_undoneTasksShuffled = _tasks.where((e)=> !e.isDone).toList()..shuffle();
super.notifyListeners();
}
...
}
I think you only need to filter the results before get a random element. you need to modify your rann method for something like
//you don't really need a future method because you don't have async code
String rann() {
final r = Random();
final undoneTasks = _tasks.where((e)=> !e.isDone).toList();
//this is for avoid RangeException on list. you can return any other thing
if(undoneTasks.isEmpty) return '';
// i think that you don't really need to shuffle whole list, you only need a random element
return undoneTasks[r.nextInt(undoneTasks.length)].name;
}
i hope this solves your question
I am new to flutter and this is my first app. I am trying to make a to do list app and want to display the time left for each task in the subtitle. I have a listview and in each element I want the have the subtitle display the minute, counting downwards towards 0. Can anyone help me with this ? Thanks!
Code : -
class toDoListState extends State<toDoList>
{
List<String> tasks = [];
List<String> completedTasks = [];
List<String> descriptions = [];
List<bool> importance = [];
List<String> time2completion = [];
List<DateTime> time = [];
Widget buildToDoList()
{
return new ListView.builder
(
itemBuilder: (context, index)
{
if(time2completion.length > 0 && index < time2completion.length && time2completion[index] != "none")
{
if(time2completion[index] == "30 minutes")
{
time[index] = DateTime.now().add(Duration(minutes: 30));
}
else if(time2completion[index] == "1 hour")
{
time[index] = DateTime.now().add(Duration(hours: 1));
}
else if(time2completion[index] == "12 hours")
{
time[index] = DateTime.now().add(Duration(hours: 12));
}
else if(time2completion[index] == "1 day")
{
time[index] = DateTime.now().add(Duration(days: 1));
}
}
if(index < tasks.length)
{
return row(tasks[index], descriptions[index], index);
}
},
);
}
Widget row(String task, String description, int index)
{
return Dismissible(
key: UniqueKey(),
background: Container(color: Colors.red, child: Align(alignment: Alignment.center, child: Text('DELETE', textAlign: TextAlign.center, style: TextStyle(color: Colors.white, fontSize: 18),))),
direction: DismissDirection.horizontal,
onDismissed: (direction) {
setState(() {
tasks.removeAt(index);
if(completedTasks.contains(task))
{
completedTasks.removeAt(index);
}
descriptions.removeAt(index);
importance.removeAt(index);
});
Scaffold.of(context).showSnackBar(SnackBar(content: Text(task+" dismissed")));
},
child: CheckboxListTile(
controlAffinity: ListTileControlAffinity.leading,
title: Text(task, style: (completedTasks.contains(task)) ? TextStyle(decoration: TextDecoration.lineThrough) : TextStyle(),),
subtitle: Text((time[index].difference(DateTime.now()).toString())),
value: completedTasks.contains(task),
onChanged: (bool value) {
setState(() {
if(!completedTasks.contains(task))
{
completedTasks.add(task);
}
else
{
completedTasks.remove(task);
}
});
},
));
}
}
You can use a timer to calculate the time differences every minute.
Timer.periodic(
Duration(minutes: 1),
(Timer t) => setState(() {
// your calculation here
}),
);
The following will create a timer object in your stateful widget and dispose of it when you navigate away from the view:
Timer _timer;
#override
void initState() {
_timer = Timer.periodic(
Duration(minutes: 1),
(Timer t) => setState(() {
// your calculation here
}),
);
super.initState();
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
You can achieve this by following:
Suppose you already have saved date in your todo and have already a date available in
form of DateTime object here in my example I am assuming savedDateTime which can be achieved either by assigning it using .hour , .sec or parsing it from string.
Now what you do is to find what time is left that is differnce
//already assumed saved date as a DateTime object that is savedDateTime
// it contains our saved date of note
//now
final currentDateTime = DateTime.now();
final difference = currentDateTime.difference(savedDateTime);
difference in Seconds,Hours,Minutes,Days is given by
print(difference.inSeconds);
print(difference.inHours);
print(difference.inMinutes);
print(difference.inDays);
What I've already done
I have a stateful widget that generates a ListView on screen.
class AppListView extends StatefulWidget {
final ValueChanged onChange;
final List<MatchList> matchList;
final ValueChanged finalBetList;
AppListView({this.onChange, this.matchList, this.finalBetList});
#override
_AppListViewState createState() => _AppListViewState();
}
class _AppListViewState extends State<AppListView> {
int counter = 0;
List<MatchList> betList = List<MatchList>();
I have a Home Screen that calls this Stateful Widget. In here I am using a callback function (onChange) to get the counter value from the widget. Everything is working perfect.
Stack(children: [
AppListView(
matchList: matchList,
//callback function brings the counter value from ListView class
onChange: (value) {
setState(() {
counter = value;
});
},
),
Positioned(
child: Padding(
padding: const EdgeInsets.only(top: 280.0, left: 330.0),
child: Container(
width: 60,
height: 60,
child: FloatingActionButton(
onPressed: () {
counter > 0
? Navigator.pushNamed(context, BetScreen.id)
: print('you shall not pass');
},
child: Text(
counter.toString(),
style: kTextStyleAppBarTitle,
),
),
),
),
)
])
What is the problem
But when I am trying to call that widget from another screen with a similar callback function(finalBetList), I got "The method 'call' was called on null. Receiver: null" error. Actually everything that I do is the same as the other example that works fine. I can't find what I'm missing. Is it something about Lists?
class _BetScreenState extends State<BetScreen> {
List<MatchList> betList = List<MatchList>();
int counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(
currentBalance: '\u0024' + '200.00',
userLevel: 'Level 30',
userName: 'Mark',
),
body: Container(
child: AppListView(
finalBetList: (value) {
setState(() {
//counter = value;
betList = value;
//print(betList);
});
},
matchList: betList,
),
),
);
}
}
After Edit
I added the cod of AppListView widget
class AppListView extends StatefulWidget {
final ValueChanged onChange;
final List<MatchList> matchList;
final ValueChanged finalBetList;
AppListView({this.onChange, this.matchList, this.finalBetList});
#override
_AppListViewState createState() => _AppListViewState();
}
class _AppListViewState extends State<AppListView> {
int counter = 0;
List<int> betList = List<int>();
#override
Widget build(BuildContext context) {
children: <Widget>[
AppButton.buildAppButton(
context,
AppButtonType.BETSELECTION,
widget.matchList[index].homeOdds,
kCategoryButtonDimensions,
color: widget.matchList[index].homeSelected
? Colors.yellow
: Colors.white,
onPressed: () {
if (widget.matchList[index].drawSelected ||
widget.matchList[index].awaySelected) {
widget.matchList[index].drawSelected =
false;
widget.matchList[index].awaySelected =
false;
counter--;
//betList part
if (betList.length > 0)
betList
.remove(widget.matchList[index].id);
}
widget.matchList[index].homeSelected =
!widget.matchList[index].homeSelected;
if (widget.matchList[index].homeSelected) {
counter++;
betList.add(widget.matchList[index].id);
} else {
counter--;
if (betList.length > 0)
betList.remove(widget.matchList[index]
.id); //if selected, add to betList to send BetScreen
}
widget.onChange(counter);
print(betList);
widget.finalBetList(betList);
setState(() {});
},
),
I am trying to list categories of products in my app, and i am using provider package for state management. During the build time its shows the list is empty even though it is not, then I add a click event to update the list that time it works.
I call the getAllCategories() function during the splash screen and the vallCategoryList has values.
This is my category class
class Category with ChangeNotifier{
int catId;
String catName;
String catThumbnail;
List<SetCategory> allCategoryList = [];
void getAllCategories() async {
String categoryUrl = 'https://app.ecwid.com/api/ccccccc';
Response allCategory = await get(categoryUrl);
print('getAllCategories');
if (allCategory.statusCode == 200) {
var categoryData = allCategory.body;
int totalcount = jsonDecode(categoryData)['count'];
if (allCategoryList.length != totalcount) {
allCategoryList.clear();
for (int i = 0; i < totalcount; i++) {
allCategoryList.add(SetCategory(
catId: jsonDecode(categoryData)['items'][i]['id'],
catName: jsonDecode(categoryData)['items'][i]['name'],
catThumbnail: jsonDecode(categoryData)['items'][i]['thumbnailUrl'],
));
}
}
}
print('allcategorylist length ${allCategoryList.length}');
notifyListeners();
}
}
class SetCategory {
int catId;
String catName;
String catThumbnail;
SetCategory(
{ this.catId, this.catName, this.catThumbnail});
}
My code for screen
class HomeScreen extends StatefulWidget {
static const String id = 'homeScreen';
// static int reload = 0;
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
final category = Provider.of<Category>(context);
print('category length ${category.allCategoryList.length}'); // here it shows length as 0 even though it has a value of 16.
return Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Category ${category.allCategoryList.length}',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 15.0,
),
textAlign: TextAlign.start,
),
),
InkWell(
onTap: () {
category.getAllCategories(); // when i tap here this updates the list
}),
],
),
Did you use ChangeNotifierProvider in your Widget as shown here
If you just used Provider it is not updating but just makes the object accessible from the descendants
Solved by adding a Consumer, changed code like this
child: Consumer<Category>(
builder: (_,category,__){
return ListView.builder();
}
i have a class that return a widget that create a text field i want call this class in a for loop in other class and every times that call this class text field set different value for my text fields and show text fields in list view how done it and next question about text field is when i set a value to text field and go to next page and back to my page value of text field cleaned and dont show how access to value of it this is my class that create a text field object
import 'package:art_man/components/Utility/Keys.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class InputText extends StatefulWidget {
String _hint, id;
double height;
TextEditingController ctrl;
Color brdercolor;
double brderwidth;
double radius;
double margintop;
TextAlign textAlign;
int maxlines;
Color hintconlor;
double hintsize;
double maxlenght;
TextInputType keyboardtype;
FontWeight fontWeight;
TextEditingController controller;
InputText(this._hint, this.id,
{this.height,
this.brdercolor,
this.brderwidth,
this.margintop,
this.radius,
this.textAlign,
this.maxlines,
this.hintconlor,
this.hintsize,
this.maxlenght,
this.keyboardtype,
this.fontWeight,
this.controller});
#override
myInputText createState() {
myInputText it = new myInputText(id,_hint,
height: height,
brdercolor: brdercolor,
brderwidth: brderwidth,
margintop: margintop,
radius: radius,
maxlines: maxlines,
hintconlor: hintconlor,
alignment: textAlign,
hintsize: hintsize,
maxlenght: maxlenght,
keyboardtype: keyboardtype,
fontwidth : fontWeight,
controller: controller);
return it;
}
}
class myInputText extends State<InputText> {
String _hint;
String id;
double height;
Color brdercolor;
double brderwidth;
double radius;
double margintop;
TextAlign alignment;
int maxlines;
Color hintconlor;
double hintsize;
double maxlenght;
TextInputType keyboardtype;
FontWeight fontwidth;
TextEditingController controller ;
myInputText(this.id,this._hint,
{this.height,
this.brdercolor,
this.brderwidth,
this.margintop,
this.radius,
this.alignment,
this.maxlines,
this.hintsize,
this.hintconlor,
this.maxlenght,
this.keyboardtype,
this.fontwidth,
this.controller});
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
margin: EdgeInsets.only(
top: margintop == null ? 1.0 : margintop,
),
padding: EdgeInsets.only(right: 3),
height: height == null ? 40.0 : height,
decoration: BoxDecoration(
border: Border.all(
color: brdercolor == null ? Colors.white : brdercolor,
width: brderwidth == null ? 0.0 : brderwidth),
color: brdercolor == null ? Colors.white : brdercolor,
borderRadius: BorderRadius.circular(radius == null ? 25 : radius)),
child: TextField(
controller: controller,
keyboardType: keyboardtype==null?TextInputType.text:keyboardtype,
textInputAction: TextInputAction.next,
inputFormatters: [
new LengthLimitingTextInputFormatter(maxlenght==null?
30:maxlenght.toInt()),
],
onChanged: (value){
Kelid.setter(id, value);
print(Kelid.getter(id));
},
textAlign: alignment == null ? TextAlign.right : alignment,
maxLines: maxlines==null?1:maxlines,
style: TextStyle(
fontSize: hintsize==null?14:hintsize,
),
textDirection: TextDirection.rtl,
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: const EdgeInsets.all(0.0),
hintText: _hint,
errorStyle: TextStyle(height: 0),
hintStyle: TextStyle(
fontWeight: fontwidth==null?FontWeight.normal:fontwidth,
color: hintconlor == null ? Colors.grey : hintconlor,
fontSize: hintsize == null ? 13 : hintsize)),
),
);
}
}
From what I understand, you're looking into generating TextField dynamically and be able to store its values. What you can do here is store TextField values using state management available in Flutter. In this approach, I've used the provider package to store the TextField values and are saved on every text change. With the TextField values stored, we can easily restore the values on the TextFields.
Complete sample
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (BuildContext context) => TextFieldDetailsModel(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ListView.builder(
itemCount: 20,
itemBuilder: (BuildContext context, int index) {
return _inputText('Hint for index $index', index);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Print all TextField String stored in Provider
// List<String> listText =
// Provider.of<TextFieldDetailsModel>(context, listen: false)
// ._textFieldContent;
// for (int x = 0; x < listText.length; x++) {
// debugPrint('Text: ${listText[x]}, index: $x');
// }
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
},
child: Icon(Icons.play_arrow),
),
);
}
Widget _inputText(String hint, int index) {
var _textEditingController = TextEditingController();
return Container(
padding: EdgeInsets.all(16.0),
child: Consumer<TextFieldDetailsModel>(builder: (BuildContext context,
TextFieldDetailsModel txtModel, Widget? child) {
// If Provider List contains value, prefill the TextField
if (txtModel._textFieldContent.length > 0 &&
txtModel._textFieldContent.length > index &&
txtModel._textFieldContent[index].trim() != '') {
_textEditingController.text = txtModel._textFieldContent[index];
// set TextField cursor at the end
_textEditingController.selection = TextSelection.collapsed(offset: _textEditingController.text.length);
}
return TextField(
controller: _textEditingController,
onChanged: (String str) {
// Only add String if TextField has value
if (str.trim().isEmpty)
txtModel.remove(index);
else
txtModel.add(str, index);
},
decoration: InputDecoration(hintText: hint),
);
}),
);
}
}
// https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple
class TextFieldDetailsModel extends ChangeNotifier {
final List<String> _textFieldContent = [];
UnmodifiableListView<String> get text =>
UnmodifiableListView(_textFieldContent);
void add(String text, int? index) {
if (index != null)
_textFieldContent.fillAndSet(index, text);
else
_textFieldContent.add(text);
// This call tells the widgets that are listening to this model to rebuild.
notifyListeners();
}
void remove(int index) {
_textFieldContent.removeAt(index);
notifyListeners();
}
void removeAll() {
_textFieldContent.clear();
// This call tells the widgets that are listening to this model to rebuild.
notifyListeners();
}
}
// Create filler for skipping ranges
// Source: https://stackoverflow.com/a/65504227/2497859
extension ListFiller<T> on List<String> {
void fillAndSet(int index, String value) {
if (index >= this.length) {
this.addAll(List<String>.filled(index - this.length + 1, ''));
}
this[index] = value;
}
}
class SecondRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
Demo
The values can also be stored only on page change. Create a List<TextEditingController> to be assigned on the TextFields generated.
late List<TextEditingController> _listTextEditingController = [];
...
Widget _inputText(String hint, int index) {
var _textEditingController = TextEditingController();
_listTextEditingController.add(_textEditingController);
...
}
On page change (i.e. button trigger), extract all the List values and store it in the Provider.
for (int x = 0; x < _listTextEditingController.length; x++) {
Provider.of<TextFieldDetailsModel>(context, listen: false).add(_listTextEditingController[x].text, x);
}