flutter, dart メモ

# dart
## bestpractices
effective dart

https://dart.dev/guides/language/effective-dart/design#dont-use-the-legacy-typedef-syntax

### 定数ファイル
constな値は頭文字にkを入れる

```dart
const kActiveColor = Colors.red;
```
## memo
### 論理演算子
```js:.js
const a = null && 1; // null
const b = null || 1; // 1
```
```dart:.dart
const a = null ?. 1; // null
const b = null ?? 1; // 1
```
### 配列
#### reduceではなくfoldを使う
 
https://qiita.com/i88_arakawa/items/dc19fec58312ae09e7e7

#### ループ内でawait
- for in
- Future.forEach

※下記のようにしてもawaitしてくれない

```dart
list.forEach(() async {
  await hogehoge();
  await new Future.delayed(new Duration(seconds: 1)); // wait 1 second
});
```
https://patorash.hatenablog.com/entry/2017/12/15/024453
#### mixin
主に、Classベースのオブジェクト指向言語において、(単一)継承の階層という制約では表現することが難しいデザインには、Mixinを適用する。  
mixin単体では機能せずwithされて機能するもの

- 継承 => 1クラスのみ
- mixin => 複数OK

# flutter
## memo
### state更新を検知させる必要のあるものはsetStateでラップする
```dart
onTap: () => setState((){//dosmothing})
```
### theme編集
```dart
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        primaryColor: Color(0xFF0A0E21),
        scaffoldBackgroundColor: Color(0xFF0A0E21),
      ),
      home: InputPage(),
    );
  }
```
### 位置情報系
- 位置情報変更
    - ios
        - simulatorのfeatures => location
            - mapを起動し現在地を再ロードしないと変更した位置情報の読み込みがされない

### routing
Navigatorを使用する
### 遷移元画面に値を送る
```dart
void changeRoute() async {
  var somthing = await await Navigator.push<String>(
    context, 
    MaterialPageroute(builder: (context) => SomePage());
  print(somthing); // print somthing
}

Navigator.pop(context, 'somthing');
```

### animation
#### animationControllerを利用するときはdisposeで破棄する
別画面に遷移してもリソースが消費され続けるため

### bottomsheet
#### キーボードの上にBottomSheetを配置する
デフォルトでは、BottomSheetは画面の半分を占めています。

```dart
  onPressed: () {
    showModalBottomSheet(
      context: context,
      builder: (context) => AddTaskScreen(),
    );
}
```

特定の画面サイズでは、「追加」ボタンが見えなくなることがあります。isScrolledControlledプロパティをtrueに設定すると、モーダルが全画面表示されるようになります。

```dart
  onPressed: () {
    showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      builder: (context) => AddTaskScreen(),
    );
}
```

AddTaskScreenをキーボードのすぐ上に配置するには、SingleChildScrollViewの中に入れて、MediaQueryを使って下部のパディングを決定します。

```dart
onPressed: () {
  showModalBottomSheet(
    context: context,
    isScrollControlled: true,
    builder: (context) => SingleChildScrollView(
      child:Container(
        padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
        child: AddTaskScreen(),
      )
    )
  );
}
```
### provier
#### constructorとLocatorMixinのinitStateの用途
##### constructor
インスタンス（オブジェクト）を作成するために使用
##### initstate
既存のオブジェクトのコンテンツを初期化するために使用

#### context.〇〇
https://qiita.com/_masaokb/items/fe77495db0aeba226d2a#page
https://medium.com/flutter-jp/state-1daa7fd66b94

```dart
context.select((Product value) => value); // インスタンスが同じなため、muutablに値の変化を行ってもダメ

```

#### list in objectの挙動

```dart
// change notifier
class ProductsController extends ChangeNotifier {
  List<Product> _items = DUMMY_PRODUCTS; // data of List<Product>
}
```
1. plane model

```dart
// model class
class Product {
  // ...somthing
  bool isFavorite;
}
```
1-1. ❌ オブジェクトの値を直接更新した場合

context.selectで取得するListは同じ参照を持っているので、rebuildが起きない

```dart
  // (ProductsController) change notifier method
  void toggleFavorite() {
    _items[0].isFavorite = !_items[0].isFavorite;
    notifyListeners();
  }

final products = context.select((ProductsController value) => value.items); // ❌ not done rebuild
final products = context.watch<ProductsController>().items; // ⭕ done rebuild
```

1-2. ❌ オブジェクトの値を更新した新しいインスタンスをリストの要素と入れ替えた場合

1-1と同じ理由

```dart
  // (ProductsController) change notifier method
  void toggleFavorite() {
    final updateItem = Product(
      id: _items[0].id, // copy
      isFavorite: !_items[0].isFavorite, // updated this!
    ); // create new instance
    _items[0] = updateItem;
    notifyListeners();
  }

final products = context.select((ProductsController value) => value.items); // ❌ not done rebuild
```

1-3. ⭕? 1-2を行い、さらに新しいListを作成する場合

Listの参照が変わるのでrebuildされる
※freezedを使ったパターンと使わないパターン、処理は同じ

ただし、Listの参照が変わるのでGridViewもrebuildされる

```dart
  // (ProductsController) change notifier method
  void toggleFavorite() {
    final item = _items[0];
    final updateItem = Product(
      id: item.id, // copy
      isFavorite: !_items[0].isFavorite, // updated this!
    ); // create new instance
    final updatedProducts =
        _items.map((e) => e != item ? e : updateItem).toList();
    _items = updatedProducts;
    notifyListeners();
  }

  // [use freezed]
  // void toggleFavorite() {
  //   final item = _items[0];
  //   final updateItem =
  //       item.copyWith(isFavorite: !item.isFavorite); // create new instance
  //   final updatedProducts =
  //       _items.map((e) => e != item ? e : updateItem).toList();
  //   _items = updatedProducts;
  //   notifyListeners();
  // }

final products = context.select((ProductsController value) => value.items); // ⭕ done rebuild
```

2\. modelにchange notifierをextendsする
※ただし、プレーンオブジェクトじゃなくなるのでmodelとChangeNotifierでクラスを分けたほうが良いと思う

```dart
// model class
class Product extends ChangeNotifier {
  // ...somthing
  bool isFavorite;

  void toggleIsFavorite() {
    isFavorite = !isFavorite;
    notifyListeners();
  }
}
```

2-1. ❌ ProductsControllerのListの要素をそのまま下位Widgetに渡した場合

context.selectは1-1と同じ理由
context.watchはGridViewのみrebuildされるが、下位Widgetはrebuildされない
※notifyListeners();をList内のオブジェクトで行っているため

```dart
class ProductGridview extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final products = context.watch<ProductsController>().items; // ❌ not done rebuild
    // final products = context.select((ProductsController value) => value.items); // ❌ not done rebuild
    return GridView.builder(
      itemCount: products.length,
      itemBuilder: (context, index) => ProductItem(product: products[index]),
    );
  }
}

class ProductItem extends StatelessWidget {
  const ProductItem({Key key, this.product}) : super(key: key);

  final Product product;

  @override
  Widget build(BuildContext context) {
    return IconButton(
        icon: product.isFavorite
            ? Icon(Icons.favorite, color: Colors.red)
            : Icon(Icons.favorite_border_outlined, color: Colors.red),
        onPressed: () => product.toggleIsFavorite());
  }
```

2-2. ProductsControllerのListの要素をChangeNotifier.valueで渡した場合

```dart
class ProductGridview extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final products = context.select((ProductsController value) => value.items);
    return GridView.builder(
      itemCount: products.length,
      itemBuilder: (context, index) => ChangeNotifierProvider.value(
        value: products[index],
        child: ProductItem(),
      ),
    );
  }
}
```

2-2-1. ❌ context.selectでChangeNotifier自体を取得した場合

context.selectしているProduct自体が同一なのでrebuildが起きない
※もちろん、watchにするとrebuildされる

```dart
class ProductItem extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final product = context.select((Product value) => value);
    return IconButton(
        icon: product.isFavorite
            ? Icon(Icons.favorite, color: Colors.red)
            : Icon(Icons.favorite_border_outlined, color: Colors.red),
        onPressed: () => context.read<Product>().toggleIsFavorite());
  }
}
```

2-2-2. ⭕ context.selectで更新をする値を取得した場合

GridViewはrebuildされず、このWidgetのみrebuildされる

```dart
class ProductItem extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return IconButton(
        icon: context.select((Product value) => value.isFavorite)
            ? Icon(Icons.favorite, color: Colors.red)
            : Icon(Icons.favorite_border_outlined, color: Colors.red),
        onPressed: () => context.read<Product>().toggleIsFavorite());
  }
}
```
※パフォーマンス系の話はこちら
https://medium.com/flutter-jp/state-performance-7a5f67d62edd


### パーフォーマンスtips
- ListView
    - https://twitter.com/_mono/status/1194233029335535617?s=20


### riverpod
- family

外部パラメーターからプロバイダーを作成する