flutter 開発を進めていく際に参考にした記事+メモ
はじめに
自分用メモのため、文体が雑になっています。
参考になる箇所がありましたら幸いです。
また、内容によっては古いところもあると思います。
目次
あまりにもただのメモである項目は飛ばしています
- Dartの言語仕様
- API呼び出し(既存のインターネット上にあるもの)
- factoryは何のために書くのか
- API呼び出し(localhost)
- <Widget>などの <> は何か
- 状態管理
- () => とは
- CI/CDについて
- @overrideについて
- 全般的に役に立つ記事
- BuildContextとは
- Map<String, dynamic> toJson() => {"aa": 1, "bb": null, "cc": "cat"} は何を表しているか
- 関数名の後に括弧がついていない場合
- ソースコード読んでてわからなかった単語(flutter用語以外もあると思います)
- (編集中)main.dartとapp.dartの使い分け
- よく見るカウンターアプリの作り方
- プロジェクト作成時のmain.dartのコメントをそのままgoogle翻訳にかけてコピペしたもの
- テキストフィールド作成
- Avoid async functions that return void
- The default 'List' constructor isn't available when null safety is enabled
- 【Riverpod】Bad state: No ProviderScope found
- FutureProvider 公式サンプルが動かない
- Widgetの一部を別ファイルに分割したい
- ボタンを押した際にAPIを再実行して再描画させる
- ドロップダウンが動かない
- モデルクラスについて
- APIで取得した値がうまく取り出せない
- freezed使用時にLinterで.g.dartで Missing parameter type for 'e'.が出る
- 更新したProviderが画面に反映されない
- CircularProgressIndicatorを中央寄せしたい
- ディレクトリ構成の神記事
- 画面遷移をしたのにAppBarに戻るボタンが出ない
- 画面遷移を左から右のスライドインにしたい
- class ●● with _$●●について
- The instance member '' can't be accessed in an initializer.
- [SEVERE] freezed on lib/model/◯◯.dart
- 【freezed】 g.dart が作成されない
- nullチェックをしているはずなのにエラーが消えない
Dartの言語仕様
- とてもわかりやすい
公式サンプル(ボタン押したら数字が増えるやつ)の解説のような記事
チュートリアル
API呼び出し(既存のインターネット上にあるもの)
参考
流れ
- Add the http package.
- Make a network request using the http package.
- Convert the response into a custom Dart object.
- Fetch and display the data with Flutter.
(意訳)
- httpパッケージ追加
- httpパッケージを使ってネットワークリクエスト作成
- 2のレスポンスをdartオブジェクトに変換
- 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パッケージを使ってネットワークリクエスト作成
- 詰まっていたこと:サンプルコードをどのファイルに書けばいいかわからない
- https://flutter.dev/docs/cookbook/networking/fetch-data#2-make-a-network-request]
- 解決: とりあえずmain.dartに書いとけばOK
理解しやすいかもしれない方法
- https://flutter.dev/docs/cookbook/networking/fetch-data#complete-example
- 一番下に載っているcomplete exampleをmain.dartに丸々コピペして、動作確認してからコメントを記入していく
factoryは何のために書くのか
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');
}
}
<Widget>
などの <>
は何か
-
型を表す
-
例
// ルーティングの定義 routes: <String, WidgetBuilder> { '/home': (BuildContext context) => new MainPage(), '/subpage': (BuildContext context) => new SubPage() },
-
{String型の値: WidgetBuilder型の値, String型の値: WidgetBuilder型の値}
の形になるMap(Rubyでいうhash) - 参考文献
-
状態管理
そもそも状態とは何か? の例
- 複数のタブがある場合、現在何を開いているか
- データに応じてレイアウトを変えたい時などに使う
- ログインしているユーザーの情報
- APIで取ってきたデータを一時的に入れておく
参考記事
↑を書く前に書いてたこと
- providerがいいらしい
- 初心者はSetState, StatefulWidgetにベタ書きして勘所を掴むのがいいらしい
- https://medium.com/flutter-jp/bloc-provider-70e869b11b2f
() =>
とは
- アロー構文、ファットアローなどの名前がある
-
() { return ●●; }
を() => ●●;
と書ける
参考
CI/CDについて
@overrideについて
- あるメンバを意図的に書き換えていることを示す
- この概念はメタデータというらしい
- 参考
全般的に役に立つ記事
BuildContextとは
- 親Widgetそのもの
- 子Widgetは引数になっているのでわかるが、親Widgetは普通にしていると見えないため
- 一番わかりやすかった記事
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"} ; }の略。
関数名の後に括弧がついていない場合
関数を引数として渡す際には括弧はつけない
(関数自体を引数に渡すという発想がなくてピンとこなかった)
ソースコード読んでてわからなかった単語(flutter用語以外もあると思います)
dio
- HTTP Clientライブラリ
- よく見るサンプルでは
http
が使われている
dao
- Data Access Object の略。デザインパターンの一つ
- データベース関連を書く
- APIアクセスではなく、ローカルのsqliteへの処理
abstract class
- インスタンス化できないクラス
- Java、PHPなどにもある機能
- 何のためにあるのか? について詳しい記事↓
(編集中)main.dartとapp.dartの使い分け
色々なflutterのプロジェクトを眺めていると、mainとappが別ファイルになっているものがあった
- main.dart: main関数+α(状態など)
- app.dart: アプリの本体。テーマやrouteがここ
のイメージ
記事を読んでわからなかった箇所メモ
コンストラクタの後のコロン
Task({
this.title,
this.isDone = false,
String id,
// idはnullならuuidが自動採番される
}) : id = id ?? _uuid.v4(); // ←コンストラクタの直後に、: id = と書いている箇所
- Initializer Lists
コンストラクタの後に、コロンに続けてフィールドの初期化処理を記述します。複数フィールドの初期化時は代入処理をカンマ区切りでリストアップします。
Undefined class 'Computed'.
- 自分の使っていたriverpodのバージョン(0.12.4)では無くなっていた
更新:Flutter2系ではまだまだ使えなさそうなので導入見送り
- 一応作業ログとして残しておく
Isarについて(ローカルDB)
- https://pub.flutter-io.cn/packages/isar/versions/0.2.0
- 0.4.0が最新だが、
isar_connect
がインストールできないので0.2.0で揃える
Schema definition
-
@Collection()
を使う際は、あらかじめimport 'package:isar/isar.dart';
をしておく
よく見るカウンターアプリの作り方
新規プロジェクト作成したら既にカウンターアプリになっている
なので、flutter create
や、Android StudioのNew Flutter Project
を使って新規作成するだけでok
プロジェクト作成時の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.
);
}
}
新規画面作成
出たエラー、warning
- Use key in widget constructors
- Android Studioで直したら、
const NextPage({Key? key}) : super(key: key);
が追加された- これが何なのか、まだわからない
- https://dart-lang.github.io/linter/lints/use_key_in_widget_constructors.html
- Android Studioで直したら、
- Prefer const with constant constructors
-
title: Text('吾輩は猫である'),
で出た -
Text('吾輩は猫である')
が定数コンストラクタなので、「constがいるよ〜」 という理解をした
-
疑問点
- 深く理解せず進めそうなものはとりあえず置いて先に進む。書いている内にだんだんわかってくることも多々あるため
- 理解を先延ばしにしたことが次の問題でボトルネックになりそうならちゃんと理解してから進む
- 逐一向き合うと時間がかかりすぎるため
- そもそもその時点の習熟度ではどうしても理解できないこともある
({Key? key}) : super(key: key) とは
参考になりそうな記事
http なぜaysncをつけなければいけないのか
- そもそもFutureが前提のパッケージだから
- そもそもFutureとは?async/awaitとは?
テキストフィールド作成
出たエラー, 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をつけることを促されるのでつけた
- 状況
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}');
}
void
→Future<void>
にするとwarningが消えた
ドロップダウン
出たエラー, 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://api.flutter.dev/flutter/material/DropdownButton-class.html)
ファイル分割
出たエラー
method not found
状況
APIを叩く処理を別ファイルに切り出すために該当メソッドを持ったクラスを別ファイルに作成。
それをimportしたところ、import自体はうまくいっているがメソッドがどうしても使えない
原因
インスタンスを作っていなかった
記事化
Riverpod導入
出たエラー
Bad state: No ProviderScope found
状況
既にアプリが動いている状態で、Riverpodのサンプルコードを拾ってきて動かそうとしたときに発生
解決策
一度アプリのRunを止めて再起動
Riverpodメモ
final counterProvider = StateProvider((ref) => 0);
final doubleCounterProvider = Provider((ref) {
final count = ref.watch(counterProvider);
return count * 2;
});
- 上記サンプルコードで、
- counterProviderをProviderにするとどうなるか
- doubleCounterProviderをStateProviderにするとどうなるか
- doubleCounterProviderの中の計算式をcounterProviderに入れ、doubleCounterProviderを呼び出さなくするとどうなるか
が気になった。
結果は
Error: The getter 'notifier' isn't defined for the class 'Provider<int>'.
type 'Provider<int>' is not a subtype of type 'StateProvider<int>' of 'function result'
- そのままだと初期値を0にするか値を2倍するかのどちらかになるのでそもそもできない。
TODO
- ProviderとStateProviderの使い分け理解
FutureProvider
公式サンプルが動かない
書いたコード
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
で発生
解決法(というか動いたコードの参照元)
有料です
ちなみに、このサンプルのままではエラーが出るため、
config['host']
の後にtoString()
をつける必要があります。
また、サンプルのjsonの中にhost
というキーがないためnullが表示されます。そのため、json側と呼び出し側を合わせる必要があります。
さらに、appBarがないと最上部に表示されて見辛いため、bodyの前にappBarを入れると何が表示されているかわかりやすいです
Providerを別ファイルに分割したい(riverpod)
動機
main.dartがカオスになってきた
やり方
- 別ファイルに元のProviderの宣言を移して該当ファイルをインポートすればそれで動きます
- クラスの外で宣言すること
- クラスの外で宣言するとグローバルになるためです
- クラスの外で宣言すること
Widgetの一部を別ファイルに分割したい
動機
main.dartが縦に長くなってきた。色々なサンプルコードを見る感じ、main.dartは結構スッキリしている
やり方
- 切り出し前のwidgetのクラスがextendしているのと同じクラスをextendsしたクラスを別ファイルで作成、適宜import追加
- main.dartなど、使う側で
クラス名()
で呼び出し
ボタンを押した際にAPIを再実行して再描画させる
やろうとしたやり方(失敗)
- ボタンのonPressedにAPI呼ぶ処理の関数入れる
- 呼んだ関数の中で
ref
を取れなかったので失敗
- 呼んだ関数の中で
できたやり方
- (API結果を持つProviderを作成)
- 画面更新回数を持ったProviderを作成
- ボタンのonPressedで画面更新回数を増やす
- 画面更新回数のProviderをAPI結果を持っているProviderでwatchする
多分もう少しマシなやり方ありそう
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 しなおしたら気づいたら治ってた...
dropdownが再描画されたタイミングでテキストフィールドの中身が消える
原因
TextEditingController()
の初期化がWidget build
内で行われていたため
再描画されるとき、どこから実行されるのか
@override
Widget build(BuildContext context, WidgetRef ref) { // 1 ここからか
final _selectItem = ref.watch(dropdownSelectedProvider);
print('0000000');
return Scaffold( // 2 ここからなのか
1からでした
モデルクラスについて
普段は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
のどちらかに実体が生成されるのだろうと推測できる
使ってみた
-
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;
}
-
flutter pub run build_runner build
を実行→address_info.freezed.dart
が生成された
ここで一つ疑問。.g.dart
は生成されなかった。
この疑問は公式を確認してすぐに解消できた
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.yaml
のdependencies
にjson_annotation: ">=4.3.0"
を追加
注意:dependencies
とdev_dependencies
を混同しないこと
freezedのabstractについて
公式を確認すると、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に何を実装するかを指示させることができます。
API関連処理がどうしてもうまくいかない
問題
zipApiResult.results
がList<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()
.g.dart
で Missing parameter type for 'e'.
が出る
freezed使用時にLinterで解決法
生成されたコードを静的解析の対象から外す
analyzer:
exclude:
- "**/*.g.dart"
- "**/*.freezed.dart"
を追記
注意:記事によってはexclude:
にしか触れられていないことがあるが、analyzer:
を忘れないこと(自分はここでやらかした)
記事化しました
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
メソッドが存在しないことになっていた
学び
The argument type '型名' can't be assigned to the parameter type 'String'.
-
.toString()
で対応してみるといけるかも
dynamicとObjectは違う
todo 後でまとめる
listが入ったmapのdecode
(とりあえず最小単位も全体もモデル化)
更新したProviderが画面に反映されない
結論: watchしていなかった
修正前
Text(ref.read(updateCountProvider)),
修正後
final _counter = ref.watch(updateCountProvider);
// 中略
Text('$_counter'),
CircularProgressIndicatorを中央寄せしたい
下記どちらでもいけました
loading: () => const Center(child: CircularProgressIndicator()), // ←ここ
Center( // ←ここ
child: Wrap(
children: [
ref.watch(mealHistoryProvider).when(
loading: () => const Center(child: CircularProgressIndicator()),
ディレクトリ構成
困りごと
自由すぎて悩む
記事によって全然違う
名記事
メモ
この記事の執筆者がドメインに基づいた構成を好んでいるということなので、それを真似してみる
よく使うコマンド・たまに使うコマンド
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
サンプルコードの切り貼りをしてたら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(),
],
),
),
),
);
}
画面遷移をしたのにAppBarに戻るボタンが出ない
結論: pushではなくpushReplacementをしていた
- pushReplacementは、一方通行の画面遷移
- ログイン画面やログアウト画面などに使う
参考
finalとは
再代入不可
providerは後から変更されるから使えないのでは?
例えばこんなコード
Widget build(BuildContext context, WidgetRef ref) {
final localUser = ref.watch(userProvider.state);
// 略
localUserの変更処理が同一buildにある場合
finalにできない
localUserの変更処理が同一build以外にある場合
userProvider.state
が変更されるのでfinalにできないのでは?
と思いましたが、その際には build
ごと変更されるので問題ない
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
教訓:とりあえずパッケージがないか確認する
画面遷移時、スライドイン(左から右)をしようとすると真っ暗になる
やっていたこと
-Animation<Offset> _offsetAnimation
を定義
- PageRouteBuilderのtransitionsBuilderにSlideTransitionを設定し、positionに_offsetAnimationを設定
page_transition パッケージ使って解決
調べ中:flutter、一画面や一部分だけで持たせたい状態をStatefulWidget、アプリ全体で持たせたい状態をRiverpodみたいにわけるのもアリなのか
riverpod に詰まったときはStatefulWidgetを理解してから進む
↑記事について調べる
シングルトンとは
名前はよく聞くけど、そうじゃないパターンと比較したときのメリットがわからない
MaterialAppとScaffoldの関係イメージ
わかりやすかった記事
(普段codezineとかが検索に上がってきたらスルーすることが多いのですがこれは普通にわかりやすかったです)
class ●● with _$●● について
freezedで自動生成されるクラスの中に、mixin _$●●
というものがあり、それをMixinしているという記述
参考記事
【調べ中】複数の値をまとめたStateを定義する(Riverpod)
色々なコードを読んでいる感じ、
- Stateで管理したいプロパティをまとめたクラスを定義(仮にSampleStateクラスとする)
- StateNotifier<SampleState>を継承したクラスを定義(仮にSampleNotifierクラスとする)
- StateNotifierProvider<SampleNotifier, SampleState>のように使う
の手順で可能っぽい
参考文献1
参考文献2(有料です)
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);
}
[SEVERE] freezed on lib/model/◯◯.dart
build_runnerが失敗して上記エラーが出ていたが、原因がメッセージを読んだだけではわからなかった
自分の場合はconstでないものにconstをつけていたので出ていたようです(取ったら通りました)
【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の自動整形で改行されていたので上の形にしていたが作成されなかった
確かに公式には下の形で書かれていたが同じ意味だと思っていたので無意識にスルーしてしまっていたので解決に時間がかかった
nullチェックをしているはずなのにエラーが消えない
この記事を参考に一旦ローカル変数に入れるといけた