Problem: CupertinoTabScaffold tab content does not refresh after closing new screen from CupertinoPageRoute
App:
There is a list of data in a Listview.
I open one data set and remove the favourite status (checked=false).
I close the data set and the listview should refresh (FutureBuilder to an SQL database).
Details:
I've got a Flutter App with two layouts (Material and Cupertino).
In each layout i have four tabs and the content widgets are the same.
On the Cupertino App every tab consists of a CupertinoPageScaffold.
When i press on an element it navigates to a new screen with CupertinoPageRoute.
After closing the CupertinoPageRoute the tab content is not refreshing.
The tab content is refreshing when i switch between the four tabs.
If one CupertinoPageRoute is open and i open a new one from here and close it again
then the content of the opened CupertinoPageScaffold is refreshing.
The data is refreshing without a callback etc., just by calling the future (i think).
Means there should be a problem with the CupertinoTabScaffold.
There is no problem on the Material App.
The tab contents of the Material App are refreshing when closing a MaterialPageRoute.
Question:
Is there a problem at this position?
Do you have the same issue or maybe a solution or workaround?
Code:
My code is not really representative so i will create a new clean project for you.
please confirm if code is necessary.
What i tried:
I tried a lot of solutions from the internet like callbacks, setState, .then, .whencomplete for the route.
nothing seems to work.
Thank you in advance.
EDIT 03.10.2021: the tabcontroller with SingleTickerProviderStateMixin reloads the open tab when i open or close an new screen. if i just use DefaultTabController Widget than it doesn't. So i have to edit the CupertinoTabController.
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late TabController _tabController;
final CupertinoTabController _cuptabController = CupertinoTabController();
Future<void> _refreshdata() async {
downloadJSON1();
setState(() {
});
}
callback() {
_refreshdata();
}
#override
void initState() {
_refreshdata();
super.initState();
_tabController = TabController(vsync: this, length: 2, initialIndex: 0);
_cuptabController.index = widget.starttab;
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
...
}
I can suggest a few options:
Option 1:
change your FutureBuilder into a StreamBuilder.
FutureBuilder does not refresh automatically when you save the data which StreamBuilder does very well.
Option 2:
Use a setState but not on the context of the route you are closing, but rather on the context of the original page you want to refreh
setState(() { });
for the moment i solved it with a .then-callback from PageRoute. i just passed the callback to the deepest Widget/PageRoute. But if you have a solution/idea for the tabcontroller would be great.
Souce:
how to refresh state on Navigator.Pop or Push in flutter
Navigator.push(context,
CupertinoPageRoute(
builder: (BuildContext context) => MyWidget(
'myparameters'),),).then((value) => setState(() {callback();}));
Related
In my app, I have a homepage that has 5 tabs on the bottom. On each tabbed page, there is an app bar that has a '+' symbol as an action, which navigates you to a different page. The navigation with that '+' button to the new page is done with the following code, alongside the Flutter Platform Widgets package:
Navigator.of(context, rootNavigator: true)
.push(
platformPageRoute(
context: context,
builder: (context) => Page1(),
),
);
I use the platformPageRoute feature as an easy way to navigate with a native feel. Now, that works fine to navigate to a new page, but the issue comes when I use
Navigator.pop(context);
to navigate back to the original page. When I use that to navigate back to that original page, it pays no attention to the tab that was selected originally. For example, if I were originally on the second tab on the homepage and then use the '+' button on that tab and then finally use
Navigator.pop(context);
on that new page, it returns the first tab of the homepage. Is there any way of ensuring when I use the above command, it goes to the right tab? I have tried something along the lines of:
Navigator.popUntil(context, '/homepageTab2');
alongside a named route, to return to the correct tab on the homepage, although that returns a black screen. Why might that be? I have also tried using:
Navigator.pushAndRemoveUntil(
context,
platformPageRoute(
context: context,
builder: (context) =>
HomePage(selectedPage: 1),
),
(route) => false,
);
This does not work either, since it returns the selected/correct page tab content, but with the first tab selected. In addition, the other
'problem' for me is that the animation is a 'push' one and that doesn't 'match' with the animation when I have more often used
Navigator.pop(context);
to navigate back to a screen. Is there a way to maybe use pushAndRemoveUntil but then change the animation to match a pop animation?
Thanks!
EDIT:
I have just noticed that with the situation I have described above, it is actually returning the correct screen content when I use Navigator.pop(context); but the tab in the tab bar at the bottom is showing as the first tab, in the second tab's position, essentially duplicating the first tab, until I navigate to a new tab and back, at which time it shows the correct tab in the correct position. I hope that makes sense!
As it turns out, the issue wasn't related to Navigator.pop(context); being used. It was the way I was controlling the selected tab. I'm posting this as an answer incase it helps someone else.
Initially, I created late values for a tab controller and the current selected page, like so:
late TabController _tabController;
late ScrollController _scrollController;
late int _selectedPage;
Then, I created a list of widgets that represented the actual page to display for each selected tab:
List<Widget> _pageWidgets = <Widget>[
Page1();
Page2();
Page3();
Page4();
Page5();
];
Then (and I think this was the bit that wasn't working) I used initState() as follows:
void initState() {
super.initState();
// Initialising a value that allows the 'final' page selector to be changed
_selectedPage = widget.selectedPage;
// Initialising the tab controller
_tabController = TabController(
length: 5,
vsync: this,
initialIndex: _selectedPage,
);
// updating the tab index when a new item is selected
_tabController.addListener(() {
setState(() {
_selectedPage = _tabController.index;
//_tabIndex = _tabController.index;
});
});
// Creating the scroll controller
_scrollViewController = ScrollController();
// Scrolling view to top when a new tab is selected
_tabController.addListener(() {
setState(() {
_scrollViewController
.jumpTo(_scrollViewController.position.minScrollExtent);
});
});
}
I then controlled the page content like this:
body: _pageWidgets.elementAt(_selectedPage),
I'm not 100% sure why this wasn't working, although I believe it would have something to do with the fact that initState() would only be called during the build and therefore placing the functionality inside there would mean changes wouldn't be detected. Either way, my new method, which works perfectly, is:
/// Controls the screen to display first
int _index = 0;
/// Creating a navigation key to control tab bar navigation
final _navigationKey = GlobalKey<CurvedNavigationBarState>();
Then, within the Scaffold() I show the page content like this:
body: _pageWidgets.elementAt(_index),
And finally, within the navigation bar (which is the CurvedNavigationBar() package from pub.dev) I give it a key and the index:
key: _navigationKey,
index: _index,
And this controls it perfectly, showing the correct tab.
Sub-pages of a TabBarView cannot be navigated using Navigator.
You can use TabController to go to your desired tab page after awaiting Navigator.push():
await Navigator.of(context, rootNavigator: true)
.push(
platformPageRoute(
context: context,
builder: (context) => Page1(),
),
);
tabController.animateTo(<index of tab>);
My Flutter app structure is making it hard for me to make tab transitions.
Screenshot
main.dart controls the tabs on the top, and there is a separate homeTab.dart file that controls the page below the tab: Annotated
I want to make it so that when the "Discover More" button at the bottom (which is made from homeTab.dart) is tapped, it animates to the second tab "Browse".
I added this to main.dart:
TabController tabController;
#override
void initState() {
tabController = new TabController(length: 5, vsync: this, initialIndex: 0);
getEmail();
}
changeTabToBrowse(){
tabController.animateTo(1);
}
The changeTabToBrowse() function works, but I can only call it from main.dart. I tried passing the function to homeTab.dart as a parameter, but it doesn't do anything.
I'm working on an app that gets the user to take a list of measurements. I use ListView to display a list of measurements. When the the user clicks on a list item it takes them to a new page, they enter the measurement, hit save and then in the save method I do Navigator.pop(context) back to the list. It all works but there is a usability problem.
If you tap a list tile and then use the app bar to go back it returns to the same scroll position in the ListView. If you enter some data then hit Save it returns to the top of the list. Even though I'm using Navigator.pop(context) in the save method. You can imagine returning to the top each time is pretty painful when the list of requirement measurements is quite long.
I guess is maybe its something to do with the fact that in the Save method I also update the model with which the list is built on so its kind of no longer the same list??
EDIT
I'm still not getting there and now I have an issue where the itemScrollController is not attached when I want to call it. Some code will hopefully help:
class ListContents extends StatefulWidget {
ListContents({
Key key,
}) : super(key: key);
#override
_ListContentsState createState() => _ListContentsState();
}
class _ListContentsState extends State<ListContents> {
ItemScrollController itemScrollController;
#override
void initState() {
super.initState();
itemScrollController = ItemScrollController();
}
I set up the scroll controller in init state. I set up a button to test this. I can run this when the page loads:
void jump(jumpIndex) {
itemScrollController.jumpTo(index: jumpIndex);
}
When I click that button it will jump to what ever index is passed.
I need to do this jumpTo when popping back from the previous page. I have this code in the list tiles. This loads an input page. - I was hoping to run the jumpTo method after it's popped.
onTapped: () async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
ChangeNotifierProvider<MeasureInstance>.value(
value: _instance,
child: InputOne(
index: index,
),
),
),
);
await _database.setInstance(_instance);
print(itemScrollController.isAttached);
itemScrollController.jumpTo(index: index);
},
When I pop back to the list page I see 'false' printed in the console and then an error that _jumpTo was called on null
I have a main widget that nest home page widget and setting page widget. inside setting, will have a nest pageView widget
main:
home
setting
pageView
I have a main_bloc to house some interactive data selected_page_index, to set the initialPage of the pageView when navigate to the setting page. Below are my code for the setting page:
class _SettingPageState extends State<SettingPage> {
MainBloc _mainBloc;
PageController facilityBookingPageviewController;
#override
void initState() {
super.initState();
_mainBloc = BlocProvider.of<MainBloc>(context);
print(_mainBloc.selected_page_index); ///<-- this give me the correct value
facilityBookingPageviewController = PageController(
initialPage: _mainBloc.selected_page_index,
);
}
I can print() out the correct value in the initState(), but somehow the initialPage will only get the _mainBloc.selected_page_index original preset value.
I believe I done something wrong in terms of timing. How to properly do this?
The controller.currentPage returns a double value. For example, when the page is being swiped the value goes from 1 to 2 gradually and does not instantly jump to 2. If use it jump second page directly. currentPage could use instead of initialPage if your _mainBloc.selected_page_index value is a double. Try this and let me know if it is worked for you
I am new to flutter. I have two screens. My first screen is Stateful, it consists of a list view, which is developed using FutureBuilder, when I select an item, I am pushing the app to a new screen which is also of Stateful type.
When I am moving to the new screen, the functions from previous screen are still calling and I do not know why it is happening.
This is what I have done:
InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => QuizDetail(
quizID: featuredContests.id,
),
),
);
},
child: Card(...))// InkWell Closed
Next Screen
class QuizDetail extends StatefulWidget {
final int quizID;
// In the constructor, require a QuizID.
QuizDetail({Key key, #required this.quizID}) : super(key: key);
#override
_QuizDetailState createState() => _QuizDetailState();
}
class _QuizDetailState extends State<QuizDetail> {
#override
void initState() {
super.initState();
getQuizDetail(quizID: widget.quizID);
}
It calls this function, but then also it calls the function from the previous screen which is used to fetch data from 'A' Network and the initState consists of a function which is used to fetch data from API 'B', but the data is not received from 'B', but comes from 'A' and entire process is dismissed. Can anyone help?
If you are not going to go back to the first screen after pushing the new screen, try using Navigator.pushReplacement() instead of Navigator.push().
Navigator.push() only pushes the new screen above the current one. It does not discard the screen underneath and hence if you're rebuilding the widget tree at some point in your code, all the screens in the call stack will get rebuilt.
Navigator.pushReplacement() pops the current topmost screen and pushes the new one in its place.
Also, if you plan on going back to the first screen, you can mark the onTap method as async and use await to force your program to wait till the QuizDetail screen is popped using Navigator.pop().
More info about navigation in flutter can be found here.
Example -
onTap: () async{
await Navigator.push(
//Rest of the code is same.
);
}