How to place in one widget tree several widgets flutter? - flutter

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).

Related

Flutter: Set global variable using OK button on AlertDialog in StatefulBuilder

How to set global variable using OK button on AlertDialog in StatefulBuilder?
String stringMain = "My String";
Future<String?> dialogChangeMainContent(BuildContext context) {
return showDialog<String>(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(builder: (context, setState) {
return AlertDialog(
title: const Text('AlertDialog Title'),
content: const Text('AlertDialog description'),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context, 'OK');
setState(() {
// setState2();
stringMain = "New String";
});
},
child: const Text('OK'),
),
],
);
});
});
}
I changed StatefulWidget from StatelessWidget, but nothing changes. What I know is the problem is because the button is in the StatefulBuilder. If using BLoC etc, it's too wasteful for only small applications.
I found a way to solve my problem, redrawing (refreshing) the Text (or other widget) in a Stateless Widget from an AlertDialog in a StatefulBuilder. By creating a setState with a different name, for example setStateTarget.
Without having to rebuild the entire page, or without state management like BLoC. My code:
String stringMain = "My String";
#override
Widget build(BuildContext context) {
return Column(
// ....
children: [
StatefulBuilder(builder: (BuildContext context, setStateTarget) {
return Column(children: [
Text(stringMain), // <--- target
TextButton(
onPressed: () => dialogChangeMainContent(context, setStateTarget),
child: const Text('Show Dialog'))
]);
})
],
);
}
Future<String?> dialogChangeMainContent(BuildContext context, StateSetter setStateTarget) {
return showDialog<String>(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(builder: (context, setState) {
return AlertDialog(
title: const Text('AlertDialog Title'),
content: const Text('AlertDialog description'),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context, 'OK');
setStateTarget(() {
// setState2();
stringMain = "New String";
});
},
child: const Text('OK'),
),
],
);
});
});
}

Should I create a bloc builder in each view flutter

I am creating an application for managing shopping list, products and users using firestore and flutter, and I am starting to learn the bloc pattern. I have 2 blocs in my app the AuthBloc for users, and the ShoppingBloc for shopping lists. Right now I display the list of shopping lists of an user, and when I press a button I want to go to another screen to create a new shopping list. I want that when I press the button I change the state and when the state is change a listener (or something similar) changes the view.
My ShoppingListView is:
#override
Widget build(BuildContext context) {
context.read<ShoppingBloc>().add(const ShoppingEventInitialize());
return BlocConsumer<ShoppingBloc, ShoppingState>(
listener: (context, state) {
if (state is ShoppingCartState) {
print('El estado es shoppingCartState');
} else {
print('El estado es ' + state.toString());
}
},
builder: (context, state) {
if (state is ShoppingCartState) {
return Scaffold(
appBar: AppBar(
title: const Text('Your Shopping Cart Lists'),
actions: [
IconButton(
onPressed: () {
context.read<ShoppingBloc>().add(CreateEvent());
},
icon: const Icon(Icons.add),
),
PopupMenuButton<MenuAction>(
onSelected: (value) async {
switch (value) {
case MenuAction.logout:
final shouldLogout = await showLogOutDialog(context);
if (shouldLogout) {
context.read<AuthBloc>().add(const AuthEventLogout());
}
}
},
itemBuilder: (context) {
return [
const PopupMenuItem<MenuAction>(
value: MenuAction.logout,
child: Text('Logout'),
),
];
},
)
],
),
body: StreamBuilder(
stream:
_shoppingCartService.getShoppingCartLists(ownerUserId: userId),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
case ConnectionState.active:
if (snapshot.hasData) {
final allShoppingCartLists =
snapshot.data as Iterable<CloudShoppingCartList>;
return ShoppingCartListView(
shoppingCartLists: allShoppingCartLists,
onDeleteShoppingCartList: (shoppingCartList) async {
await _shoppingCartService.deleteShoppingCart(
shoppingCartListId:
shoppingCartList.shoppingCartListId);
},
onTap: (shoppingCartList) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ShoppingCartListDetailsView(
shoppingCartId:
shoppingCartList.shoppingCartListId,
shoppingCartName: shoppingCartList
.name)),
);
},
);
} else {
return const CircularProgressIndicator();
}
default:
return const CircularProgressIndicator();
}
})
);
} else {
return (const CreateUpdateShoppingCartListView());
}
And the CreateShoppingListView is:
#override
Widget build(BuildContext context) {
return BlocBuilder<ShoppingBloc, ShoppingState>(
builder: (context, state) {
if (state is ShoppingCartState) {
return ShoppingCartView();
} else
return Scaffold(
appBar: AppBar(
title: const Text('New shopping cart list'),
actions: [
IconButton(
onPressed: () {
final text = _textController.text;
final ownerUserId = currentUser.id;
context.read<ShoppingBloc>()
.add(ShoppingCreateNewShoppingCartEvent(
ownerUserId, text));
},
icon: const Icon(Icons.add),
),
IconButton(
onPressed: () async {
final text = _textController.text;
if (_shoppingCartList == null || text.isEmpty) {
await showCannotShareEmptyNoteDialog(context);
} else {
Share.share(text);
}
},
icon: const Icon(Icons.share),
)
],
),
body: Column(children: [
TextField(
controller: _textController,
keyboardType: TextInputType.multiline,
maxLines: null,
textInputAction: TextInputAction.go,
decoration: const InputDecoration(
hintText: 'Start typing you shopping cart name...',
),
),
],
}
}
I needed to create a bloc builder in the CreateShoppingListView to listen to the shoppingState and change view if the state is ShoppingCartState. My question is, it is necessary to create a bloc builder in each view to react to the states changes or is there a way to create a bloc builder that works for all views. I don't know if I explained myself corretly.
Thank you in advance

How to prevent duplicate card widget on same product if i click more than one time in Flutter

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
},
),
),
);
},
));
},
);
}
}

How to filter list when use FutureBuilder?

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.

Get index of an Item from an other List

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(...