New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TickerProviderStateMixin dispose 报错 #461
Comments
能提供一个最小的demo么? |
https://github.com/bitterking/redex_sample @zjuwjf 麻烦大佬看一下,谢谢~~ |
@bitterking 这个问题我好像遇到过 我之前的推断原因是: flutter执行了"错误"的生命周期导致的:initState->initState->dispose, 导致dispose的是最新的controller。 暂时的解决办法: void _initState(Action action, Context<XxxxxState> ctx) {
....
ctx.extra["closeAnimation"] = clone.controller;
...
}
void _dispose(Action action, Context<XxxxState> ctx) {
(ctx.extra["closeAnimation"] as AnimationController)?.dispose();
ctx.extra["closeAnimation"] = null;
} |
Fix TickerProviderMixin’s dispose bug #461
感谢,复现了这个问题。 它是由于 flutter 不是最合理的实现导致的 @override
void dispose() {
assert(() {
if (_tickers != null) {
for (Ticker ticker in _tickers) {
if (ticker.isActive) {
throw FlutterError('$this was disposed with an active Ticker.\n'
'$runtimeType created a Ticker via its TickerProviderStateMixin, but at the time '
'dispose() was called on the mixin, that Ticker was still active. All Tickers must '
'be disposed before or after calling super.dispose(). Tickers used by AnimationControllers '
'should be disposed by calling dispose() on the AnimationController itself. '
'Otherwise, the ticker will leak.\n'
'The offending ticker was: ${ticker.toString(debugIncludeStack: true)}');
}
}
}
return true;
}());
super.dispose();
} mixin TickerProviderMixin<T> on Component<T> {
@override
_TickerProviderStfState<T> createState() => _TickerProviderStfState<T>();
}
class _TickerProviderStfState<T> extends ComponentState<T>
with TickerProviderStateMixin {} 当 Page mixin TickerProviderMixin 后 class WelcomePage extends Page<WelcomeState, Map<String, dynamic>>
with TickerProviderMixin<WelcomeState> {} 当执行到dispose 所以还没进入我们的 void _dispose(Action action, Context<WelcomeState> ctx) {
ctx.state.controller.dispose();
} 前,就先assert报错了。 合理的应该Flutter 应该这个做 @override
void dispose() {
super.dispose();
assert(() {
if (_tickers != null) {
for (Ticker ticker in _tickers) {
if (ticker.isActive) {
throw FlutterError('$this was disposed with an active Ticker.\n'
'$runtimeType created a Ticker via its TickerProviderStateMixin, but at the time '
'dispose() was called on the mixin, that Ticker was still active. All Tickers must '
'be disposed before or after calling super.dispose(). Tickers used by AnimationControllers '
'should be disposed by calling dispose() on the AnimationController itself. '
'Otherwise, the ticker will leak.\n'
'The offending ticker was: ${ticker.toString(debugIncludeStack: true)}');
}
}
}
return true;
}());
} 目前fish-redux 内部处理了这个case。 下周一会在0.2.6中发布。 目前可以依赖master分支。 |
@MMMzq @bitterking 你们的问题解决了么? |
我也是遇到了同样的问题 |
你说的问题,我并没有复现, 不断下拉刷新,执行一切符合预期。 initState 两次 是因为有两个item。 我的环境:flutter v1.9.1-hotfixes |
我的版本信息:
|
@MMMzq 非常感谢你提供的这个case。 为什么通过
会dispose最新的controller, 而非想要的dispose那份老的controller。 而通过
是正确的。 这引导我们去思考一个问题🤔: 状态的生命周期问题。 ctx.state 是一个函数,它总会根据固定的方法(connector)去获取自己最新的状态。在绝大多数场景(slots)下,它work的很好,但是在DynamicFlowAdapter场景下,情况会变的复杂。 一般而言,我们都说,组件是有生命周期的,状态是无生命周期的。 但是在@MMMzq 的case中,列表中的每一项状态,都是需要有生命周期的。 DynamicFlowAdapter的列表下 上面 @MMMzq 提到的问题就是,当为每一个组件设置一个唯一Key的时候,也就是每一个组件都不复用,那如何让刷新前的第1项的组件获得的state是刷新前的state,而让刷新后的第1项的组件获得的state是刷新后的state,尽管他们都是通过index来获取状态的。 再考虑这样的场景,当List下的某一个Item组件,发出一个action,被自己的reducer所处理,如何让列表下的组件合理的获取自己最新的state? 再考虑一个场景,当List 被插入一行数据的时候,状态和widget又是如何在被复用的? 下面解释下这个过程: /// Define itemBean how to get state with connector
///
/// [_isSimilar] return true just use newState after reducer safely
/// [_isSimilar] return false we should use cache state before reducer invoke.
/// for reducer change state immediately but sub component will refresh on next
/// frame. in this time the sub component will use cache state.
Get<Object> _subGetter(Get<List<ItemBean>> getter, int index) {
final List<ItemBean> curState = getter();
final Object subCache = curState[index].data;
return () {
final List<ItemBean> newState = getter();
/// Either all sub-components use cache or not.
if (_isSimilar(curState, newState)) {
return newState[index].data;
} else {
return subCache;
}
};
}
/// Judge [oldList] and [newList] is similar
///
/// if true: means the list size and every itemBean type & data.runtimeType
/// is equal.
bool _isSimilar(
List<ItemBean> oldList,
List<ItemBean> newList,
) {
if (oldList != newList &&
oldList?.length == newList.length &&
Collections.isNotEmpty(newList)) {
bool isEvery = true;
for (int i = 0; i < newList.length; i++) {
if (oldList[i].type != newList[i].type ||
oldList[i].data.runtimeType != newList[i].data.runtimeType) {
isEvery = false;
break;
}
}
return isEvery;
}
return false;
}
最新的 /// Define itemBean how to get state with connector
///
/// [_isSimilar] return true just use newState after reducer safely
/// [_isSimilar] return false we should use cache state before reducer invoke.
/// for reducer change state immediately but sub component will refresh on next
/// frame. in this time the sub component will use cache state.
Get<Object> _subGetter(Get<List<ItemBean>> getter, int index) {
final List<ItemBean> curState = getter();
ItemBean cacheItem = curState[index];
return () {
final List<ItemBean> newState = getter();
/// Either all sub-components use cache or not.
if (newState != null && newState.length > index) {
final ItemBean newItem = newState[index];
if (_couldReuse(cacheItem, newItem)) {
cacheItem = newItem;
}
}
return cacheItem.data;
};
}
bool _couldReuse(ItemBean beanA, ItemBean beanB) {
if (beanA.type != beanB.type) {
return false;
}
final Object dataA = beanA.data;
final Object dataB = beanB.data;
if (dataA.runtimeType != dataB.runtimeType) {
return false;
}
final Object keyA = dataA is StateKey ? dataA.key() : null;
final Object keyB = dataB is StateKey ? dataB.key() : null;
return keyA == keyB;
} 修改后, Item状态的获取是否是最新的state,还是沿用上一份state, 完全取决于组件是否被复用,和ListView 下 Widget的复用机制吻合。 同时将Key的表达,推荐,迁移到State中来,让框架能感知到它。 class SuperItemState implements Cloneable<SuperItemState>, StateKey {
AnimationController controller;
Animation<Color> animationColor;
UniqueKey uniqueKey = UniqueKey();
SuperItemState();
@override
SuperItemState clone() {
return SuperItemState()
..controller = controller
..animationColor = animationColor
..uniqueKey = uniqueKey;
}
@override
Object key() {
return uniqueKey;
}
}
/// 同时不在需要在Component组合中提供 key
class SuperItemComponent extends Component<SuperItemState>
with
PrivateReducerMixin<SuperItemState>,
TickerProviderMixin<SuperItemState> {
SuperItemComponent()
: super(
effect: buildEffect(),
reducer: buildReducer(),
view: buildView,
// key: (state) => state.key(),
);
} |
非常感谢,问题解决了😀. 但是我还有一个问题就是:为什么现在还是 |
这个是flutter内部的处理流程, 从我理解,比较符合预期,先创建再释放。 一个listview下有2个item initState item0 |
@zjuwjf 请问一下什么时候可以把这个改动发版? |
在Lifecycle.dispose 中将正在执行的animationController dispose掉,会报错
感觉TickerProviderStateMixin 的dispose会先于ComponentState 中的dispose执行
`Effect buildEffect() {
return combineEffects(<Object, Effect>{
Lifecycle.initState: _init,
Lifecycle.dispose: _dispose,
SeatsAction.volumeChanged: _onVolumeChanged,
SeatsAction.selfVolumeChanged: _onSelfVolumeChanged,
});
}
void _init(Action action, Context ctx) {
final Object tickerProvider = ctx.stfState;
ctx.state.volumeAnimationControllers = List.generate(8, (index) {
return AnimationController(
duration: Duration(milliseconds: 1000), vsync: tickerProvider);
});
}
void _dispose(Action action, Context ctx) {
ctx.state.volumeAnimationControllers.forEach((controller) {
controller.dispose();
});
}`
The text was updated successfully, but these errors were encountered: