Commit d5e072c3 authored by Ian Hickson's avatar Ian Hickson

Fix crash when going back in stocks app.

Heroes with the same tag have to have keys that are unique across the
entire subtree. Since we can now show both stock lists, this means we
have to include the tab in the heroes' keys.

Fixes #668.
parent 38aa83b0
......@@ -199,8 +199,9 @@ class StockHomeState extends State<StockHome> {
));
}
Widget buildStockList(BuildContext context, Iterable<Stock> stocks) {
Widget _buildStockList(BuildContext context, Iterable<Stock> stocks, StockHomeTab tab) {
return new StockList(
keySalt: tab,
stocks: stocks.toList(),
onAction: _buyStock,
onOpen: (Stock stock, Key arrowKey) {
......@@ -214,6 +215,13 @@ class StockHomeState extends State<StockHome> {
);
}
Widget _buildStockTab(BuildContext context, StockHomeTab tab, List<String> stockSymbols) {
return new Container(
key: new ValueKey<StockHomeTab>(tab),
child: _buildStockList(context, _filterBySearchQuery(_getStockList(stockSymbols)).toList(), tab)
);
}
static const List<String> portfolioSymbols = const <String>["AAPL","FIZZ", "FIVE", "FLAT", "ZINC", "ZNGA"];
static GlobalKey searchFieldKey = new GlobalKey();
......@@ -259,13 +267,6 @@ class StockHomeState extends State<StockHome> {
);
}
Widget buildStockTab(BuildContext context, StockHomeTab tab, List<String> stockSymbols) {
return new Container(
key: new ValueKey<StockHomeTab>(tab),
child: buildStockList(context, _filterBySearchQuery(_getStockList(stockSymbols)).toList())
);
}
double _viewWidth = 100.0;
void _handleSizeChanged(Size newSize) {
setState(() {
......@@ -282,14 +283,14 @@ class StockHomeState extends State<StockHome> {
onSizeChanged: _handleSizeChanged,
child: new TabBarView<StockHomeTab>(
selection: _tabBarSelection,
items: [StockHomeTab.market, StockHomeTab.portfolio],
items: <StockHomeTab>[StockHomeTab.market, StockHomeTab.portfolio],
itemExtent: _viewWidth,
itemBuilder: (BuildContext context, StockHomeTab tab, _) {
switch (tab) {
case StockHomeTab.market:
return buildStockTab(context, tab, config.symbols);
return _buildStockTab(context, tab, config.symbols);
case StockHomeTab.portfolio:
return buildStockTab(context, tab, portfolioSymbols);
return _buildStockTab(context, tab, portfolioSymbols);
default:
assert(false);
}
......
......@@ -5,8 +5,9 @@
part of stocks;
class StockList extends StatelessComponent {
StockList({ Key key, this.stocks, this.onOpen, this.onShow, this.onAction }) : super(key: key);
StockList({ Key key, this.keySalt, this.stocks, this.onOpen, this.onShow, this.onAction }) : super(key: key);
final Object keySalt;
final List<Stock> stocks;
final StockRowActionCallback onOpen;
final StockRowActionCallback onShow;
......@@ -18,6 +19,7 @@ class StockList extends StatelessComponent {
itemExtent: StockRow.kHeight,
itemBuilder: (BuildContext context, Stock stock, int index) {
return new StockRow(
keySalt: keySalt,
stock: stock,
onPressed: onOpen,
onDoubleTap: onShow,
......
......@@ -7,18 +7,22 @@ part of stocks;
enum StockRowPartKind { arrow }
class StockRowPartKey extends Key {
const StockRowPartKey(this.stock, this.part) : super.constructor();
const StockRowPartKey(this.keySalt, this.stock, this.part) : super.constructor();
final Object keySalt;
final Stock stock;
final StockRowPartKind part;
bool operator ==(dynamic other) {
if (other is! StockRowPartKey)
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
final StockRowPartKey typedOther = other;
return stock == typedOther.stock &&
part == typedOther.part;
return keySalt == typedOther.keySalt
&& stock == typedOther.stock
&& part == typedOther.part;
}
int get hashCode => 37 * (37 * (373) + identityHashCode(stock)) + identityHashCode(part);
String toString() => '[StockRowPartKey ${stock.symbol}:${part.toString().split(".")[1]})]';
int get hashCode => 37 * (37 * (37 * (373) + identityHashCode(keySalt)) + identityHashCode(stock)) + identityHashCode(part);
String toString() => '[$runtimeType ${keySalt.toString().split(".")[1]}:${stock.symbol}:${part.toString().split(".")[1]}]';
}
typedef void StockRowActionCallback(Stock stock, Key arrowKey);
......@@ -26,11 +30,12 @@ typedef void StockRowActionCallback(Stock stock, Key arrowKey);
class StockRow extends StatelessComponent {
StockRow({
Stock stock,
Object keySalt,
this.onPressed,
this.onDoubleTap,
this.onLongPressed
}) : this.stock = stock,
_arrowKey = new StockRowPartKey(stock, StockRowPartKind.arrow),
_arrowKey = new StockRowPartKey(keySalt, stock, StockRowPartKind.arrow),
super(key: new ObjectKey(stock));
final Stock stock;
......
......@@ -88,6 +88,8 @@ class HeroController extends NavigatorObserver {
}
Set<Key> _getMostValuableKeys() {
assert(_from != null);
assert(_to != null);
Set<Key> result = new Set<Key>();
if (_from.settings.mostValuableKeys != null)
result.addAll(_from.settings.mostValuableKeys);
......
......@@ -107,7 +107,19 @@ class Hero extends StatefulComponent {
assert(tag != null);
Key key = hero.widget.key;
final Map<Key, HeroState> tagHeroes = heroes.putIfAbsent(tag, () => <Key, HeroState>{});
assert(!tagHeroes.containsKey(key));
assert(() {
if (tagHeroes.containsKey(key)) {
debugPrint('Tag: $tag Key: $key');
assert(() {
'There are multiple heroes that share the same key within the same subtree. '
'Within each subtree for which heroes are to be animated (typically a PageRoute subtree), '
'either each Hero must have a unique tag, or, all the heroes with a particular tag must '
'have different keys. The relevant tag and key were dumped above. ';
return false;
});
}
return true;
});
tagHeroes[key] = hero.state;
}
element.visitChildren(visitor);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment