Skip to content

Conversation

chunhtai
Copy link
Contributor

@chunhtai chunhtai commented Aug 10, 2020

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.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I signed the CLA.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I updated/added relevant documentation (doc comments with ///).
  • All existing and new tests are passing.
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Did any tests fail when you ran them? Please read Handling breaking changes.

@flutter-dashboard flutter-dashboard bot added the c: contributor-productivity Team-specific productivity, code health, technical debt. label Aug 10, 2020
@johnpryan johnpryan mentioned this pull request Aug 17, 2020
3 tasks
@chunhtai chunhtai force-pushed the nav-refactor-stock branch from c5dd346 to 1c91069 Compare August 20, 2020 23:50
@chunhtai chunhtai requested review from goderbauer and Hixie August 20, 2020 23:51
void main() {
runApp(StocksApp());
}
void main() => runApp(const StocksApp());
Copy link
Contributor

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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, i see

@Hixie
Copy link
Contributor

Hixie commented Aug 21, 2020

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?

@chunhtai
Copy link
Contributor Author

chunhtai commented Sep 3, 2020

Thanks for review @Hixie I almost forgot about this PR, i try my best to separate out the stock configuration can you another look?

@ido-ran
Copy link

ido-ran commented Sep 5, 2020

I think there is a problem with showing the current location in the browser address bar due to Navigator 2.0 change.
It happened both in this sample app and a smaller test app I've built.

The RouteInformationParser has the function restoreRouteInformation which seems that it is exactly what the browser should show, but it always show #/ at the end of the application address.

The router does update the application correctly if I change the URL but then it reset it back to #/ immediately after.

On the subject but a different topic - is it possible/plan to have Flutter web app use pushstate API?

@chunhtai
Copy link
Contributor Author

chunhtai commented Sep 8, 2020

I think there is a problem with showing the current location in the browser address bar due to Navigator 2.0 change.
It happened both in this sample app and a smaller test app I've built.

The RouteInformationParser has the function restoreRouteInformation which seems that it is exactly what the browser should show, but it always show #/ at the end of the application address.

The router does update the application correctly if I change the URL but then it reset it back to #/ immediately after.

On the subject but a different topic - is it possible/plan to have Flutter web app use pushstate API?

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

@chunhtai
Copy link
Contributor Author

chunhtai commented Sep 9, 2020

@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}');
Copy link
Contributor

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.

Copy link
Contributor

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) {
Copy link
Member

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
Copy link
Member

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);
Copy link
Member

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

Suggested change
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));
Copy link
Member

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);

Comment on lines 85 to 92
bool get debugInitialized {
bool answer;
assert((){
answer = routePath != null;
return true;
}());
return answer;
}
Copy link
Member

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?

Comment on lines 145 to 57
static StockData stockDataOf(BuildContext context) {
return context.findAncestorWidgetOfExactType<StockStateScope>().stocks;
}
Copy link
Member

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();
}
}
}
Copy link
Contributor

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;
  }

Copy link
Contributor Author

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

Copy link
Contributor Author

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 {
Copy link
Contributor

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?

Copy link
Contributor Author

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?

Copy link
Contributor Author

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

Copy link
Member

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.

@Hixie
Copy link
Contributor

Hixie commented Sep 16, 2020

Sorry for delay in reviewing this.

@skia-gold
Copy link

Gold has detected about 3 untriaged digest(s) on patchset 5.
View them at https://flutter-gold.skia.org/cl/github/63424

@chunhtai chunhtai force-pushed the nav-refactor-stock branch 3 times, most recently from 2589b82 to fae77c7 Compare September 28, 2020 20:56
@chunhtai
Copy link
Contributor Author

chunhtai commented Sep 29, 2020

I added the following change

separate out the router related class into routing/
Make use of browser state to store the tab index in home page

ready for another look

class _RouterConfiguration {
_RouterConfiguration(this.path, this.browserState);
StockRoutePath path;
Map<String, dynamic> browserState;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this?

Copy link
Member

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>?

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

@chunhtai chunhtai Oct 19, 2020

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?

Copy link
Contributor Author

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?

Comment on lines 108 to 119
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));
}
Copy link
Member

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 {
Copy link
Member

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.

@chunhtai chunhtai force-pushed the nav-refactor-stock branch 2 times, most recently from 09d17d9 to 209b2e1 Compare September 30, 2020 23:04
@chunhtai chunhtai force-pushed the nav-refactor-stock branch from 209b2e1 to ccdc65f Compare October 1, 2020 00:01
@chunhtai chunhtai requested a review from goderbauer October 1, 2020 16:37
@chunhtai
Copy link
Contributor Author

chunhtai commented Oct 1, 2020

ready for another look.

@chunhtai
Copy link
Contributor Author

@goderbauer a gentle ping

}

ThemeData get theme {
switch (_configuration.stockMode) {
ThemeData _geThemeFromConfiguration(StockConfiguration configuration) {
Copy link
Contributor

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;
Copy link
Contributor

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.

@Hixie
Copy link
Contributor

Hixie commented Oct 19, 2020

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.

@chunhtai
Copy link
Contributor Author

chunhtai commented Oct 19, 2020

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
We also plan to use this app as an sample app that uses Router
Maybe we should create a codelab or something? What do you think @johnpryan ?

@johnpryan
Copy link
Contributor

johnpryan commented Oct 19, 2020

I think a cookbook article is a good idea. The article has a codelab/exercise in it already.

@orestesgaolin
Copy link
Contributor

As developer I would love to see sample and simple Router or RouterDelegate implementation in the code documentation similarly as there is NoAnimationTransitionDelegate in TransitionDelegate. In most cases this should be just enough to start developing on your own.

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.

@chunhtai chunhtai closed this Mar 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: contributor-productivity Team-specific productivity, code health, technical debt.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants