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> { ...@@ -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( return new StockList(
keySalt: tab,
stocks: stocks.toList(), stocks: stocks.toList(),
onAction: _buyStock, onAction: _buyStock,
onOpen: (Stock stock, Key arrowKey) { onOpen: (Stock stock, Key arrowKey) {
...@@ -214,6 +215,13 @@ class StockHomeState extends State<StockHome> { ...@@ -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 const List<String> portfolioSymbols = const <String>["AAPL","FIZZ", "FIVE", "FLAT", "ZINC", "ZNGA"];
static GlobalKey searchFieldKey = new GlobalKey(); static GlobalKey searchFieldKey = new GlobalKey();
...@@ -259,13 +267,6 @@ class StockHomeState extends State<StockHome> { ...@@ -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; double _viewWidth = 100.0;
void _handleSizeChanged(Size newSize) { void _handleSizeChanged(Size newSize) {
setState(() { setState(() {
...@@ -282,14 +283,14 @@ class StockHomeState extends State<StockHome> { ...@@ -282,14 +283,14 @@ class StockHomeState extends State<StockHome> {
onSizeChanged: _handleSizeChanged, onSizeChanged: _handleSizeChanged,
child: new TabBarView<StockHomeTab>( child: new TabBarView<StockHomeTab>(
selection: _tabBarSelection, selection: _tabBarSelection,
items: [StockHomeTab.market, StockHomeTab.portfolio], items: <StockHomeTab>[StockHomeTab.market, StockHomeTab.portfolio],
itemExtent: _viewWidth, itemExtent: _viewWidth,
itemBuilder: (BuildContext context, StockHomeTab tab, _) { itemBuilder: (BuildContext context, StockHomeTab tab, _) {
switch (tab) { switch (tab) {
case StockHomeTab.market: case StockHomeTab.market:
return buildStockTab(context, tab, config.symbols); return _buildStockTab(context, tab, config.symbols);
case StockHomeTab.portfolio: case StockHomeTab.portfolio:
return buildStockTab(context, tab, portfolioSymbols); return _buildStockTab(context, tab, portfolioSymbols);
default: default:
assert(false); assert(false);
} }
......
...@@ -5,8 +5,9 @@ ...@@ -5,8 +5,9 @@
part of stocks; part of stocks;
class StockList extends StatelessComponent { 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 List<Stock> stocks;
final StockRowActionCallback onOpen; final StockRowActionCallback onOpen;
final StockRowActionCallback onShow; final StockRowActionCallback onShow;
...@@ -18,6 +19,7 @@ class StockList extends StatelessComponent { ...@@ -18,6 +19,7 @@ class StockList extends StatelessComponent {
itemExtent: StockRow.kHeight, itemExtent: StockRow.kHeight,
itemBuilder: (BuildContext context, Stock stock, int index) { itemBuilder: (BuildContext context, Stock stock, int index) {
return new StockRow( return new StockRow(
keySalt: keySalt,
stock: stock, stock: stock,
onPressed: onOpen, onPressed: onOpen,
onDoubleTap: onShow, onDoubleTap: onShow,
......
...@@ -7,18 +7,22 @@ part of stocks; ...@@ -7,18 +7,22 @@ part of stocks;
enum StockRowPartKind { arrow } enum StockRowPartKind { arrow }
class StockRowPartKey extends Key { 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 Stock stock;
final StockRowPartKind part; final StockRowPartKind part;
bool operator ==(dynamic other) { bool operator ==(dynamic other) {
if (other is! StockRowPartKey) if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false; return false;
final StockRowPartKey typedOther = other; final StockRowPartKey typedOther = other;
return stock == typedOther.stock && return keySalt == typedOther.keySalt
part == typedOther.part; && stock == typedOther.stock
&& part == typedOther.part;
} }
int get hashCode => 37 * (37 * (373) + identityHashCode(stock)) + identityHashCode(part); int get hashCode => 37 * (37 * (37 * (373) + identityHashCode(keySalt)) + identityHashCode(stock)) + identityHashCode(part);
String toString() => '[StockRowPartKey ${stock.symbol}:${part.toString().split(".")[1]})]'; String toString() => '[$runtimeType ${keySalt.toString().split(".")[1]}:${stock.symbol}:${part.toString().split(".")[1]}]';
} }
typedef void StockRowActionCallback(Stock stock, Key arrowKey); typedef void StockRowActionCallback(Stock stock, Key arrowKey);
...@@ -26,11 +30,12 @@ typedef void StockRowActionCallback(Stock stock, Key arrowKey); ...@@ -26,11 +30,12 @@ typedef void StockRowActionCallback(Stock stock, Key arrowKey);
class StockRow extends StatelessComponent { class StockRow extends StatelessComponent {
StockRow({ StockRow({
Stock stock, Stock stock,
Object keySalt,
this.onPressed, this.onPressed,
this.onDoubleTap, this.onDoubleTap,
this.onLongPressed this.onLongPressed
}) : this.stock = stock, }) : this.stock = stock,
_arrowKey = new StockRowPartKey(stock, StockRowPartKind.arrow), _arrowKey = new StockRowPartKey(keySalt, stock, StockRowPartKind.arrow),
super(key: new ObjectKey(stock)); super(key: new ObjectKey(stock));
final Stock stock; final Stock stock;
......
...@@ -88,6 +88,8 @@ class HeroController extends NavigatorObserver { ...@@ -88,6 +88,8 @@ class HeroController extends NavigatorObserver {
} }
Set<Key> _getMostValuableKeys() { Set<Key> _getMostValuableKeys() {
assert(_from != null);
assert(_to != null);
Set<Key> result = new Set<Key>(); Set<Key> result = new Set<Key>();
if (_from.settings.mostValuableKeys != null) if (_from.settings.mostValuableKeys != null)
result.addAll(_from.settings.mostValuableKeys); result.addAll(_from.settings.mostValuableKeys);
......
...@@ -107,7 +107,19 @@ class Hero extends StatefulComponent { ...@@ -107,7 +107,19 @@ class Hero extends StatefulComponent {
assert(tag != null); assert(tag != null);
Key key = hero.widget.key; Key key = hero.widget.key;
final Map<Key, HeroState> tagHeroes = heroes.putIfAbsent(tag, () => <Key, HeroState>{}); 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; tagHeroes[key] = hero.state;
} }
element.visitChildren(visitor); 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