Open58

flutter 開発を進めていく際に参考にした記事+メモ

ピン留めされたアイテム
k13ik13i

はじめに

自分用メモのため、文体が雑になっています。
参考になる箇所がありましたら幸いです。

また、内容によっては古いところもあると思います。

目次

あまりにもただのメモである項目は飛ばしています

k13ik13i

API呼び出し(既存のインターネット上にあるもの)

参考

流れ

  1. Add the http package.
  2. Make a network request using the http package.
  3. Convert the response into a custom Dart object.
  4. Fetch and display the data with Flutter.

(意訳)

  1. httpパッケージ追加
  2. httpパッケージを使ってネットワークリクエスト作成
  3. 2のレスポンスをdartオブジェクトに変換
  4. flutterでデータを表示

1. httpパッケージ追加

  • pubspec.yamlにパッケージ追加を書く
    • pubspec.yamlはパッケージ管理のファイル
      • RailsのGemfileみたいな感じ dependenciesとdev_dependencies(本番では使わないもの)で使い分けできる
  • <latest_version>はパッケージの最新のバージョンに置き換える
    • 自動的に取ってきてくれる訳ではないので注意(1敗)
  • AndroidManifest.xmlに設定追加とあるが、同じ名前のファイルが3つあって混乱、app/src/profile/AndroidManifest.xmlには追加する内容が既に書かれていてさらに混乱
    • TODO: 調査
    • とりあえず、変更なしで進む

2. httpパッケージを使ってネットワークリクエスト作成


理解しやすいかもしれない方法

k13ik13i

API呼び出し(localhost)

https://stackoverflow.com/a/61690100 これを参考に、自分のlocalhostをipに変換してからローカルのAPIのURLを記入

// Future<User>型が戻り値の fetchUser 関数を定義
// asyncなので非同期
Future<User> fetchUser() async {
  final response = await http.get('http://192.168.179.4:3003/users/1'); // ←

  if (response.statusCode == 200) {
    // APIレスポンスからjsonを取得してUserコンストラクタを返す
    return User.fromJson(jsonDecode(response.body));
  } else {
    throw Exception('Failed to load user');
  }
}
k13ik13i

状態管理

そもそも状態とは何か? の例

  • 複数のタブがある場合、現在何を開いているか
  • データに応じてレイアウトを変えたい時などに使う
  • ログインしているユーザーの情報
  • APIで取ってきたデータを一時的に入れておく

参考記事

https://techracho.bpsinc.jp/wingdoor/2020_10_08/97815

https://note.com/mxiskw/n/n5c06bc2dd0d5

https://qiita.com/hammer0802/items/0a12d129222441a3a91c

↑を書く前に書いてたこと

k13ik13i

Map<String, dynamic> toJson() => {"aa": 1, "bb": null, "cc": "cat"} は何を表しているか

  • Map:Map型。他の言語で言う辞書型やHashみたいなkey-valueの形
  • Map<String, dynamic>:keyがString型、valueがdynamic(動的型づけ)なMap
  • Map<String, dynamic> toJson():戻り値の型がMap<String, dynamic>なtoJsonメソッドの定義
  • => {"aa": 1, "bb": null, "cc": "cat"} :{ return {"aa": 1, "bb": null, "cc": "cat"} ; }の略。
k13ik13i

関数名の後に括弧がついていない場合

関数を引数として渡す際には括弧はつけない

(関数自体を引数に渡すという発想がなくてピンとこなかった)

k13ik13i

ソースコード読んでてわからなかった単語(flutter用語以外もあると思います)

dio

  • HTTP Clientライブラリ
  • よく見るサンプルでは http が使われている

https://pub.dev/packages/dio

dao

  • Data Access Object の略。デザインパターンの一つ
  • データベース関連を書く
    • APIアクセスではなく、ローカルのsqliteへの処理

abstract class

  • インスタンス化できないクラス
    • Java、PHPなどにもある機能
    • 何のためにあるのか? について詳しい記事↓

https://qiita.com/aiko_han/items/e8ddce85188970fd77da

k13ik13i

(編集中)main.dartとapp.dartの使い分け

色々なflutterのプロジェクトを眺めていると、mainとappが別ファイルになっているものがあった

  • main.dart: main関数+α(状態など)
  • app.dart: アプリの本体。テーマやrouteがここ

のイメージ

k13ik13i

記事を読んでわからなかった箇所メモ

https://qiita.com/hammer0802/items/0a12d129222441a3a91c#todoタスクのモデルとコントローラー

コンストラクタの後のコロン

Task({
    this.title,
    this.isDone = false,
    String id,
    // idはnullならuuidが自動採番される
}) : id = id ?? _uuid.v4(); // ←コンストラクタの直後に、: id = と書いている箇所

https://dev.classmethod.jp/articles/about_dart_constructors/

  • Initializer Lists

コンストラクタの後に、コロンに続けてフィールドの初期化処理を記述します。複数フィールドの初期化時は代入処理をカンマ区切りでリストアップします。

Undefined class 'Computed'.

  • 自分の使っていたriverpodのバージョン(0.12.4)では無くなっていた

https://pub.dev/packages/riverpod/changelog#060

k13ik13i

更新:Flutter2系ではまだまだ使えなさそうなので導入見送り

  • 一応作業ログとして残しておく

Isarについて(ローカルDB)

Schema definition

  • @Collection()を使う際は、あらかじめimport 'package:isar/isar.dart';をしておく
k13ik13i

よく見るカウンターアプリの作り方

新規プロジェクト作成したら既にカウンターアプリになっている
なので、flutter createや、Android StudioのNew Flutter Projectを使って新規作成するだけでok

k13ik13i

プロジェクト作成時のmain.dartのコメントをそのままgoogle翻訳にかけてコピペしたもの

main.dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // 「フラッターラン」でアプリケーションを実行してみてください。が表示されます
        // アプリケーションには青いツールバーがあります。次に、アプリを終了せずに、試してみてください
        // 以下のprimarySwatchをColors.greenに変更してから、
        // 「ホットリロード」(「フラッターラン」を実行したコンソールで「r」を押します。
        // または、Flutter IDEで「ホットリロード」への変更を保存するだけです)。
        // カウンターがゼロにリセットされなかったことに注意してください。アプリケーション
        // 再起動されません。
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo fHome Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  // このウィジェットは、アプリケーションのホームページです。ステートフル、つまり
  // 影響を与えるフィールドを含むStateオブジェクト(以下に定義)があること
  // それがどのように見えるか。

  // このクラスは、状態の構成です。それは値を保持します(これでは
  // 親(この場合はアプリウィジェット)によって提供されたタイトル)と
  // stateのビルドメソッドで使用されます。ウィジェットサブクラスのフィールドは次のとおりです。
  // 常に「最終」とマークされます。

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // このsetStateの呼び出しは、Flutterフレームワークに何かが持っていることを伝えます
      // この状態で変更されたため、以下のビルドメソッドが再実行されます
      // 表示に更新された値を反映できるようにします。変更した場合
      // setState()を呼び出さずに_counterを実行すると、ビルドメソッドは
      // 再度呼び出されたため、何も起こらなかったようです。
      _counter++;
    });
  }

  
  Widget build(BuildContext context) {
    // このメソッドは、たとえば完了したように、setStateが呼び出されるたびに再実行されます。
    // 上記の_incrementCounterメソッドによって。
    //
    // Flutterフレームワークは、ビルドメソッドを再実行するように最適化されています
    // 高速なので、更新が必要なものはすべて再構築できます
    // ウィジェットのインスタンスを個別に変更する必要はありません。
    return Scaffold(
      appBar: AppBar(
        // ここでは、によって作成されたMyHomePageオブジェクトから値を取得します
        // App.buildメソッドを使用し、それを使用してアプリバーのタイトルを設定します。
        title: Text(widget.title),
      ),
      body: Center(
        // センターはレイアウトウィジェットです。それは一人っ子を取り、それを配置します親の真ん中で。
        child: Column(
          //
          // 列はレイアウトウィジェットでもあります。それは子供たちのリストを取り、
          // それらを垂直に配置します。デフォルトでは、サイズに合わせてサイズが調整されます
          //
          // 「デバッグペインティング」を呼び出します(コンソールで「p」を押して、
          // AndroidのFlutterInspectorからの「デバッグペイントの切り替え」アクション
          // Studio、またはVisual Studio Codeの「ToggleDebugPaint」コマンド)
          // 各ウィジェットのワイヤーフレームを確認します。
          //
          // 列には、それ自体のサイズとサイズを制御するためのさまざまなプロパティがあります。
          // 子をどのように配置するか。ここでは、mainAxisAlignmentを使用して
          // 子供を垂直に中央に配置します。ここでの主軸は垂直です
          // 列が垂直であるため、軸(交差軸は水平)。
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

k13ik13i

新規画面作成

出たエラー、warning

  • Use key in widget constructors
  • Prefer const with constant constructors
    • title: Text('吾輩は猫である'), で出た
    • Text('吾輩は猫である')が定数コンストラクタなので、「constがいるよ〜」 という理解をした
k13ik13i

疑問点

  • 深く理解せず進めそうなものはとりあえず置いて先に進む。書いている内にだんだんわかってくることも多々あるため
    • 理解を先延ばしにしたことが次の問題でボトルネックになりそうならちゃんと理解してから進む
    • 逐一向き合うと時間がかかりすぎるため
      • そもそもその時点の習熟度ではどうしても理解できないこともある

({Key? key}) : super(key: key) とは

参考になりそうな記事

http なぜaysncをつけなければいけないのか

k13ik13i

テキストフィールド作成

出たエラー, warning

  • SizedBox for whitespace.
    • A Container is a heavier Widget than a SizedBox, and as bonus, SizedBox has a const constructor.
      • 「Containerは重いから使えるときはSizedBox使ってね」的な感じなのかな
  • Invalid constant value.
    • 状況
    • 最初、何が悪いのかさっぱりわからなかったが、StackOverflowにドンピシャな回答があった
    • どうやら、TextFieldをconstで宣言しているのにonChangedの中身がconstではないことが問題だったっぽい
      • TextFieldの前のconstを取ると、decorationのInputDecorationにconstをつけることを促されるのでつけた
k13ik13i

APIを叩く

出たエラー、warning

  • Avoid async functions that return void
    • 状況
void _zipApi() async {
    final url =
        Uri.parse('https://zipcloud.ibsnet.co.jp/api/search?zipcode=0010000');
    final response = await http.get(url);
    print('Response status: ${response.statusCode}');
    print('Response body: ${response.body}');
  }

voidFuture<void>にするとwarningが消えた

k13ik13i

ドロップダウン

出たエラー, warning

The default 'List' constructor isn't available when null safety is enabled. (Documentation)  Try using a list literal, 'List.filled' or 'List.generate'.

状況

final List<DropdownMenuItem<int>> _items = List();

解決策

final List<DropdownMenuItem<int>> _items = List<DropdownMenuItem<int>>.empty();

参考

https://stackoverflow.com/questions/63451506/the-default-list-constructor-isnt-available-when-null-safety-is-enabled-try

余談

最終的に、拾ってきたコードではドロップダウンの実現ができなかったので、公式ドキュメントから動くコードを持ってきた(https://api.flutter.dev/flutter/material/DropdownButton-class.html)

k13ik13i

ファイル分割

出たエラー

method not found

状況

APIを叩く処理を別ファイルに切り出すために該当メソッドを持ったクラスを別ファイルに作成。
それをimportしたところ、import自体はうまくいっているがメソッドがどうしても使えない

原因

インスタンスを作っていなかった

記事化

https://zenn.dev/ishiki/articles/f4c3ac7eaa72e3

k13ik13i

Riverpod導入

出たエラー

Bad state: No ProviderScope found

状況

既にアプリが動いている状態で、Riverpodのサンプルコードを拾ってきて動かそうとしたときに発生

解決策

一度アプリのRunを止めて再起動

k13ik13i

Riverpodメモ

final counterProvider = StateProvider((ref) => 0);

final doubleCounterProvider = Provider((ref) {
  final count = ref.watch(counterProvider);
  return count * 2;
});

  • 上記サンプルコードで、
  1. counterProviderをProviderにするとどうなるか
  2. doubleCounterProviderをStateProviderにするとどうなるか
  3. doubleCounterProviderの中の計算式をcounterProviderに入れ、doubleCounterProviderを呼び出さなくするとどうなるか

が気になった。

結果は

  1. Error: The getter 'notifier' isn't defined for the class 'Provider<int>'.
  2. type 'Provider<int>' is not a subtype of type 'StateProvider<int>' of 'function result'
  3. そのままだと初期値を0にするか値を2倍するかのどちらかになるのでそもそもできない。

TODO

  • ProviderとStateProviderの使い分け理解
k13ik13i

FutureProvider

公式サンプルが動かない

https://pub.dev/documentation/riverpod/latest/riverpod/FutureProvider-class.html

書いたコード

final configProvider = FutureProvider<Configuration>((ref) async {
  final content = json.decode(
    await rootBundle.loadString('assets/configurations.json'),
  ) as Map<String, Object?>;

  return Configuration.fromJson(content);
});

// Widget example.
class ProviderPage extends ConsumerWidget {
  const ProviderPage({Key? key}) : super(key: key);

  static const String title = 'ProviderPage';
  static const String routeName = 'provider-page';

  
  Widget build(BuildContext context, WidgetRef ref) {
    AsyncValue<Configuration> config = ref.watch(configProvider);

    return config.when(
      loading: () => const CircularProgressIndicator(),
      error: (err, stack) => Text('Error: $err'),
      data: (config) {
        return Text(config.host);
      },
    );
  }

出たエラー, warning

The name 'Configuration' isn't a type so it can't be used as a type argument. Try correcting the name to an existing type, or defining a type named 'Configuration'.

冒頭、<Configuration>で発生

The argument type 'dynamic' can't be assigned to the parameter type 'String'

await rootBundle.loadString('assets/configurations.json')で発生

Undefined name 'Configuration'. Try correcting the name to one that is defined, or defining the name.

return Configuration.fromJson(content);Configurationで発生

解決法(というか動いたコードの参照元)

有料です
https://zenn.dev/riscait/books/flutter-riverpod-practical-introduction/viewer/v1-future-provider

ちなみに、このサンプルのままではエラーが出るため、
config['host']の後にtoString()をつける必要があります。
また、サンプルのjsonの中にhostというキーがないためnullが表示されます。そのため、json側と呼び出し側を合わせる必要があります。

さらに、appBarがないと最上部に表示されて見辛いため、bodyの前にappBarを入れると何が表示されているかわかりやすいです

k13ik13i

Providerを別ファイルに分割したい(riverpod)

動機

main.dartがカオスになってきた

やり方

  • 別ファイルに元のProviderの宣言を移して該当ファイルをインポートすればそれで動きます
    • クラスの外で宣言すること
      • クラスの外で宣言するとグローバルになるためです
k13ik13i

Widgetの一部を別ファイルに分割したい

動機

main.dartが縦に長くなってきた。色々なサンプルコードを見る感じ、main.dartは結構スッキリしている

やり方

  1. 切り出し前のwidgetのクラスがextendしているのと同じクラスをextendsしたクラスを別ファイルで作成、適宜import追加
  2. main.dartなど、使う側でクラス名()で呼び出し
k13ik13i

ボタンを押した際にAPIを再実行して再描画させる

やろうとしたやり方(失敗)

  • ボタンのonPressedにAPI呼ぶ処理の関数入れる
    • 呼んだ関数の中でrefを取れなかったので失敗

できたやり方

  1. (API結果を持つProviderを作成)
  2. 画面更新回数を持ったProviderを作成
  3. ボタンのonPressedで画面更新回数を増やす
  4. 画面更新回数のProviderをAPI結果を持っているProviderでwatchする

多分もう少しマシなやり方ありそう

k13ik13i

Dropdown

動かなかった

Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
'package:flutter/src/material/dropdown.dart':
Failed assertion: line 915 pos 15: 'items == null || items.isEmpty || value == null ||
              items.where((DropdownMenuItem<T> item) {
                return item.value == value;
              }).length == 1'

解決法

DropdownButtonのvalueで指定した初期値がDropdownMenuItemの中に無かった

原因

  • エラーの内容を理解していなかった
  • コードの切り貼りを色々してた

値が変更されない

原因

  • 選択した値を保持するProviderをwatchしていなかった

見た目上の値は変更されるが取得した値が1つ前のものになっていた

run しなおしたら気づいたら治ってた...


原因

TextEditingController()の初期化がWidget build内で行われていたため

k13ik13i

再描画されるとき、どこから実行されるのか

  @override
  Widget build(BuildContext context, WidgetRef ref) { // 1 ここからか
    final _selectItem = ref.watch(dropdownSelectedProvider);
    print('0000000');

    return Scaffold( // 2 ここからなのか

1からでした

k13ik13i

モデルクラスについて

普段はRailsばかり書いていて、モデルは当たり前のように存在するもので、迷うことなんてない

と思っていたのですが、Flutterではなくても書ける
とはいえjsonをそのまま画面に出すとカッコ悪いし、開発する側も思考整理されてない等、使わないという選択肢は実質ない イメージ

どうやるか

RailsではApplicationRecordを継承したクラスを作るとそれがモデルになって自動でいろんなメソッドが使えて...というような至れり尽くせりな状態だったわけですが、Flutterはデフォルトではそういうのはないらしい。

そこで色々なサンプルコードを見ているとfreezedという単語を多く目にする。

ユーザーモデルを例えに出すと、

  • user.dart
  • user.g.dart
  • user.freezed.dart

の3ファイルがある。これがfreezedがやっていることなのだろうと推測。まだあまりわかりませんが。

少し調べた感じでは、最初にあるのはuser.dartのみで、残り2つはfreezedで生成されたものらしい。

元のuser.dartにあるUserクラスの定義がabstractになっているので、.g.freezedのどちらかに実体が生成されるのだろうと推測できる

使ってみた

  1. lib/entity/address_info.dartを作成
import 'package:freezed_annotation/freezed_annotation.dart';

part 'address_info.freezed.dart';

@freezed
class AddressInfo with _$AddressInfo {
  const factory AddressInfo(
      String address1,
      String address2,
      String address3,
      String kana1,
      String kana2,
      String kana3,
      int prefcode,
      String zipcode,
      ) = Data;
}
  1. flutter pub run build_runner buildを実行→address_info.freezed.dartが生成された

ここで一つ疑問。.g.dartは生成されなかった。
この疑問は公式を確認してすぐに解消できた

https://pub.dev/packages/freezed#fromjsontojson

fromJson追加時に出たエラー

先程のファイルに、

factory AddressInfo.fromJson(Map<String, dynamic> json) => _$AddressInfoFromJson(json);

を追加して、flutter pub run build_runner buildを実行した際に下記エラー

You are missing a required dependency on json_annotation in the "dependencies" section of your pubspec with a lower bound of at least "4.3.0".

これに従い、pubspec.yamldependenciesjson_annotation: ">=4.3.0"を追加
注意:dependenciesdev_dependenciesを混同しないこと

freezedのabstractについて

https://pub.dev/packages/freezed#the-abstract-keyword

公式を確認すると、abstractは不要という記述があった。

As you might have noticed, the abstract keyword is not needed anymore when declaring freezed classes.
This allows you to easily use mixins with the benefit of having your IDE telling you what to implement.

google翻訳

お気づきかもしれませんが、フリーズしたクラスを宣言するときに、abstractキーワードはもう必要ありません。

これにより、ミックスインを簡単に使用できるようになり、IDEに何を実装するかを指示させることができます。

k13ik13i

API関連処理がどうしてもうまくいかない

問題

zipApiResult.resultsList<AddressInfo>?型だが中身が取得できない
Listなら['要素名']で取れるのでは? という理解なので手詰まり感

画面表示

コード

Widget build(BuildContext context, WidgetRef ref)の抜粋

Wrap(
            children: [
              ref.watch(searchResultProvider).when(
                    loading: () => const CircularProgressIndicator(),
                    error: (error, stack) => Text('Error: $error'),
                    data: (zipApiResult) {
                      return RefreshIndicator(
                        onRefresh: () async =>
                            ref.refresh(searchResultProvider),
                        child: Column(
                          children: [
                            Text(zipApiResult.results)
                          ],
                        ),
                      );
                    },
                  ),
            ],
          ),
@freezed
class ZipApiResult with _$ZipApiResult {
  const factory ZipApiResult({
    String? message,
    required int status,
    List<AddressInfo>? results,
  }) = Data;

  factory ZipApiResult.fromJson(Map<String, dynamic> json) =>
      _$ZipApiResultFromJson(json);
}

searchResultProviderの実装

final searchResultProvider = FutureProvider<ZipApiResult>((ref) async {
  final zip = ref.watch(zipcodeProvider);
  final url =
  Uri.parse('https://zipcloud.ibsnet.co.jp/api/search?zipcode=$zip');
  final response = await http.get(url);

  final decodedJson = json.decode(response.body) as Map<String, dynamic>;
  if(decodedJson['status'] == 400){
    print('zip api error');
    return ZipApiResult(status: response.statusCode);
  }else{
    final zipResult = ZipApiResult.fromJson(decodedJson);
    return zipResult;
  }
});

解決

zipApiResult.results![0].address1.toString()

k13ik13i

freezed使用時にLinterで.g.dartMissing parameter type for 'e'.が出る

解決法

生成されたコードを静的解析の対象から外す

analyzer:
  exclude:
    - "**/*.g.dart"
    - "**/*.freezed.dart"

を追記
注意:記事によってはexclude:にしか触れられていないことがあるが、analyzer:を忘れないこと(自分はここでやらかした)

記事化しました

https://zenn.dev/ishiki/articles/flutter_freezed_missing_parameter

k13ik13i

Mapのリスト形式のjsonデコード

やりたいこと

 [{"id"=>46, "timing"=>"lunch", "taste_type_id"=>1, "meal_type_id"=>1, "user_id"=>1, "description"=>"gohan", "created_at"=>"2021-11-29T06:05:14.803Z", "updated_at"=>"2021-11-29T06:05:14.803Z"}, {"id"=>45, "timing"=>"lunch", "taste_type_id"=>1, "meal_type_id"=>1, "user_id"=>1, "description"=>"gohan", "created_at"=>"2021-11-29T06:05:14.784Z", "updated_at"=>"2021-11-29T06:05:14.784Z"}]

[MealHistory(), MealHistory()]

の形にしたい

変更前モデル(できなかった例)

import 'package:freezed_annotation/freezed_annotation.dart';

part 'meal_history.freezed.dart';
part 'meal_history.g.dart';


class MealHistory with _$MealHistory {
  const factory MealHistory({
    required int id,
    String? timing,
    String? description,
    String? mealTypeName,
    String? tasteTypeName,

  }) = Data;
  factory MealHistory.fromJson(Map<String, dynamic> json) => _$MealHistoryFromJson(json);
}

試みたこと(まだ動かない)

import 'package:freezed_annotation/freezed_annotation.dart';

part 'meal_history.freezed.dart';
part 'meal_history.g.dart';


class MealHistory with _$MealHistory {
  const factory MealHistory({
    int? id, // ←変更
    String? timing,
    String? description,
    String? mealTypeName,
    String? tasteTypeName,

  }) = Data;
  // 以下変更
  factory MealHistory.fromJson(Map<String, dynamic> json) {
    return MealHistory(
      id: json['id'],
      timing: json['timing'],
      description: json['description'],
      mealTypeName: json['mealTypeName'],
      tasteTypeName: json['tasteTypeName']
    );
  }
}

途中で踏んだミス

  • 同じ名前のクラスを別のファイルに書いていて、fromJsonメソッドが存在しないことになっていた
k13ik13i

todo 後でまとめる

listが入ったmapのdecode
(とりあえず最小単位も全体もモデル化)

k13ik13i

更新したProviderが画面に反映されない

結論: watchしていなかった

修正前

Text(ref.read(updateCountProvider)),

修正後

final _counter = ref.watch(updateCountProvider);
// 中略
Text('$_counter'),
k13ik13i

CircularProgressIndicatorを中央寄せしたい

下記どちらでもいけました

loading: () => const Center(child: CircularProgressIndicator()), // ←ここ
Center( // ←ここ
            child: Wrap(
            children: [
              ref.watch(mealHistoryProvider).when(
                    loading: () => const Center(child: CircularProgressIndicator()),
k13ik13i

ディレクトリ構成

困りごと

自由すぎて悩む
記事によって全然違う

名記事

https://blog.dalt.me/2941

メモ

この記事の執筆者がドメインに基づいた構成を好んでいるということなので、それを真似してみる

k13ik13i

よく使うコマンド・たまに使うコマンド

Dart・Flutterのバージョン確認

flutter --version

freezed生成

flutter pub run build_runner build --delete-conflicting-outputs

エミュレータがインターネットに繋がらない時に使うやつ

Android emulatorのあるディレクトリで実行
./emulator -avd Pixel_5_Edited_API_30(エミュレータ名) -dns-server 8.8.8.8

k13ik13i

サンプルコードの切り貼りをしてたらMaterialAppが2つになった

修正前

  Widget build(BuildContext context, WidgetRef ref) {
    return MaterialApp(
      home: DefaultTabController(
        length: 2,
        child: Scaffold(
          appBar: AppBar(
            title: const Text(title),
            bottom: TabBar(
              tabs: myTabs,
            ),
          ),
          body: TabBarView(
            // controller: TabController(length: 3, vsync: this),
            children: [
              SearchByZipcode(),
              History(),
            ],
          ),
        ),
      ),
    );
  }

修正後

Widget build(BuildContext context, WidgetRef ref) {
  return DefaultTabController(
      length: 2,
      child: Scaffold(
        child: Scaffold(
          appBar: AppBar(
            title: const Text(title),
            bottom: TabBar(
              tabs: myTabs,
            ),
          ),
          body: TabBarView(
            // controller: TabController(length: 3, vsync: this),
            children: [
              SearchByZipcode(),
              History(),
            ],
          ),
        ),
      ),
    );
  }
k13ik13i

finalとは

再代入不可

providerは後から変更されるから使えないのでは?

例えばこんなコード

Widget build(BuildContext context, WidgetRef ref) {
    final localUser = ref.watch(userProvider.state);
  // 略

localUserの変更処理が同一buildにある場合

finalにできない

localUserの変更処理が同一build以外にある場合

userProvider.stateが変更されるのでfinalにできないのでは?
と思いましたが、その際には buildごと変更されるので問題ない

k13ik13i

AnimationControllerとは

Transition系のWidgetのパラメタに設定するもの という理解

vsync、どのサンプルコード見ても大体thisが入ってるけど、今書いているのがConsumerWidgetだからなのかエラーになる

with TickerProviderStateMixin が要りそうだがStateでないので無理っぽい

'TickerProviderStateMixin<StatefulWidget>' can't be mixed onto 'ConsumerWidget' because 'ConsumerWidget' doesn't implement 'State<StatefulWidget>'.

ConsumerWidgetとは何ぞやってところをしっかり理解せずに勉強を進めてきたのでここらで今自分が何を書いてるかを1行ずつ理解していく

ConsumerWidget(riverpod)

A StatelessWidget that can listen to providers.

https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ConsumerWidget-class.html

k13ik13i

教訓:とりあえずパッケージがないか確認する

画面遷移時、スライドイン(左から右)をしようとすると真っ暗になる

やっていたこと

-Animation<Offset> _offsetAnimationを定義

  • PageRouteBuilderのtransitionsBuilderにSlideTransitionを設定し、positionに_offsetAnimationを設定

page_transition パッケージ使って解決

https://pub.dev/packages/page_transition

k13ik13i

調べ中:flutter、一画面や一部分だけで持たせたい状態をStatefulWidget、アプリ全体で持たせたい状態をRiverpodみたいにわけるのもアリなのか

k13ik13i

MaterialAppとScaffoldの関係イメージ

わかりやすかった記事

https://codezine.jp/article/detail/13329

(普段codezineとかが検索に上がってきたらスルーすることが多いのですがこれは普通にわかりやすかったです)

k13ik13i

【調べ中】複数の値をまとめたStateを定義する(Riverpod)

色々なコードを読んでいる感じ、

  1. Stateで管理したいプロパティをまとめたクラスを定義(仮にSampleStateクラスとする)
  2. StateNotifier<SampleState>を継承したクラスを定義(仮にSampleNotifierクラスとする)
  3. StateNotifierProvider<SampleNotifier, SampleState>のように使う

の手順で可能っぽい

参考文献1

https://qiita.com/_masaokb/items/fe77495db0aeba226d2a

参考文献2(有料です)

https://zenn.dev/riscait/books/flutter-riverpod-practical-introduction/viewer/v1-state-notifier-provider

k13ik13i

The instance member '' can't be accessed in an initializer.

main関数の中で宣言したものとclassの中で宣言した変数で動きが違ったのでそのあたり?

class Api {
  // ng
  //   final aa = 300;
  //   final bb = aa * 100;
  
  // ng
  //   final aaa = 33;
  //   int iii = aaa * 2;
  
  // ng
  //   final int kk = 999;
  //   kk = 1000;
  
  // ng
  //   final dd = [1,5,6,7];
  //   dd[2] = 3;
  
  // ok
  static const aa = 200;
  int bb = aa * 100;
  
  // ok
  static const A = 400;
  final b = A * 30;
}

main(){
  final uu = 2; // constをつけるように促されるがエラーにはならない
  final ww = uu * 9;
  
  final aaa = 33; // constをつけるように促されるがエラーにはならない
  int iii = aaa * 2;
  
  // The final variable 'kk' can only be set once.
  //   final int kk = 999;
  //   kk = 1000;
  
  final dd = [1,5,6,7];
  dd[2] = 3;
  
  final api = Api();
  print(ww);
  print(iii);
  print(api.b);
  print(api.bb);
  print(dd);
}
k13ik13i

[SEVERE] freezed on lib/model/◯◯.dart

build_runnerが失敗して上記エラーが出ていたが、原因がメッセージを読んだだけではわからなかった

自分の場合はconstでないものにconstをつけていたので出ていたようです(取ったら通りました)

k13ik13i

【freezed】 g.dart が作成されない

fromJsonの形が違ったらしい

NG

factory Model.fromJson(Map<String, dynamic> json) {
    return _$ModelFromJson(json);
}

OK

factory Model.fromJson(Map<String, dynamic> json) =>
      _$ModelFromJson(json);

VSCodeの自動整形で改行されていたので上の形にしていたが作成されなかった

確かに公式には下の形で書かれていたが同じ意味だと思っていたので無意識にスルーしてしまっていたので解決に時間がかかった