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
Related
I'm trying to do a search history using a search delegate but I'm having a problem.
When I perform a search, that element can appear several times in the history and what I want is that it not be repeated.
If I search 3 times for the same person, in the search history it appears 3 times
And I only want it to appear once.
How could I do it?
help would be appreciated.
Code and image::
class MPState extends State<MP>{
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: new Scaffold(
resizeToAvoidBottomInset : false,
appBar: AppBar(
title: Text("App"),
backgroundColor: Colors.redAccent,
elevation: 0,
actions: [
IconButton(
icon: Icon(Icons.search),
onPressed: () async{
final busqueda = await showSearch(
context: context,
delegate: SearchDelegateMP("Buscar...",this.historialMenuPrincipal)
);
if(busqueda != null ) {
if (this.historialMenuPrincipal.length > 0) {
// this.historialMenuPrincipal.add(busqueda);
/*historialMenuPrincipal.removeWhere((item) =>
item.email == busqueda.email); // si elimina*/
for (int i = 0; i < historialMenuPrincipal.length; i++) {
if(historialMenuPrincipal[i].email== busqueda.email){
print(historialMenuPrincipal[i].email);
break;
}else{
this.historialMenuPrincipal.add(busqueda);
break;
}
}
}else{ this.historialMenuPrincipal.add(busqueda);}
}
}
class SearchDelegateMPextends SearchDelegate<SearchDelegateM>{
#override
List<Widget> buildActions(BuildContext context) {
return [
//code
];
}
#override
Widget buildResults(BuildContext context) {
//code
}
Widget buildSuggestions(BuildContext context) {
return StatefulBuilderSuggestions(context,this.historialMenuPrincipal);
}
Widget StatefulBuilderSuggestions(BuildContext context ,List <SearchDelegateM> historialMenuPrincipal){
return Container(
child:StatefulBuilder(
builder:(context,setState)
{
return Container(
child: ListView.builder(
itemCount: historialMenuPrincipal.length,
itemBuilder: (context,i)
{
contentPadding: EdgeInsets.symmetric(vertical: 12,horizontal: 16);
leading:CircleAvatar(
radius: 32,
backgroundImage: NetworkImage(
"https://2.bp.blogspot.com/-3ZzNt8ZsjQk/WR9W4IFn4II/AAAAAAAAAJw/_inTVynhS60V7F5IZ-461-pda7WArTStwCEw/s1600/ANA.jpg"),
);
return
ListTile(
title: Text(historialMenuPrincipal[i].email ),
trailing: IconButton(
icon: Icon(Icons.cancel,color: Colors.black,),
onPressed: () {
setState(() {
historialMenuPrincipal.remove(historialMenuPrincipal[i]);
});
},)
);
}
),
);
}
)
);
}
enter image description here
Empty your list with every new search, before you start adding to it.
this.historialMenuPrincipal.clear();
What is happening is that the result is being added n number of times, even if the result is already there from previous searches.
N = the number of times the search is matched.
List can have repeated elements. You can parse your list to Set as set only contains unique elements.
List <SearchDelegateM> uniqueElementsList = historialMenuPrincipal.toSet().toList();
use this code before showing your elements in Listview.builder() and use uniqueElementsList in your builder.
Let me explain, I have two List Views with Items. At the moment I don’t give an id to these items but I have a problem. In fact, when I remove an item from my favorites list, it doesn’t change the icon (favorite or not favorite) for the right item on the home_screen.
I want to get the place of the item in the menu screen so I can change the icon from the favorites list. I’m using the provider package.
And so I wonder if it wouldn’t be better to create an id for each item and store a List<int> and then create a List<Item> in my favorites list. Also, I can use this id to change the right icon.
But I don’t know how to use these ids to create a List and then change the right icon.
Illustrations of what I said :
Black heart = in favorite and White heart = not in favorite.
It is the wrong item which is deleting.
My code on Github ans some relevant parts of my code :
favModel.dart
class FavModel extends ChangeNotifier {
List<Item> favList = [];
List<bool> isInFav = [];
addInFavorite(title, description, index){
Item item = Item(title: title, description: description, );
favList.add(item);
isInFav[index] = true;
notifyListeners();
}
removeOfFavorite(int index, int index2){
favList.removeAt(index);
isInFav[index2] = false;
notifyListeners();
}
implement(){
isInFav.add(false);
}
}
favorite_screen.dart
class Favorite extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Favorite'),
),
body: Consumer<FavModel>(
builder: (context, favModel, child) {
return ListView.builder(
itemCount: favModel.favList.length,
itemBuilder: (context, index) {
return TextObject(favModel.favList[index].title,
favModel.favList[index].description),
Padding(
padding: const EdgeInsets.all(7.0),
child: GestureDetector(
child: Icon(
Icons.favorite,
color: Colors.red,
size: 32,
),
onTap: () {
favModel.removeOfFavorite(index, index);
}),
),
});
},
),
);
}
}
home_screen.dart
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
actions: [
IconButton(
icon: Icon(Icons.favorite_border),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) {
return Favorite();
},
),
),
),
],
),
body: Consumer<FavModel>(builder: (context, favModel, child) {
return ListView.builder(
shrinkWrap: false,
itemCount: itemData.length,
itemBuilder: (context, index) {
favModel.implement();
return TextObject(
itemData[index].title, itemData[index].description),
Padding(
padding: const EdgeInsets.all(7.0),
child: GestureDetector(
child: Icon(
favModel.isInFav.elementAt(index)
? Icons.favorite
: Icons.favorite_border,
color:
favModel.isInFav[index] ? Colors.red : null,
size: 32,
),
onTap: () {
favModel.isInFav[index]
? null
: Provider.of<FavModel>(context,
listen: false)
.addInFavorite(
itemData[index].title,
itemData[index].description,
index,
);
}),
);
});
}),
);
}
}
Where I want to get the index is in the favorite_screen.dart at this line favModel.removeOfFavorite(index, index);
I would suggest you to add bool isFavorite to your class Item and add an id for the class also. So you can avoid having two arrays.
And using the id will help you using some awesome methods like findWhere and removeWhere
EDIT
You can iterate the List using for
for(int i = 0;i<favList.length;i++){
if(favList[i].id == selectedItem.id){
favList[i].isSelected = true;
break;// break the loop no need to continue
}
}
notifyListeners()
Notice that now you have to pass Item instead of index
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);
}