I started to use providers but I have a problem. I want to get the index of items that are in an other list in an other screen. How can i get them ? I have two screens: a home screen and a favorite screen and I have a listView in each. I want to get the index of the item in the home screen when it is remove from the favorite screen. This is the link of my code on GitHub : https://github.com/Rianou20/my_app_from_scratch/tree/master/my_app_from_scratch. And 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);
Without knowing the exact use case, you can potentially store the removed values in a list and use them on your home screen.
class FavModel extends ChangeNotifier {
List<Item> favList = [];
List<bool> isInFav = [];
List<int> _removedItemIndexList = []
get removedItemIndexList => _removedItemIndexList;
addInFavorite(title, description, countdown, imageURL, index){
Item item = Item(title: title, description: description, countdown:countdown, imageURL: imageURL);
favList.add(item);
isInFav[index] = true;
notifyListeners();
}
removeOfFavorite(int index, int index2){
favList.removeAt(index);
isInFav[index2] = false;
_addToRemovedIndexList(index);
notifyListeners();
}
void _addToRemovedIndexList(int index) {
_removedItemIndexList.add(index);
}
implement(){
isInFav.add(false);
}
}
And then use on home_sreen.dart as
...
body: Consumer<FavModel>(builder: (context, favModel, child) {
List<int> removedIndexes = favModel.removedItemIndexList;
return ListView.builder( ... ) };
Note that the FavModel provider class must be lifted above then home_screen.dart on the widget tree in order to be able to access its values. i.e. you would want to do something like this in your main.dart
...
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: FavModel(),
),
],
child: MaterialApp(...
Related
What is the right way to place all these widgets together? When I tried to place together widget which is building the listview. separated widget and the widgets where I am creating the UI for filters, it draws only widget with filtering items but doesn't create the listview. I was searching that I should place it in the column widget and in the expanded but it also doesn't work as I want.
Here is my code:
class _ProductListState extends State<ProductList> {
#override
Widget build(BuildContext context) {
var providerGridPagination = Provider.of<ProviderGridProduct>(context);
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
title: Text("Категории товаров", style: TextStyle(color: Colors.black45),),
leading: IconButton(onPressed: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const Home()),
);
}, icon: Icon(Icons.arrow_back, color: Colors.black45,),),
),
body: Column(
children: [
FiltersWidget(),
SmartRefresher(
controller: providerGridPagination.refreshController,
enablePullUp: true,
onRefresh: () async {
final result = await providerGridPagination.getProductData(isRefresh: true);
if (result) {
providerGridPagination.refreshController.refreshCompleted();
} else {
providerGridPagination.refreshController.refreshFailed();
}
},
onLoading: () async {
final result = await providerGridPagination.getProductData();
if (result) {
providerGridPagination.refreshController.loadComplete();
} else {
providerGridPagination.refreshController.loadFailed();
}
},
child: ListView.separated(
itemBuilder: (context, index) {
//final gridItems = providerGridPagination.itemgrid[index];
return ListTile(
title: Text(providerGridPagination.itemgrid[index].title!),
);
},
separatorBuilder: (context, index) => Divider(),
itemCount: providerGridPagination.itemgrid.length,
),
),
],
),
);
}
}
the error is:
The following assertion was thrown during a scheduler callback:
This widget has been unmounted, so the State no longer has a context (and should be considered defunct).
I use provider library state management for doing add to cart and basically i am a bit beginner in provider. So the issue i am facing is for example there are three products laptop , iphone x & keyboard. Now if i put laptop two times in the cart then in cart page it displays two laptop card widgets, instead i want to display only one card widget in that laptop qty: 2. And second issue is that i have implemented + and - button in each card widget in cart page and if i click on + or - button then it should reflect on qty and also on total price. Really appreciate if you help me in this problem.
main.dart
void main() {
runApp(ChangeNotifierProvider(
create: (context) => Cart(),
child: MyApp(),
));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final List<Item> items = [
Item(title: 'laptop ', price: 500.0),
Item(title: 'iphone x ', price: 400.0),
Item(title: 'keyboard ', price: 40.0),
];
#override
Widget build(BuildContext context) {
return Consumer<Cart>(builder: (context, cart, child) {
return Scaffold(
appBar: AppBar(
title: Text('Shopping cart'),
actions: <Widget>[
Padding(
padding: EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
IconButton(
icon: Icon(
Icons.shopping_cart,
color: Colors.white,
),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => CheckoutPage()));
},
),
Text(cart.count.toString())
],
),
)
],
centerTitle: true,
),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].title),
subtitle: Text(items[index].price.toString()),
trailing: Icon(Icons.add),
onTap: () {
cart.add(items[index]);
},
);
},
),
);
});
}
}
CheckoutPage.dart
class CheckoutPage extends StatefulWidget {
#override
_CheckoutPageState createState() => _CheckoutPageState();
}
class _CheckoutPageState extends State<CheckoutPage> {
#override
Widget build(BuildContext context) {
return Consumer<Cart>(
builder: (context, cart, child) {
return Scaffold(
appBar: AppBar(
title: Text('Checkout Page [\$ ${cart.totalPrice}]'),
actions: [
TextButton(
onPressed: () {
print(cart.totalPrice);
},
child: Text('Check'))
],
),
body: cart.basketItems.length == 0
? Text('no items in your cart')
: ListView.builder(
itemCount: cart.basketItems.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(cart.basketItems[index].title),
subtitle: Row(
children: [
TextButton(onPressed: () {}, child: Text('+')),
Text(cart.basketItems[index].qty.toString()),
TextButton(onPressed: () {}, child: Text('-')),
],
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
cart.remove(cart.basketItems[index]);
},
),
),
);
},
));
},
);
}
}
Item.dart
class Item {
String title;
double price;
Item({this.title, this.price});
}
Cart.dart
class Cart extends ChangeNotifier {
List<Item> _items = [];
double _totalPrice = 0.0;
void add(Item item) {
_items.add(item);
_totalPrice += item.price;
notifyListeners();
}
void remove(Item item) {
_totalPrice -= item.price;
_items.remove(item);
notifyListeners();
}
int get count {
return _items.length;
}
double get totalPrice {
return _totalPrice;
}
List<Item> get basketItems {
return _items;
}
}
Hmm try before adding item add a certain function that will look up for the duplicate item like this
e.g. inside on add
Add qty on you class on item.dart so that in every add item you should have default qty to one then goes this below.
class Item {
String title;
double price;
int qty;
Item({this.title, this.price,this.qty});
}
void add(Item item) {
final itemIsExist = _items.where((e)=> e.title == item.title);
if(itemIsExist.isNotEmpty){
// if item exist and you want to add +1 on qty
final addQty = _items.firstWhere((e)=> e.title == item.title);
addQty.qty= addQty.qty+1;
// do your thing here to calculate again the total
}else{
_items.add(item);
_totalPrice += item.price;
notifyListeners();
}
}
I suggest creating another variable on base class and extend it for model, But now let's follow your way.
We can create a map to iterate items on _CheckoutPageState and create a Set, but we need to count the item quantity,
We can take the help of map in this case and place it just under Consumer builder before returning Scaffold
Map<String, int> itemsMap = {};
for (final item in cart._items) {
if (!itemsMap.containsKey(item.title)) {
itemsMap.putIfAbsent(item.title, () => 1);
} else {
itemsMap.update(item.title, (value) => itemsMap[item.title]! + 1);
}
}
And uses will be like
itemBuilder: (context, index) {
final keys = itemsMap.keys.toList();
final count = itemsMap.values.toList();
return Card(
child: ListTile(
title: Text(keys[index].toString()),
subtitle: Row(
children: [
TextButton(onPressed: () {}, child: Text('+')),
Text(count[index].toString()),
TextButton(onPressed: () {}, child: Text('-')),
],
),
State class
class _CheckoutPageState extends State<CheckoutPage> {
#override
Widget build(BuildContext context) {
return Consumer<Cart>(
builder: (context, cart, child) {
Map<String, int> itemsMap = {};
for (final item in cart.basketItems) {
if (!itemsMap.containsKey(item.title)) {
itemsMap.putIfAbsent(item.title, () => 1);
} else {
itemsMap.update(item.title, (value) => itemsMap[item.title]! + 1);
}
}
return Scaffold(
appBar: AppBar(
title: Text('Checkout Page [\$ ${cart.totalPrice}]'),
actions: [
TextButton(
onPressed: () {
print(cart.totalPrice);
},
child: Text('Check'))
],
),
body: cart.basketItems.length == 0
? Text('no items in your cart')
: ListView.builder(
itemCount: itemsMap.length,
itemBuilder: (context, index) {
final keys = itemsMap.keys.toList();
final count = itemsMap.values.toList();
return Card(
child: ListTile(
title: Text(keys[index].toString()),
subtitle: Row(
children: [
TextButton(
onPressed: () {
cart.add(
Item(
title: keys[index].toString(),
price: keys[index].trim() == "laptop"
? 500
: keys[index].trim() == "iphone x"
? 400
: 40,
),
);
},
child: Text('+')),
Text(count[index].toString()),
TextButton(
onPressed: () {
cart.remove(Item(
title: keys[index].toString(),
price: keys[index].trim() == "laptop"
? 500
: keys[index].trim() == "iphone x"
? 400
: 40,
));
},
child: Text('-')),
],
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
cart.remove(cart.basketItems[
index]); // remove match all on remove method
},
),
),
);
},
));
},
);
}
}
I tried making a growable list which will contain the salary that have been calculated from other pages. But when I tried to insert the latest salary into the list, the previous salary was rewritten as the latest added salary, so I ended up with 2 same item in the list. So I ended up with 2 same items inside the list. My objective here is to just make a screen with a growable list where the counter increases when I pushed the button from the previous page. Here is my code:
`class HistoryPage extends StatefulWidget {
HistoryPage ({Key key, this.title}) : super(key : key);
final String title;
#override
_HistoryPageState createState() => _HistoryPageState();
}
class _HistoryPageState extends State<HistoryPage> {
List<String> items = [];
dynamic salary;
dynamic counter = 0 ;
#override
void initState() {
items = addItem(counter, salary);
salary = globals.payment;
counter = globals.counter;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[200],
appBar: AppBar(
backgroundColor: Colors.blue[800],
title: Text("Your Salary History"),
centerTitle: true,
elevation: 0,
),
body: ListView.builder(
itemCount: counter,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(salary),
),
);
},
),
);
}
List addItem(int counter, String salary) {
setState(() {
items = List.from(items)
..insert(counter, salary);
}
);
}
}`
Here is the code for the page that increase the counter for my list:
import 'package:flutter/material.dart';
import 'package:flutter_auth/Screens/History/components/body.dart';
import 'package:flutter_auth/Screens/Payment/Components/background.dart';
import 'package:flutter_auth/components/rounded_button.dart';
import 'package:flutter_auth/Screens/PaymentDuration/paymentduration.dart';
import 'package:flutter_auth/globals.dart' as globals;
class PaymentSelection extends StatelessWidget {
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
// This size provide us total height and width of our screen
return Background(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(height: size.height * 0.05),
RoundedButton(
text: "CALCULATE PAYMENT",
press: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return PaymentDuration();
},
),
);
},
),
RoundedButton(
text: "HISTORY",
press: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return HistoryPage();
},
),
);
incrementCounter();
},
),
],
),
),
);
}
}
int incrementCounter() {
globals.counter = globals.counter + 1;
}
I use FutureBuilder in IndexedListView to show a phonebook list,
Widget _buildBody(context) {
String url = api_url_phonebook;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Column(
children: [
TextField(
onTap: () => onTextChanged(_controller.text),
...
),
Expanded(
child: IndexedListView(
100.0,
_getData(context, url),
...
),
),
],
),
);
}
Future<Either<ResponseError, List<dynamic>>> _getData(context, url) {
final result = RequestApi(url).fetchList();
return result;
}
onSearchTextChanged(String text) {
...
}
Now I want filter this phonebook when I input text in TextField, what can I do?
I suggest you to use the Delegate pattern to do that.
Make a class like the following:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mywallet/model/user_account.dart';
import 'package:mywallet/ui/account_item.dart';
class AccountListSearch extends SearchDelegate {
final List<UserAccount> newItems;
String selectedResult = '';
AccountListSearch(this.newItems);
bool _match(UserAccount account, String text) {
return account.name.toLowerCase().contains(text.toLowerCase()) ||
account.user.toLowerCase().contains(text.toLowerCase());
}
#override
List<Widget> buildActions(BuildContext context) => [
IconButton(
icon: Icon(Icons.close),
onPressed: () {
query = '';
},
),
];
#override
Widget buildLeading(BuildContext context) => IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
);
#override
Widget buildResults(BuildContext context) {
return Container(
child: Center(
child: Text(selectedResult),
));
}
#override
Widget buildSuggestions(BuildContext context) {
var suggestionList = <UserAccount>[];
query.isEmpty
? suggestionList = newItems
: suggestionList.addAll(
newItems.where((item) {
return _match(item, query);
}),
);
return ListView.builder(
itemCount: suggestionList.length,
itemBuilder: (context, index) => AccountItem(
account: suggestionList[index],
),
);
}
}
Copy and paste the AccountListSearch class, only change the UserAccount class with your data class ( PhoneBook Item), and change the AccountItem stateless widget with your Widget used to display your PhoneBook item ( something like class PhoneBookItemWidget extends StatelessWidget......)
Then in the main page like this class:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<AccountCubit, AccountState>(
builder: (_, state) {
var currentState = state;
if (currentState is InitializedState) {
var accounts = currentState.userAccounts;
return Scaffold(
appBar: AppBar(
title: Text('All Accounts'),
actions: [
IconButton(
onPressed: () {
showSearch(
context: context,
delegate: AccountListSearch(accounts));
},
icon: Icon(
Icons.search,
),
)
],
),
drawer: AppDrawer(),
body:
ListView.builder(
itemCount: accounts.length,
itemBuilder: (context, counter) => AccountItem(
account: accounts[counter],
)
....
}
As you can see, the 'magic' is in the method :
showSearch( context: context, delegate: AccountListSearch(accounts));
The method showSearch(...) is inherited from StatelessWidget and make all the work for you.
I used the Bloc pattern, but it is not important right now, only to tell where to find the list of items, but it is not directly related to your question.
I'm trying to display data in a ListView with a FutureBuilder. In debug mode, when I launch the app, no data is displayed, but, if I reload the app (hot Reload or hot Restart), the ListView displays all the data. I already tried several approaches to solve this - even without a FutureBuilder, I still haven't succeeded. If I create a button to populate the ListView, with the same method "_getregistos()", the ListView returns the data correctly.
This is the code I'm using:
import 'package:flutter/material.dart';
import 'package:xxxxx/models/task_model.dart';
import 'package:xxxxx/shared/loading.dart';
class AddTask extends StatefulWidget {
static const id = 'add_task';
#override
_AddTaskState createState() => _AddTaskState();
}
class _AddTaskState extends State<AddTask> {
dynamic tasks;
final textController = TextEditingController();
_getRegistos() async {
List<TaskModel> taskList = await _todoHelper.getAllTask();
// print('DADOS DA tasklist: ${taskList.length}');
return taskList;
}
TaskModel currentTask;
final TodoHelper _todoHelper = TodoHelper();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(32),
child: Column(
children: <Widget>[
TextField(
controller: textController,
),
FlatButton(
child: Text('Insert'),
onPressed: () {
currentTask = TaskModel(name: textController.text);
_todoHelper.insertTask(currentTask);
},
color: Colors.blue,
textColor: Colors.white,
),
//
FutureBuilder(
future: _getRegistos(),
builder: (context, snapshot) {
if (snapshot.hasData) {
tasks = snapshot.data;
return ListView.builder(
shrinkWrap: true,
itemCount: tasks == null ? 0 : tasks.length,
itemBuilder: (BuildContext context, int index) {
TaskModel t = tasks[index];
return Card(
child: Row(
children: <Widget>[
Text('id: ${t.id}'),
Text('name: ${t.name}'),
IconButton(
icon: Icon(Icons.delete), onPressed: () {})
],
),
);
},
);
}
return Loading();
}),
],
),
),
);
}
}
Thank you.
You need to use ConnectionState inside your builder. Look at this code template: (Currently your builder returns ListView widget without waiting for the future to complete)
return FutureBuilder(
future: yourFuture(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// future complete
// if error or data is false return error widget
if (snapshot.hasError || !snapshot.hasData) {
return _buildErrorWidget();
}
// return data widget
return _buildDataWidget();
// return loading widget while connection state is active
} else
return _buildLoadingWidget();
},
);
Thanks for your help.
I already implemented ConnectionState in the FutureBuilder and the issue persists.
When I launch the app, I get error "ERROR or No-Data" (is the message I defined in case of error of no-data.
If I click on the FlatButton to call the method "_getTasks()", the same method used in FutureBuilder, everything is ok. The method return data correctly.
This is the code refactored:
import 'package:flutter/material.dart';
import 'package:xxxx/models/task_model.dart';
import 'package:xxxx/shared/loading.dart';
class AddTask extends StatefulWidget {
static const id = 'add_task';
#override
_AddTaskState createState() => _AddTaskState();
}
class _AddTaskState extends State<AddTask> {
final textController = TextEditingController();
Future<List<TaskModel>> _getTasks() async {
List<TaskModel> tasks = await _todoHelper.getAllTask();
print('Tasks data: ${tasks.length}');
return tasks;
}
TaskModel currentTask;
//list to test with the FlatButton List all tasks
List<TaskModel> tasksList = [];
final TodoHelper _todoHelper = TodoHelper();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(32),
child: Column(
children: <Widget>[
TextField(
controller: textController,
),
FlatButton(
child: Text('Insert'),
onPressed: () {
currentTask = TaskModel(name: textController.text);
_todoHelper.insertTask(currentTask);
},
color: Colors.blue,
textColor: Colors.white,
),
//when clicking on this flatButton, I can populate the taskList
FlatButton(
child: Text('Show all Tasks'),
onPressed: () async {
List<TaskModel> list = await _getTasks();
setState(() {
tasksList = list;
print(
'TaskList loaded by "flatButton" has ${tasksList.length} rows');
});
},
color: Colors.red,
textColor: Colors.white,
),
//
FutureBuilder(
future: _getTasks(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// future complete
// if error or data is false return error widget
if (snapshot.hasError || !snapshot.hasData) {
return Text('ERROR or NO-DATA');
}
// return data widget
return ListItems(context, snapshot.data);
// return loading widget while connection state is active
} else
return Loading();
},
),
],
),
),
);
}
}
//*****************************************
class ListItems extends StatelessWidget {
final List<TaskModel> snapshot;
final BuildContext context;
ListItems(this.context, this.snapshot);
#override
Widget build(BuildContext context) {
return Expanded(
child: ListView.builder(
itemCount: snapshot == null ? 0 : snapshot.length,
itemBuilder: (context, index) {
TaskModel t = snapshot[index];
return Text(' ${t.id} - ${t.name}');
}),
);
}
}