chenenyu / lifecycle

Lifecycle support for Flutter widgets.
https://pub.dev/packages/lifecycle
Apache License 2.0
50 stars 6 forks source link

多个LifecycleObserver时,报:Concurrent modification during iteration: Instance(length:10) of '_GrowableList'. #20

Closed xlfdyzcs closed 2 years ago

xlfdyzcs commented 2 years ago
// main.dart
class MyAppLess extends StatelessWidget {
  const MyAppLess({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorObservers: [FlutterSmartDialog.observer, LifecycleObserver()],
      home: MyApp(),
    );
  }
}
// TabNavigator.dart
Navigator(
                  key: TabNavigator.globalKey,
                  initialRoute: path ?? '/',
                  observers: [defaultLifecycleObserver],
                  // onUnknownRoute: ,
                  onGenerateRoute: (RouteSettings routeSettings) {
                    WidgetBuilder? builder;
                    String? name = routeSettings.name;
                    dynamic arg = routeSettings.arguments;
                    switch (name) {
                      case '/monitor':
                        builder = (BuildContext ctx) => MonitorScreen();
                        break;
                    if (builder == null) return null;
                    return MaterialPageRoute(builder: builder);
                  },
                )

// 使用处
// 
class TaskRealName extends StatefulWidget {
  final DeviceEntity deviceEntity;
  final TextStyle? style;
  const TaskRealName({Key? key, required this.deviceEntity, this.style}) : super(key: key);

  @override
  _TaskRealNameState createState() => _TaskRealNameState();
}

class _TaskRealNameState extends State<TaskRealName> with LifecycleAware, LifecycleMixin {
  DeviceEntity? deviceEntity;
  List<TaskEntity> tasks = [];
  PausableTimer? _timer;

  getTaskByDeviceId() async {
    DataEntity re = await TaskApi().getTaskList(data: {
      'workspaceId': Provider.of<WorkspaceListModel>(MyApp.navigatorKey.currentContext!, listen: false).currentWorkSpace?.id,
      'entityId': deviceEntity?.id,
    });
    if (re.hasData) {
      tasks = re.data;
      if (mounted) {
        setState(() {

        });
      }
    }
  }

  loadTaskPoly() {
    try {
      _timer = PausableTimer(Duration(milliseconds: 1500), () {
        getTaskByDeviceId();
        resumePoly();
      });
      _timer?.start();
    } catch (e) {
      cancelPoly();
    }
  }

  cancelPoly() {
    _timer?.cancel();
    _timer = null;
  }

  resumePoly() {
    _timer?.reset();
    _timer?.start();
  }

  pausedPoly() {
    _timer?.pause();
  }

  @override
  void initState() {
    deviceEntity = widget.deviceEntity;
    loadTaskPoly();
    super.initState();
  }

  @override
  void onLifecycleEvent(LifecycleEvent event) {
    debugPrint(event.name);
    if (event == LifecycleEvent.visible) {
      resumePoly();
    } else if (event == LifecycleEvent.invisible) {
      pausedPoly();
    }
  }

  @override
  void dispose() {
    cancelPoly();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    String taskName = '';
    if (tasks.isNotEmpty) {
      taskName = tasks.first.name ?? '';
    }
    return Text(taskName, style: widget.style ?? TextStyle(fontSize: 20.sp, color: ColorHelper.hintColor),);
  }
}
chenenyu commented 2 years ago

你的TabNavigator是在哪用的

xlfdyzcs commented 2 years ago

你的TabNavigator是在哪用的

image image 在LoginPage中: Navigator.pushNamedAndRemoveUntil(context, HomePage.routeName, (route) => false);

chenenyu commented 2 years ago

方便提供一个能复现的最简单demo吗? 我看你的使用场景还是有点复杂的

feimenggo commented 2 years ago

方便提供一个能复现的最简单demo吗? 我看你的使用场景还是有点复杂的

我也遇到这个问题了,在使用getx的嵌套导航就会出现。 嵌套导航:https://github.com/jonataslaw/getx/blob/master/documentation/zh_CN/route_management.md#%E5%B5%8C%E5%A5%97%E5%AF%BC%E8%88%AA

feimenggo commented 2 years ago

解决办法就是避免在遍历集合的时候,执行移除元素的操作,或者改成倒序遍历即可。

https://github.com/chenenyu/lifecycle/blob/3253178465f418b8d8fa7af605e29549050ee9a3/lib/src/lifecycle_observer.dart#L25-L32

改之前:

    for (LifecycleObserver observer in _cache) {
      if (observer.navigator == null) {
        WidgetsBinding.instance?.removeObserver(observer);
        _cache.remove(observer);
      } else if (observer.navigator == navigator) {
        return observer;
      }
    }

改之后:

    LifecycleObserver? targetObserver;
    for (var i = _cache.length - 1; i >= 0; i--) {
      LifecycleObserver observer = _cache[i];
      if (observer.navigator == null) {
        WidgetsBinding.instance?.removeObserver(observer);
        _cache.removeAt(i);
      } else if (observer.navigator == navigator) {
        targetObserver = observer;
      }
    }
    if (targetObserver != null) return targetObserver;
feimenggo commented 2 years ago

另外,使用嵌套导航会有个问题,就是嵌套导航里的LifecycleWrapper,无法感知到外部导航的生命周期变化。

chenenyu commented 2 years ago

另外,使用嵌套导航会有个问题,就是嵌套导航里的LifecycleWrapper,无法感知到外部导航的生命周期变化。

@feimenggo 这个问题是有的,因为每个导航都有一份自己的路由栈,后面想想怎么优化

chenenyu commented 2 years ago

@feimenggo @xlfdyzcs 试下0.4.4,解决了Concurrent modification during iteration问题

chenenyu commented 2 years ago

@feimenggo 后面会出个beta版本解决你说的内部的LifecycleWrapper无法感知外部导航生命周期变化的问题,如果有时间的话可以帮忙测试下。

feimenggo commented 2 years ago

@feimenggo @xlfdyzcs 试下0.4.4,解决了Concurrent modification during iteration问题

我试试

feimenggo commented 2 years ago

@feimenggo 后面会出个beta版本解决你说的内部的LifecycleWrapper无法感知外部导航生命周期变化的问题,如果有时间的话可以帮忙测试下。

好的👌🏻

feimenggo commented 2 years ago

@feimenggo @xlfdyzcs 试下0.4.4,解决了Concurrent modification during iteration问题

改成倒序遍历能解决Concurrent modification during iteration的问题。 但会产生新的问题,创建新的LifecycleObserver会执行 _cache.add(this); 代码,把最新的对象添加到_cache列表的末尾,又因为倒序的原因,紧接着 LifecycleObserver.internalGet() 执行就会直接到这个分支

else if (observer.navigator == navigator){
   // 因为_cache.add(this),在最后一个,所以执行到这个if分支,直接return了
   return observer; 
}

会导致_cache前面的observer.navigator == null的项没有判断并从_cache集合里移除

if (observer.navigator == null) { 
         WidgetsBinding.instance?.removeObserver(observer); 
         _cache.removeAt(i); 
}

所以当时我是这么改的:

    LifecycleObserver? targetObserver;
    for (var i = _cache.length - 1; i >= 0; i--) {
      LifecycleObserver observer = _cache[i];
      if (observer.navigator == null) {
        WidgetsBinding.instance?.removeObserver(observer);
        _cache.removeAt(i);
      } else if (observer.navigator == navigator) {
        targetObserver = observer;
      }
    }
    if (targetObserver != null) return targetObserver;

https://github.com/chenenyu/lifecycle/blob/1289c9d09b9c23b7df8129bd59654f4b9852bdc3/lib/src/lifecycle_observer.dart#L16-L36

chenenyu commented 2 years ago

@feimenggo 的确是有缓存在部分场景下不能及时清除的问题,不过不影响功能,后面会一起优化这个点

chenenyu commented 2 years ago

@feimenggo 试一下把这个变量定义成static final的,看看多个navigator的有没有什么问题 https://github.com/chenenyu/lifecycle/blob/1289c9d09b9c23b7df8129bd59654f4b9852bdc3/lib/src/lifecycle_observer.dart#L11

feimenggo commented 2 years ago

@feimenggo 试一下把这个变量定义成static final的,看看多个navigator的有没有什么问题

https://github.com/chenenyu/lifecycle/blob/1289c9d09b9c23b7df8129bd59654f4b9852bdc3/lib/src/lifecycle_observer.dart#L11

没有效果呢

xlfdyzcs commented 2 years ago

@feimenggo @xlfdyzcs 试下0.4.4,解决了Concurrent modification during iteration问题

改成倒序遍历能解决Concurrent modification during iteration的问题。 但会产生新的问题,创建新的LifecycleObserver会执行 _cache.add(this); 代码,把最新的对象添加到_cache列表的末尾,又因为倒序的原因,紧接着 LifecycleObserver.internalGet() 执行就会直接到这个分支

else if (observer.navigator == navigator){
   // 因为_cache.add(this),在最后一个,所以执行到这个if分支,直接return了
   return observer; 
}

会导致_cache前面的observer.navigator == null的项没有判断并从_cache集合里移除

if (observer.navigator == null) { 
         WidgetsBinding.instance?.removeObserver(observer); 
         _cache.removeAt(i); 
}

所以当时我是这么改的:

    LifecycleObserver? targetObserver;
    for (var i = _cache.length - 1; i >= 0; i--) {
      LifecycleObserver observer = _cache[i];
      if (observer.navigator == null) {
        WidgetsBinding.instance?.removeObserver(observer);
        _cache.removeAt(i);
      } else if (observer.navigator == navigator) {
        targetObserver = observer;
      }
    }
    if (targetObserver != null) return targetObserver;

https://github.com/chenenyu/lifecycle/blob/1289c9d09b9c23b7df8129bd59654f4b9852bdc3/lib/src/lifecycle_observer.dart#L16-L36

能fork后,发起merge request么?让@chenenyu大佬合并一下

feimenggo commented 2 years ago

新的0.4.4 已经不会报:Concurrent modification during iteration的错误了呢

chenenyu commented 2 years ago

另外,使用嵌套导航会有个问题,就是嵌套导航里的LifecycleWrapper,无法感知到外部导航的生命周期变化。

@feimenggo 最近太忙了,能帮忙提供一个最简化的demo吗?