Want to imitate the Android fragment switching effect, dynamically update the FrameLayout layout through the fragment. However, when using provider in a project, an exception occurs when calling element. Updata (covariant widget, newwidget).
Exception caught==========
The following assertion was thrown while handling a gesture:
'package:flutter/src/widgets/framework.dart': Failed assertion: line 3439 pos 7: '_lifecycleState == _ElementLifecycle.active
&& widget != null
&& newWidget != null
&& newWidget != widget
&& depth != null
&& Widget.canUpdate(widget, newWidget)': is not true.
Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
https://github.com/flutter/flutter/issues/new?template=2_bug.md
When the exception was thrown, this was the stack:
#2 Element.update (package:flutter/src/widgets/framework.dart:3439:7)
#3 StatefulElement.update (package:flutter/src/widgets/framework.dart:4751:11)
#4 _HomePageState.itemWidget.<anonymous closure>.<anonymous closure> (package:flutter_app/a/ui/page/home_page.dart:192:25)
#5 BuildOwner.lockState (package:flutter/src/widgets/framework.dart:2473:15)
#6 _HomePageState.itemWidget.<anonymous closure> (package:flutter_app/a/ui/page/home_page.dart:191:27)
...
Handler: "onTap"
Recognizer: TapGestureRecognizer#2d601
debugOwner: GestureDetector
state: ready
won arena
finalPosition: Offset(120.0, 195.0)
finalLocalPosition: Offset(40.0, 33.5)
button: 1
sent tap down
The simplified code is as follows (can be run directly). Click the top button to switch the widget
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
@override
State createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
TabController tabController;
Widget currentWidget;
var pageMap = new Map();
@override
Widget build(BuildContext context) {
return ProviderWidget(
model: SubjectModel(),
onModelReady: (subjectModel) {
subjectModel.curIndexTitle = 'title1';
subjectModel.getSubjects();
currentWidget = LinkListPage(subjectModel.curSubject);
pageMap['title1'] = currentWidget;
},
builder: (context, subjectModel, child) {
return DefaultTabController(
length: subjectModel.subjectList.length,
initialIndex: 0,
child: Builder(
builder: (context) {
if (tabController == null) {
tabController = DefaultTabController.of(context);
tabController.addListener(() {});
}
return Scaffold(
appBar: AppBar(
title: Row(
children: [
Text("${subjectModel.curIndexTitle}"),
RaisedButton(
child: Text('title1'),
onPressed: () {
changeWidget(
subjectModel, subjectModel.subjectList[0]);
}),
RaisedButton(
child: Text('title2'),
onPressed: () {
changeWidget(
subjectModel, subjectModel.subjectList[1]);
}),
RaisedButton(
child: Text('title3'),
onPressed: () {
changeWidget(
subjectModel, subjectModel.subjectList[2]);
}),
],
),
),
body: Center(
child: null != currentWidget
? currentWidget
: CircularProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
)),
);
},
));
});
}
changeWidget(SubjectModel model, Subject subject) {
if (model.curIndexTitle == subject.nameCn) return;
model.changeSubject(subject);
Element e = findChild(context as Element, currentWidget);
if (e != null) {
if (pageMap.containsKey(subject.nameCn)) {
currentWidget = pageMap[subject.nameCn];
} else {
currentWidget = SubjectLinksPage(subject);
}
e.owner.lockState(() {
e.update(currentWidget);
});
}
}
static Element findChild(Element e, Widget w) {
Element child;
void visit(Element element) {
if (w == element.widget)
child = element;
else
element.visitChildren(visit);
}
visit(e);
return child;
}
}
enum SubjectType { HOT, NEW }
//subject_page
class SubjectLinksPage extends StatefulWidget {
final Subject _subject;
SubjectLinksPage(this._subject);
@override
_SubjectLinksPageState createState() => _SubjectLinksPageState();
}
class _SubjectLinksPageState extends State<SubjectLinksPage>
with SingleTickerProviderStateMixin {
TabController _tabController;
final arr = ["hot", "new"];
@override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: arr.length);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Container(
child: Container(
child: Center(
child: _appBarView(),
),
),
),
),
body: TabBarView(
controller: _tabController,
children: arr.map((e) {
print(widget._subject.nameCn);
return Center(
child: LinkListPage(widget._subject,
subjectType: e == arr[0] ? SubjectType.HOT : SubjectType.NEW),
);
}).toList(),
),
);
}
Widget _appBarView() {
return TabBar(
tabs: arr.map((e) {
return Tab(
child: Text(e),
);
}).toList(),
controller: _tabController,
indicatorColor: Colors.white,
indicatorSize: TabBarIndicatorSize.tab,
isScrollable: true,
labelColor: Colors.white,
unselectedLabelColor: Colors.grey[400],
indicatorWeight: 4.0,
labelStyle: TextStyle(height: 2));
}
}
//link_page
class LinkListPage extends StatefulWidget {
final Subject subject;
final SubjectType subjectType;
LinkListPage(this.subject, {this.subjectType});
@override
State createState() => _LinkListPageState();
}
class _LinkListPageState extends State<LinkListPage> {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
return ProviderWidget(
model: LinksModel(widget.subject),
onModelReady: (model) {},
builder: (context, linksModel, child) {
return Text("${widget.subject.nameCn}----${widget.subjectType}");
});
}
}
//model
class SubjectModel with ChangeNotifier {
Subject curSubject;
String curIndexTitle;
List subjectList = [];
changeSubject(Subject value) async {
curSubject = value;
curIndexTitle = value.nameCn;
notifyListeners();
}
getSubjects() {
try {
subjectList.add(new Subject.init(0, "hot", "title1", "hot"));
subjectList.add(new Subject.init(1, "news", "title2", "r/news"));
subjectList.add(new Subject.init(2, "scoff", "title3", "r/scoff"));
curSubject = subjectList[0];
} catch (e, s) {}
notifyListeners();
}
}
class LinksModel with ChangeNotifier {
final Subject subject;
LinksModel(this.subject);
}
//provider
class ProviderWidget<T extends ChangeNotifier> extends StatefulWidget {
final ValueWidgetBuilder<T> builder;
final T model;
final Widget child;
final Function(T model) onModelReady;
final bool autoDispose;
ProviderWidget(
{Key key,
@required this.model,
@required this.builder,
this.child,
this.onModelReady,
this.autoDispose: true})
: super(key: key);
@override
_ProviderWidgetState<T> createState() => _ProviderWidgetState<T>();
}
class _ProviderWidgetState<T extends ChangeNotifier>
extends State<ProviderWidget<T>> {
T model;
@override
void initState() {
model = widget.model;
widget.onModelReady?.call(model);
super.initState();
}
@override
void dispose() {
if (widget.autoDispose) model.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<T>.value(
value: model,
child: Consumer<T>(
builder: widget.builder,
child: widget.child,
),
);
}
}
class Subject {
int id;
String name;
String uri;
String nameCn;
Subject.init(
this.id,
this.name,
this.nameCn,
this.uri,
);
}