I have been working on this for several hours and I give up. I need help please. I am selecting a BottomNavBarItem onTap and I launch a void function called _onItemTapped which then determines the index of the BNBI then launches a new Class. I have a couple test prints in the code so I can see that it gets down to the second 'print 'testing1'' but nothing loads and nothing happens. Can someone please explain what I am doing wrong?
Code below from the BNBI code
BottomNavigationBarItem(
backgroundColor: Colors.blueGrey,
icon: Icon(Icons.login),
label: ' Search \n Ingredients Db',
tooltip:
'Login or Create an Account to Search Ingredients Database',
),
],
onTap: _onItemTapped,
//loads
void _onItemTapped(final int index) {
setState(() {
Future.sync(() => _CocPageState());
_selectedIndex = index;
});
if (_selectedIndex == 0) {
_CocPageState()
.build(context); // takes you to the Chemicals of Concern page.
} else if (_selectedIndex == 1) {
_CocPageState().build(context); // //todo go to the dirty list
} else if (_selectedIndex == 2) {
_CocPageState().build(context); //todo go to the safer products list
}
_CocPageState().build(context);
}
// to the Class
class CocPage extends StatefulWidget {
#override
_CocPageState createState() => _CocPageState();
}
class _CocPageState extends State<CocPage> {
#override
Widget build(BuildContext context) {
print('test');
const title =
'Chemicals of Concern';
print('testing1'); //nothing happens beyond this point that I can tell.
return Scaffold(
body: ListView.builder(
itemCount: chemBrain.chemsBank.length,
// itemBuilder: (context, index),
prototypeItem: ListTile(
title: Text('Index 0: chemBrain.chemsBank[index].chemName')),
itemBuilder: (BuildContext context, index) {
return ListTile(
title: Text(chemBrain.chemsBank[index].chemName),
trailing: Column(
children: [
InkWell(
child: Text(chemBrain.getChemsName()),
I would suggest that you call Navigator.of(context).push() when you tap on the BottomNavigationBarItem.Then you simply push to a new screen and don't have to worry about state.
Related
I'm creating a todo list (sort of) app.
I have a view where I use a FutureBuilder, it calls a function to fetch the articles from the SQLite db and show as Card.
I have a getx_controller.dart which contains the list as observable:
class Controller extends GetxController {
// this is the list of items
var itemsList = <Item>[].obs;
}
and I have a items_controller.dart
// function to delete an item based on item id
void deleteItem(id) {
final databaseHelper = DatabaseHelper();
// remove item at item.id position
databaseHelper.deleteItem(tableItems, id);
print('delete item with id $id');
this.loadAllItems();
}
the loadAllItems is a function that queries the DB
And finally, the list_builder.dart which contains the FutureBuilder
final itemController = Get.put(ItemsController());
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Item>>(
future: itemController.loadAllItems(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.hasData) {
print(snapshot.data.toString());
return Obx(() => ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return Slidable(
actionExtentRatio: 0.25,
actionPane: SlidableDrawerActionPane(),
child: _itemCard(index, snapshot.data![index]),
actions: <Widget>[
IconSlideAction(
caption: 'Elimina',
color: Colors.red,
icon: Icons.delete,
onTap: () {
itemController.deleteItem(snapshot.data![index].id);
itemController.loadAllItems();
}),
],
secondaryActions: <Widget>[
IconSlideAction(
caption: 'Modifica',
color: Colors.blue,
icon: Icons.edit,
onTap: () {},
),
],
);
}));
} else {
return Container(
child: Text('Loading'),
);
}
});
// });
}
My issue is that when I tap on "Elimina" (or I create a new item, same procedure), the ListBuilder doesn't refresh the list on the screen, despite of having the itemsList recreated (using getxController.itemsList = RxList.generate(...)) and observable.
You don't need to use FutureBuilder when using observables. This is redundant and makes your code complicated.
Instead you should assign the awaited Future (the actual data/items) to your observable list (itemList). And your UI should update automagically.
So your controller should look something like this:
class Controller extends GetxController {
// this is the list of items
var itemsList = <Item>[].obs;
#override
onInit()async{
await loadAllItems();
}
loadAllItems()async{
var databaseResponse= await dbHelper.getAll();
itemList.assignAll(databaseResponse);
}
}
And your UI build method should just return the observing (Obx) ListView.builder like the following and you are done:
#override
Widget build(BuildContext context){
return Obx(() => ListView.builder(
itemCount: controller.itemList.length,
itemBuilder: (context, index) {
var item = comtroller.itemList[index]; // use this item to build your list item
return Slidable(
I have three screen.
Home screen. 2 Mortgage Screen. 3. New branch Screen. [Each Mortgage can have one or more branches]
The home screen shows a list of all current mortgages a user ended, with a summary of each the branches in each mortgages.
When the user clicks on one of the mortgages in the list in screen 1, he gets to screen 2 which shows all the details of the branches of that mortgage. User can add new branch by clicking floating action button, to get to page 3.
In page 3, the user fills out a form to add a new branch. Once a branch is added, page 3 is popped, and page 2 is still appearing.
When page 3 is done, a new branch is added to the selected mortgage, and it is supposed to update the data displayed in page 2 and in page 1. I have done this by passing callback methods into pages 2 and 1, and then calling set state in both classes.
Page 2 is updated and displays fine. However, when I go back from page 2 to page 1, page 1 has not updated. Even though the setState method is called in page 1.
I hope its clear, I will add the code of page 1, and maybe you can help me see why the page is not rerendering.
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
List<MaslulModel> savedMaslulim = <MaslulModel>[];
List<MortgageModel> savedMortgages = <MortgageModel>[];
// THIS METHOD IS CALLED FROM PAGE 2.
notifyHomeScreen() async {
print('2124: notifyHomeScreen called in home_screen');
savedMaslulim.clear();
savedMortgages.clear();
savedMaslulim = await SharedPrefsMethods.getMaslulListFromPrefs();
for (var i = 0; i < savedMaslulim.length; i++) {
print(savedMaslulim[i].getDetails());
}
savedMortgages = sortOutMaslulimToMortgages(savedMaslulim);
setState(() {
print('2124: Set state. Maslul at 0 List size: ${savedMortgages[0].maslulList.length}');
});
}
TextEditingController _textFieldController = TextEditingController();
String codeDialog = '';
String valueText = '';
#override
initState() {
super.initState();
print('InitState');
asyncGetSavedMortgages();
}
void asyncGetSavedMortgages() async {
savedMaslulim = await SharedPrefsMethods.getMaslulListFromPrefs();
savedMortgages = sortOutMaslulimToMortgages(savedMaslulim);
print(savedMortgages.length);
setState(() {
print('Set state called');
});
}
#override
Widget build(BuildContext context) {
for (var i = 0; i < savedMortgages.length; i++) {
if(savedMortgages[i].name=='tonight'){
print('2124: From HOME: ${savedMortgages[i].maslulList.length}');
}
}
return Scaffold(
appBar: AppBar(title: Text(AppLocalizations.of(context)!.translate('my_mortgages'))),
drawer: MainDrawer(),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
// Navigator.pushNamed(context, '/new_mortgage_screen');
_displayTextInputDialog(context);
},
label: Text('הוסף משכנתא'),
icon: Icon(Icons.add),
backgroundColor: Colors.pink,
),
body: ListView.builder(
itemCount: savedMortgages.length,
key: Key(savedMortgages.length.toString()),
itemBuilder: (context, index){
for (var i = 0; i < savedMortgages.length; i++) {
if(savedMortgages[i].name=='tonight'){
print('2124: From HOME itemBuilder: ${savedMortgages[i].maslulList.length}');
}
}
return MortgageSummaryWidget(savedMortgages[index], notifyHomeScreen: notifyHomeScreen );
},
),
);
}
Future<void> _displayTextInputDialog(BuildContext context) async {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('הכנס שם של המשנכתא:'),
content: TextField(
onChanged: (value) {
setState(() {
valueText = value;
});
},
controller: _textFieldController,
decoration: InputDecoration(hintText: "שם"),
),
actions: <Widget>[
FlatButton(
color: Colors.white,
textColor: Colors.red,
child: Text('בטל'),
onPressed: () {
setState(() {
Navigator.pop(context);
});
},
),
FlatButton(
color: Colors.blue,
textColor: Colors.white,
child: Text('בצע'),
onPressed: () {
setState(() {
codeDialog = valueText;
if(codeDialog.isEmpty){
showAlertDialog(context, 'שגיאה', 'לא הכנסת שם מסלול');
return;
}
Navigator.pop(context);
// Navigator.pushNamed(context, '/new_mortgage_screen');
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => NewMortgageScreen(notifyParent: notifyHomeScreen, title: codeDialog,)));
// Navigator.pushNamed(
// context,
// '/new_mortgage_screen',
// arguments: {'mortgageName': codeDialog}
// );
});
},
),
],
);
});
}
}
All the values are updated, but the screen display isn't.
I cannot figure this out. Thanks
I realised the problem, I was sending a parameter into the State, and this wan't getting updated. I changed it to get the parameter by using widget.parameter.
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(...
I am trying to build a list where each item contains an icon on the far side. However, when I try and access the list when more than one item is added I get the error:
Invalid value: Only valid value is 0: 1
Here is the code:
import 'package:flutter/material.dart';
import './images.dart';
class LikedList extends StatefulWidget {
#override
_LikedListState createState() => _LikedListState();
}
class _LikedListState extends State<LikedList> {
static List<bool> _likes = List.filled(ImagesState.likes.length,true);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Liked List'),
),
body: ListView.separated(
separatorBuilder: (context, index) => Divider(),
itemCount: ImagesState.likes.length,
itemBuilder: (context, index) {
final item = ImagesState.likes[index];
return ListTile(
title: Text(item),
trailing: IconButton(
icon: _likes[index]
? Icon(
Icons.favorite_border,
color: Colors.grey,
)
: Icon(
Icons.favorite,
color: Colors.red,
),
onPressed: () {
setState(() {
print(_likes);
_likes[index] = !_likes[index];
print(_likes);
});
},
),
onLongPress: () {
setState(() {
print(ImagesState.likes[index]);
ImagesState.likes.removeAt(index);
});
},
);
},
),
);
}
}
Does anyone know why this is happening?
Similar suggestions point to itemCount not being set, but I am using that.
You appear to be creating a fixed length list with the statement:
static List<bool> _likes = List.filled(ImagesState.likes.length,true);
That will create a filled fixed length list of length .likes.length, containing values 'true'.
You need to add a third parameter 'growable: true' in your method call to be able to add to the list.
Trying to activate CupertinoTabBar's tab 0 while tab 1 is active on a stream event like this:
{
class HomeScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => HomeScreenState();
}
class HomeScreenState extends State<HomeScreen> {
int _currentTabIndex = 0;
#override
void initState() {
super.initState();
_drawerStream.listen((state) {
if (_currentTabIndex != 0) {
SchedulerBinding.instance.addPostFrameCallback((_) {
setState(() => _currentTabIndex = 0);
});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(
elevation: 0.0,
child: DrawerScreen(),
),
body: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
onTap: (index) {
_currentTabIndex = index;
},
currentIndex: _currentTabIndex,
backgroundColor: Colors.white,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
title: Text('Main'),
icon: Icon(IconData(0xe800), size: 20),
),
BottomNavigationBarItem(
title: Text('Goodies'),
icon: Icon(IconData(0xe84b), size: 20),
),
],
),
tabBuilder: (BuildContext context, int index) {
return CupertinoTabView(
builder: (BuildContext context) {
switch (index) {
case 0: return MainScreen();
case 1: return GoodiesScreen();
}
},
);
},
),
);
}
}
}
It nothing happens visually when an event comes from _drawerStream. Still tracing what's going on using the debugger it found that CupertinoTabBar widget builds 2 times and 1st time it has current index parameter 0, what we actually need. But the second run it rebuilds with current index parameter set to 1, which is not what we want.
What the reason for that, how we can switch to a tab on an external event?
It's solved using StreamBuilder, a method that was actually attempted first. This method has a different caveat: it switches to tab 0 when we have to stay on, for example, 1st tab and it happens when we come back from another screen open on top of the screen with tabs.
What's wrong this time? The stream builder re-emits last event (BehaviourSubject based stream) and this way _currentTabIndexis set to 0, while we need it to keep a current value. The solution is to "remember" the last event and recognize it just re-emitted. This may not solve all similar issues, but at least gives a clue.
A piece of code to illustrate the solution:
class HomeScreenState extends State<HomeScreen> {
int _currentTabIndex = 0;
AsyncSnapshot<UIState> lastSnapshot;
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(
elevation: 0.0,
child: DrawerScreen(),
),
body: StreamBuilder(
stream: _drawerStream,
builder: (context, AsyncSnapshot<UIState> snapshot) {
if (_currentTabIndex != 0 && lastSnapshot != snapshot) {
SchedulerBinding.instance.addPostFrameCallback((_) =>
setState(() => _currentTabIndex = nextTab));
}
lastSnapshot = snapshot;
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
onTap: (index) {
_currentTabIndex = index;
},
currentIndex: _currentTabIndex,
backgroundColor: Colors.white,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
title: Text('Main'),
icon: Icon(IconData(0xe800), size: 20),
),
BottomNavigationBarItem(
title: Text('Goodies'),
icon: Icon(IconData(0xe84b), size: 20),
),
],
),
tabBuilder: (BuildContext context, int index) {
return CupertinoTabView(
builder: (BuildContext context) {
switch (index) {
case 0: return MainScreen();
case 1: return GoodiesScreen();
}
},
);
},
),
);
}
}
You can also use GlobalKeys and your problem should go away. Here is a clear tutorial on how to do it
https://medium.com/#info_4766/ios-tab-bar-in-flutter-9379cf09df31