当我需要从第一个Tab跳到第三个Tab时,我的flutter应用程序遇到了问题。我会在下面更好地解释:
[当我有一个带有3个选项卡的TabBar且在TabBarView下方以填充此选项卡时,我正在使用Flutter构建屏幕。
类似的东西:
Widget _buildReportAppbar() {
return AppBar(
backgroundColor: AppColors.colorPrimary,
elevation: 5,
leading: ...
title: ...
actions: ...
bottom: PreferredSize(
preferredSize: Size.fromHeight(58.0),
child: Padding(
padding: EdgeInsets.only(bottom: 10),
child: TabBar(
labelColor: AppColors.colorPrimary,
labelPadding: EdgeInsets.only(left: 8, right: 8),
labelStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
unselectedLabelColor: Colors.white,
unselectedLabelStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.normal),
indicatorSize: TabBarIndicatorSize.label,
isScrollable: true,
indicatorWeight: 0,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
controller: _tabController,
tabs: _tabs,
),
),
),
);
}
Widget _buildReportBody() {
return TabBarView(
controller: _tabController,
children: _provideTabScreenList(),
);
}
我的选项卡控制器的工作原理很像,在每个TabBarView内,我都有一个statefulWidget来构建报告屏幕。每个StatefulWidget都有一个api调用,可为我带来报告信息,并使用SetState(){}方法将信息显示在屏幕上。
List<Widget> _provideTabScreenList() {
_tabScreenList.clear();
_tabScreenList.add(PeriodResumeReport(filterData: currentFilter));
_tabScreenList.add(SaleStatisticReport(filterData: currentFilter));
_tabScreenList.add(SalePerDayReport(filterData: currentFilter));
return _tabScreenList;
}
class PeriodResumeReport extends StatefulWidget {
final _periodResumeReportState = _PeriodResumeReportState();
final SelectedFilter filterData;
PeriodResumeReport({Key key, @required this.filterData}) : super(key: key);
@override
_PeriodResumeReportState createState() => _periodResumeReportState;
//My tabController has a listener that detect the position change and notify the child.
void isVisible() {
_periodResumeReportState.isVisible();
}
}
//I'm using the AutomaticKeepAliveClientMixin to keep the state when i move between the
childs of the TabBarView
class _PeriodResumeReportState extends State<PeriodResumeReport>
with AutomaticKeepAliveClientMixin<PeriodResumeReport> {
var _loadingData = false;
var _apiErrorMessage = "";
var _hasResponseFromApi = false;
var _response = ...api response;
@override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}
//Load first data when the screen visible for the first time.
@override
void initState() {
super.initState();
_reloadData();
}
@override
Widget build(BuildContext context) {
super.build(context);
return ListView(
children: <Widget>[
_loadingData ? LinearProgressIndicator() : Container(),
_apiErrorMessage.isNotEmpty
? Padding(...)
: Container(),
_hasResponseFromApi ? _buildTotalOfSalesContainer() : Container(),
_hasResponseFromApi ? _buildComparativeChart() : Container(),
],
);
}
Widget _buildTotalOfSalesContainer() {
return ...;
}
Widget _buildComparativeChart() {
return ...;
}
//reload data if the user return to the screen
void isVisible() {
_reloadData();
}
Future<void> _reloadData() async {
//show ProgressBar and clear apiError
setState(() {
_loadingData = true;
_apiErrorMessage = "";
});
try {
var response = .... api call ....
....
.... handle api response
....
setState(() {
_response = response;
_loadingData = false;
_hasResponseFromApi = true;
});
}
@override
bool get wantKeepAlive => true;
}
并且使用此代码,一切正常。但是有一个问题,如果我滑动到第一个选项卡到第二个选项卡,然后再滑动到第三个选项卡,都可以。如果我在第一个选项卡中,然后单击以移至第三个选项卡而不是传递至第二个选项卡,则会发生问题。这样做,第二个选项卡崩溃,并显示以下错误:
Exception has occurred.
FlutterError (setState() called after dispose(): _SaleStatisticReportState#8c846(lifecycle
state: defunct)
This error happens if you call setState() on a State object for a widget that no longer
appears in the widget tree (e.g., whose parent widget no longer includes the widget in its.
build). This error can occur when code calls setState() from a timer or an animation
callback.
The preferred solution is to cancel the timer or stop listening to the animation in the
dispose() callback. Another solution is to check the "mounted" property of this object
before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object
is retaining a reference to this State object after it has been removed from the tree. To
avoid memory leaks, consider breaking the reference to this object during dispose().)
在此行:
@override
void setState(fn) {
if(mounted){
super.setState(fn);
}
}
我不知道出了什么问题,也许从第一个选项卡直接滑动到第三个选项卡的操作运行isVisible方法并在第二个选项卡上启动api调用,但是正如我在第三个选项卡上一样,setState() {}在第二次崩溃时。我该如何解决?
问题出在_reloadData方法中,因为您正在initstate中调用此方法,因此即使未安装屏幕,此方法也将调用setState。要解决此问题,请尝试以下解决方法。
在以下解决方案中,我确保已安装屏幕,如果已安装,则仅调用setState。
Future<void> _reloadData() async {
//show ProgressBar and clear apiError
if(mounted){
setState(() {
_loadingData = true;
_apiErrorMessage = "";
});
}
try {
var response = .... api call ....
....
.... handle api response
....
if(mounted)
setState(() {
_response = response;
_loadingData = false;
_hasResponseFromApi = true;
});
}
}
更新:
class DeleteWidget extends StatefulWidget {
@override
_DeleteWidgetState createState() => _DeleteWidgetState();
}
class _DeleteWidgetState extends State<DeleteWidget> {
int clockHours = 10;
int clockMinutes = 10;
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
backgroundColor: Colors.teal,
appBar: AppBar(
title: const Text('Tabbed AppBar'),
bottom: TabBar(
isScrollable: true,
tabs: [
Tab(text: "1"),
Tab(text: "2"),
Tab(text: "3"),
],
),
),
body: TabBarView(
children: [
Home1(),
Home2(),
Home3(),
],
),
),
);
}
}
class Home1 extends StatefulWidget {
@override
_Home1State createState() => _Home1State();
}
class _Home1State extends State<Home1> {
@override
void initState() {
super.initState();
callme();
}
@override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}
callme() async {
await Future.delayed(Duration(seconds: 1));
setState(() {});
}
@override
Widget build(BuildContext context) {
return Container();
}
}
class Home2 extends StatefulWidget {
@override
_Home2State createState() => _Home2State();
}
class _Home2State extends State<Home2>
with AutomaticKeepAliveClientMixin<Home2> {
@override
void initState() {
super.initState();
callme();
}
@override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}
callme() async {
await Future.delayed(Duration(seconds: 1));
// if (mounted) {
setState(() {});
//}
}
@override
Widget build(BuildContext context) {
super.build(context);
return Container();
}
@override
bool get wantKeepAlive => true;
}
class Home3 extends StatefulWidget {
@override
_Home3State createState() => _Home3State();
}
class _Home3State extends State<Home3>
with AutomaticKeepAliveClientMixin<Home3> {
@override
void initState() {
super.initState();
callme();
}
callme() async {
await Future.delayed(Duration(seconds: 1));
setState(() {});
}
@override
Widget build(BuildContext context) {
super.build(context);
return Container();
}
@override
bool get wantKeepAlive => true;
}
更新:
如何从父窗口小部件调用子方法。
class DeleteWidget extends StatefulWidget {
@override
_DeleteWidgetState createState() => _DeleteWidgetState();
}
class _DeleteWidgetState extends State<DeleteWidget> {
GlobalKey<Home1State> _keyChild1;
@override
void initState() {
super.initState();
_keyChild1 = GlobalKey();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RaisedButton(
child: Text("press"),
onPressed: () {
_keyChild1.currentState.callme();
},
),
Home1(
key: _keyChild1,
)
],
),
);
}
}
class Home1 extends StatefulWidget {
Home1({Key key}) : super(key: key);
@override
Home1State createState() => Home1State();
}
class Home1State extends State<Home1> {
callme() {
print("method call from parent");
setState(() {});
}
@override
Widget build(BuildContext context) {
return Container(
child: Text("Home1"),
);
}
}