Skip to content

[102] Complete #3

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

Merged
merged 34 commits into from
Apr 10, 2018
Merged

[102] Complete #3

merged 34 commits into from
Apr 10, 2018

Conversation

willlarche
Copy link
Collaborator

@willlarche willlarche commented Apr 5, 2018

Dependent on #2

Ignore anything in a directory with 101. It's awaiting the earlier pull request to set those files straight.

Also, all the data an images is temporary. New images coming next week.

Expanded(
child: Padding(
padding: EdgeInsets.fromLTRB(
16.0,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Did the flutter formatter put all these numbers on their own lines? Because that's the first time I've seen positional parameters split out like that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It did.

Copy link

Choose a reason for hiding this comment

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

I think it does that only if the last one ends in a comma.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ah.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Mind blown.

),
SizedBox(height: 8.0),
// TODO(larche): Make subtitle2 when available
Text('${products[index].priceString}',
Copy link
Collaborator

Choose a reason for hiding this comment

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

products[index].priceString is already a string, unless I'm mistaken. The string interpolation should be unnecessary here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh, duh. Yeah :)


final String priceString;

const Product(category, id, isFeatured, name, price, priceString)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Consider named parameters here with @required from dart/meta. Six positional parameters is a lot to remember/get right each time.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Having this constructor isn't actually necessary anymore. It was when I was trying to have the formatter live in the class and format on creation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

But did the @required on the next one.


import 'product.dart';

List<Product> allProducts = generateProducts();
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure how much kibitzing you're looking for here, but I would recommend refactoring this code a bit

  • Get rid of allProducts, which I don't think is being used.
  • Change formatter to be a parameter in the call to generateProducts().
  • Change getData to return Future<List<Product>> rather than Future<Null>
  • Change _HomePageState to have a member variable called allProducts of type List<Product>
  • In the initState method for HomePage (line 61) of previous file, do this:
getData().then((list)` =>
  setState(() {
    allProducts = list;
  });
);

That puts a copy of the products to be displayed into the State object, and avoids having an empty setState call, which I've heard is a code smell for Flutter.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So, I couldn't do exactly that because I needed to have a default value for the formatter incase the async method took too long (which I've seen.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

PTAL

icon: Icon(Icons.menu),
onPressed: () {
print('Menu button');
}),
Copy link

Choose a reason for hiding this comment

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

General comment, I think it might be a style guidance to put a comma after each parameter, so the end ) of IconButton matches the indent of leading. Same for below, where there'd be a comma after childAspectRatio: 8.0 / 9.0, so that the last ), matches the child indent. Might want to verify w Flutter team

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes you are correct! I had that wrong.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I went thru and added them anywhere I could catch. It really improved the formatting!


import 'package:flutter/material.dart';

void main() => runApp(new ShrineApp());
Copy link

Choose a reason for hiding this comment

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

I think this can just be ShrineApp(), no need new

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

}

class _HomePageState extends State<HomePage> {
List<Product> allProducts = generateProducts(null);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Rather than catching and replacing this null at line 15 of data.dart, I'd recommend just passing NumberFormat.simpleCurrency() here. If the home page is responsible for getting the locale and providing the formatter in initState, it seems reasonable to me that it would also be responsible for choosing the fallback formatter and providing that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done!

Copy link
Collaborator

@redbrogdon redbrogdon left a comment

Choose a reason for hiding this comment

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

Two mostly opinionated comments.

16.0,
12.0,
16.0,
8.0,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: if the trailing comma after "8.0" is what's making these appear on their own lines (and unless you have other guidance from Hans or whomever), I'd remove it. It's just bonkers to me to have these spread out so much.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Well, I had it taken out and then I asked around the office and people said yes to trailing commas. They didn't see this case tho. Maybe this is an exception. @HansMuller , what do you prefer?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree, just one line is much easier to read. In cases like this we usually take a break from the trailing comma dogma.

EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),

super.initState();

getLocale().then((locale) => setState(() {
formatter = NumberFormat.simpleCurrency(
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't have anything real to back this up, but for some reason I feel like the function passed to setState should be as small as possible, and just do assignments to the state object. I'm not sure if that's an official style point or just something I've picked up from various examples, but to me this code:

getLocale().then((locale) {
  formatter = NumberFormat.simpleCurrency(
     locale: locale,
    decimalDigits: 0,
  );
  setState(() {
    allProducts = generateProducts(formatter);
  });
});

Seems cleaner, since all it's doing in setState is setting things on the State object. Hans or Filip can correct me if I'm being weird or the closures don't work this way, of course. 😃

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Check out the changes Hans asked for. This is actually out. He knew a better way to get the locale.

Copy link
Collaborator

@HansMuller HansMuller left a comment

Choose a reason for hiding this comment

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

The gist of much of the feedback is: do the localization late, when the UI is built (not in initState() or class vars).

// TODO(larche): Make headline6 when available
Text(
products[index].name,
style: Theme.of(context).textTheme.title,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Theme.of() is somewhat expensive, so best to cache a ThemeData value at the top of the build function. This is really common, conventionally the variable is called theme:

final ThemeData theme = Theme.of(context);

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

return [];
}

List<Card> cards = List.generate(
Copy link
Collaborator

Choose a reason for hiding this comment

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

There doesn't seem to be any need to create this variable. Note also: you could make the formatting a little more conventional like this:

return List.generate(products.length, (int index) {
  return Card(
    child: ...
  );
}).toList();

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done! But why do I need the toList()? Doesn't List.generate return a List?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, you are correct, the toList() isn't needed.


List<Card> cards = List.generate(
products.length,
(int index) => Card(
Copy link
Collaborator

Choose a reason for hiding this comment

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

We only use => for one-liners that return a value

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done!

void initState() {
super.initState();

getLocale().then((locale) => setState(() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

You shouldn't call setState() from initState(). I don't think you want to do this here at all. You don't want to call getLocale() either, since Flutter's idea of Locale depends on where a widget is in its tree.

Use Localizations.localeOf(context) to look up the locale when you need it (you'll have to convert it to a String to use it with dart:intl, like locale.toString())

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.


List<Card> _buildGridCards(BuildContext context, List<Product> products) {
if (products == null || products.isEmpty) {
return [];
Copy link
Collaborator

Choose a reason for hiding this comment

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

return const <Card>[]

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done!

return localeString;
}

List<Product> generateProducts(NumberFormat formatter) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

You could just export a constant list here, like kAllProducts. If you're really trying to model an implicit lookup, then getAllProducts().

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done. I went with getAllProducts() since that will set us up for category filtering later.


List<Product> generateProducts(NumberFormat formatter) {
return <Product>[
Product(
Copy link
Collaborator

Choose a reason for hiding this comment

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

To be consistent: 2 space indents.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think it is 2 space indents. But the kerning is weird and you can't easily tell.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Check the whole file...

@required this.isFeatured,
@required this.name,
@required this.price,
@required this.priceString,
Copy link
Collaborator

Choose a reason for hiding this comment

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

The localized price string should be computed by the widget that displays Product, not here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

/// Formatted currency representation of price. Localized if possible.
final String priceString;

const Product({
Copy link
Collaborator

Choose a reason for hiding this comment

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

Put the constructor first; assert that the parameters aren't null

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Like this:

class Product {
  const Product({
    @required this.category,
    @required this.id,
    @required this.isFeatured,
    @required this.name,
    @required this.price,
  })  : assert(category != null),
        assert(id != null),
        assert(isFeatured != null),
        assert(name != null),
        assert(price != null);

  final Category category;
  final int id;
  final bool isFeatured;
  final String name;
  final int price;

  @override
  String toString() => "$name (id=$id)";
}

?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes

}

class _HomePageState extends State<HomePage> {
List<Product> allProducts = generateProducts(NumberFormat.simpleCurrency(
Copy link
Collaborator

Choose a reason for hiding this comment

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

This value should be computed when it's needed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I put it inside _buildGridCards as a local variable. Lemme know if that makes sense.

final ThemeData theme = Theme.of(context);
final NumberFormat formatter = NumberFormat.simpleCurrency(
locale: Localizations.localeOf(context).toString());
return List.generate(products.length, (int index) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you're just looking up the products by index, then using map would be simpler:

return getAllProducts().map((Product product) {
  return Card( ... product.name ... product.id ... etc )
}).toList();

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done!

@HansMuller
Copy link
Collaborator

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants