-
Notifications
You must be signed in to change notification settings - Fork 29.1k
stock app rewrite using router #63424
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
Conversation
c5dd346
to
1c91069
Compare
void main() { | ||
runApp(StocksApp()); | ||
} | ||
void main() => runApp(const StocksApp()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the benefit of having this in a separate file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, i see
I think this code would benefit from a further separation of the "app state" from the widget code. Right now, it's sort of entangled (a result of the original architecture, my bad), e.g. with the StocksApp widget having static methods, and the data being in the State of that widget, and so on. Does that make sense? |
Thanks for review @Hixie I almost forgot about this PR, i try my best to separate out the stock configuration can you another look? |
I think there is a problem with showing the current location in the browser address bar due to Navigator 2.0 change. The The router does update the application correctly if I change the URL but then it reset it back to On the subject but a different topic - is it possible/plan to have Flutter web app use |
The web browser does not work until #64494 is fixed the #/ is due to our location strategy. There is a pr to change that flutter/engine#17829 |
@goderbauer @Hixie This is ready for another look |
if (configuration is StockHomePath) | ||
return const RouteInformation(location: _kHomePageLocation); | ||
if (configuration is StockSymbolPath) | ||
return RouteInformation(location: '$_kStockPageLocation?symbol=${configuration.symbol}'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the symbol needs escaping, otherwise you have (in principle, anyway) an injection vulnerability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(i'm assuming the constants don't need escaping because they're designed to not have anything outside of the local appropriate alphabet)
} | ||
|
||
ThemeData get theme { | ||
switch (_configuration.stockMode) { | ||
ThemeData geThemeFromConfiguration(StockConfiguration configuration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be private?
} | ||
|
||
bool _handlePopPage(Route<dynamic> route, dynamic result) { | ||
// _handlePopPage should not be called on the home page because the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"should not" or "will not"? (i.e. does the PopNavigatorRouterDelegateMixin make sure that this is never called?)
|
||
@override | ||
Widget build(BuildContext context) { | ||
assert(routerState.debugInitialized); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of introducing this extra "debugInitialized" field, could this just be
assert(routerState.debugInitialized); | |
assert(reouterState.routePath != null); |
if (widget.updater != null) | ||
widget.updater(widget.configuration.copyWith(stockMode: value)); | ||
final StockState state = StockStateScope.of(context); | ||
state.updateConfiguration(StockStateScope.configurationOf(context).copyWith(stockMode: value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This pattern seems pretty common. Maybe StockState should have a method like this:
state.updateConfiguration(stockMode: value);
bool get debugInitialized { | ||
bool answer; | ||
assert((){ | ||
answer = routePath != null; | ||
return true; | ||
}()); | ||
return answer; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment above, I think we can just remove this to simplify things?
static StockData stockDataOf(BuildContext context) { | ||
return context.findAncestorWidgetOfExactType<StockStateScope>().stocks; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This only works in this toy example because the stock data never changes, right? If it would, you'd want a dependency to rebuild whenever the data changes, no?
notifyListeners(); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it make sense to replace the routePath getter here with something like:
bool get showSettings => ...; // if true, we show the settings page; otherwise:
String get symbol => ...; // if non-null, we show the symbol information page; otherwise, show home page.
It seems like it would be simpler (far fewer classes, for example). You'd have to have a function to set the current mode to replace the set routePath
setter, maybe something like;
void updateState({ bool showSettings, String symbol }) {
assert(!showSettings || symbol == null);
// ...set private fields that affect the getters above...
notifyListeners;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is app is an example for developer to use router, i think the current approach of using the route class is better. if we convolute route classes into only a few settings, thing may become out of control if developer have more route.
For example, navigating between page if we use these property will be
showSettings= true; symbol =null // go to setting page
showSettings = false; symbol ='AAPL' // got to symbol page
showSettings= false; symbol =null // go to home page
vs
routePath = StockSettingPath() // go to setting page
routePath = StockSymbolPath(symbol: 'AAPL') // go to symbol page
routePath = StockHomePath() // go to Home page
I think the latter is more descriptive and easier to maintain
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is almost like we are asking user to figure out
A = 1100
B = 0110
C = 0101
D = 1001
now X xor Y = 0011 , figure what is X and Y
enum StockMode { optimistic, pessimistic } | ||
enum BackupMode { enabled, disabled } | ||
|
||
class StockConfiguration { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could leave this in stock_types.dart to make the patch simpler; maybe I'm missing a good reason to move it here though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this part of the appstate? wouldn't it be good to group all the app state together in one file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I separated out the router state to be in the routing/router_state.dart
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to keeping this in the old file for now.
Sorry for delay in reviewing this. |
f763d4a
to
fb2a57c
Compare
Gold has detected about 3 untriaged digest(s) on patchset 5. |
2589b82
to
fae77c7
Compare
I added the following change separate out the router related class into routing/ ready for another look |
class _RouterConfiguration { | ||
_RouterConfiguration(this.path, this.browserState); | ||
StockRoutePath path; | ||
Map<String, dynamic> browserState; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, it's to store the active tab. Shouldn't that be a field on the appropriate StockRoutePath instead of just a generic Map<String, dynamic>?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this information is part of StockHomePath, it will be problematic when user currently on StockSymbolPath and go back to StockHomePath using Navigator.pop. In the _handlePopPage, we don't know what is the current tab for StockHomePath.
This makes me think, anything that does not reflect to the URL (i.e. browser state) should be global to all the routes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @goderbauer that this is suboptimal. I don't think we should be encouraging people to use open maps in this way. The whole point of the router API is to make the data structured.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But how do we solve the issue when user uses navigator.pop to go back from stocksymbolpath to stockhomepath?
If the tab index is only store of StockHomePath, in the _handlePopPage I only have the information about stocksymbolpath and i do not know how to set the tab index in StockHomePath.
We can probably make the StockHomePath to take optional tab index. If it is not provided, it will use whatever in its current state. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
making the browser state global is a a compromise between imperative API vs browser routing. If we are sure user do not mix one or another, we can make it a state inside StockHomePath. Or we can simply remove this functionality and let developer figure out what is the best way to store browser state. What do you think?
final List<Page<void>> pages = <Page<void>>[ | ||
StockHomePage() | ||
]; | ||
|
||
if (routerState.routePath is StockSettingsPath) { | ||
pages.add(StockSettingsPage()); | ||
} | ||
|
||
if (routerState.routePath is StockSymbolPath) { | ||
final StockSymbolPath routePath = routerState.routePath as StockSymbolPath; | ||
pages.add(StockPage(routePath.symbol)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could use collection if, e.g.
pages = [
FooPage(),
if (...) BarPage(),
]
enum StockMode { optimistic, pessimistic } | ||
enum BackupMode { enabled, disabled } | ||
|
||
class StockConfiguration { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to keeping this in the old file for now.
09d17d9
to
209b2e1
Compare
209b2e1
to
ccdc65f
Compare
ready for another look. |
@goderbauer a gentle ping |
} | ||
|
||
ThemeData get theme { | ||
switch (_configuration.stockMode) { | ||
ThemeData _geThemeFromConfiguration(StockConfiguration configuration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_geThemeFromConfiguration -> _getThemeFromConfiguration
class _RouterConfiguration { | ||
_RouterConfiguration(this.path, this.browserState); | ||
StockRoutePath path; | ||
Map<String, dynamic> browserState; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @goderbauer that this is suboptimal. I don't think we should be encouraging people to use open maps in this way. The whole point of the router API is to make the data structured.
I tried to port Stocks to Router myself, to see what I would come up with, but before I got started I realised we don't seem to have any documentation on how to get started with Router. It would be great to have something on our Web site in the cookbook or similar that talks about how to build an app with Router, and then link to it from MaterialApp.router, Router, RouterDelegate, etc. |
We have a blog post about it https://medium.com/flutter/learning-flutters-new-navigation-and-routing-system-7c9068155ade |
I think a cookbook article is a good idea. The article has a codelab/exercise in it already. |
As developer I would love to see sample and simple I tried to understand how to use Router and came up with this example using ChangeNotifier and Provider. The web demo can be found here. |
Description
Rewrite the stock app with router widget
Related Issues
#63429
Tests
I added the following tests:
existing test coverage
Checklist
Before you create this PR, confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]
). This will ensure a smooth and quick review process.///
).flutter analyze --flutter-repo
) does not report any problems on my PR.Breaking Change
Did any tests fail when you ran them? Please read Handling breaking changes.