I am making a simple tab bar based app with Flutter. It uses CupertinoTabScaffold
and CupertinoTabBar
. Each CupertinoTabView
has its own WebView
.
In the first TabView
, a button is placed that if clicked the selected tab will be changed to the next one to load a CupertinoTabView
containing a WebView
widget, then navigate to the certain website URL (note that the URL is not written on WebView:initialURL
, but retrieved from the first tab).
The problem is that when clicking the button, while the selected tab is changed, WebView
does not load a specific URL because the WebView
controller is not assigned.
How to make the app 'wait' for the new CupertinoTabView
widget to be fully loaded?
If I run the app, the error is the following:
Unhandled Exception: NoSuchMethodError: The method 'navigateTo' was called on null.
This seems because when clicking the button to execute the following code, before the line tabController.index = tabIndex
will take effect, keyWebPage.curentState.navigateTo
(which is null yet) is called.
tabController.index = tabIndex;
keyWebPage.currentState.navigateTo(url); //executed 'before' the new TabView initialization
I suspect the reason for this problem, but couldn't figure out how to solve it.
main.dart
final GlobalKey<PageWebState> keyWebPage = GlobalKey();
class _MyHomePageState extends State<MyHomePage> {
final CupertinoTabController tabController = CupertinoTabController();
int _currentTabIndex = 0;
WebPage pageWeb;
@override
void initState() {
super.initState();
pageWeb = new PageWeb(
this,
key: keyWebPage,
);
}
void navigateTab(int tabIndex, String url) {
setState(() {
_currentTabIndex = tabIndex;
tabController.index = tabIndex;
// HERE is the error occurring part
keyWebPage.currentState.navigateTo(url);
});
}
@override
Widget build(BuildContext context) {
final Widget scaffold = CupertinoTabScaffold(
controller: tabController,
tabBar: CupertinoTabBar(
currentIndex: _currentTabIndex,
onTap: (index) {
setState(() {
_currentTabIndex = index;
});
},
items: const <BottomNavigationBarItem>[
const BottomNavigationBarItem(icon: const Icon(Icons.home), title: Text('Start')),
const BottomNavigationBarItem(icon: const Icon(Icons.web), title: Text('Webpage')),
],
),
tabBuilder: (context, index) {
CupertinoTabView _viewWidget;
switch (index) {
case 0:
_viewWidget = CupertinoTabView(
builder: (context) {
return Center(
child: CupertinoButton(
child: Text('Navigate'),
onPressed: () {
navigateTab(1, 'https://stackoverflow.com/');
},
),
);
},
);
break;
case 1:
_viewWidget = CupertinoTabView(
builder: (context) {
return pageWeb;
},
);
break;
}
return _viewWidget;
},
);
return scaffold;
}
}
WebPage.dart
final Completer<WebViewController> _controller = Completer<WebViewController>();
class PageWeb extends StatefulWidget {
final delegate;
final ObjectKey key;
PageWeb(this.delegate, this.key);
@override
PageWebState createState() {
return PageWebState();
}
}
void navigateTo(String url) { //Function that will be called on main.dart
controller.future.then((controller) => controller.loadUrl(url));
}
class PageWebState extends State<PageWeb> {
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text("Test"),
),
child: SafeArea(
child: Container(
child: new WebView(
key: widget.key,
initialUrl: "https://www.google.com/",
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
))));
}
}
Please note that using constructor to passing variables is not in consideration because the URL will be changed and the clicking the button can be more than once.