Flutter Provider calls Element.updata (widget) to report an error

Viewed 47

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,
  );
}

0 Answers
Related