Open10

30日後に始まるFlutter

めっしめっし

0日目:自己ステータス

Android開発5年ぐらい
Flutter2年弱前に副業でちょっと

以上。

めっしめっし

1日目:2024年9月のFlutter事情調査

https://flutter.dev/

Flutter 3.24 and Dart 3.5

自分がやってた当時はFlutter 2だった記憶
https://medium.com/flutter/whats-new-in-flutter-3-8c74a5bc32d0

  • Flutter3での変化
    • 2022年5月リリース
    • 全てのデスクトッププラットフォームへの対応
    • モバイルアップデート
      • 折りたたみ式携帯対応
      • 簡素化されたiOSリリース
      • Gradleバージョンの更新
    • ウェブ
    • ツールのアプデ
      • lint
      • パフォーマンス
      • インぺラー
        • パフォーマンス向上とジャンク軽減を目指すFlutterの新エンジン
  • Dart3の変化
めっしめっし

2日目:古サンプルアプリのビルド/pubspec.yaml

最終変更半年前ぐらいの自分のSampleAppを開いてみたら案の定ビルドできなかったので対応していく

ElevatedButton.styleFrom(primary: colorName)

primaryがなくなってるらしい。
「primary」は、「backgroundColor」に
「onPrimary」は、「foregroundColor」に

一旦ビルドできた

pubspec.yamlに関して

チートシートに丁寧に書かれてる
https://qiita.com/Kurunp/items/76e13bfd03fd3dec1e27

  • publish_to:パッケージを公開する場所を指定します。'none'とすることでパッケージの公開を防止できます。
  • environment:ビルド環境に関する設定
    • sdk:dartのバージョン
    • flutter:Flutterのバージョン
  • dependencies
    • cupertino_icons:iosアプリスタイルのUI作成するためにCupertinoIconsを使う場合必要
  • dev_dependencies:開発時に利用するライブラリ。リリースビルドには含まれない。テスト・Lint系
  • flutter:アプリのアセットとして含むファイルやフォントの指定

バージョン指定

^を毎回忘れてしまう

フィールド 説明
>=0.1.2 <1.0.0 0.1.2以上、1.0.0未満のバージョンを許可
^0.1.2 0.1.2以上、1.0.0未満のバーションを許可。指定されたバージョン以上でメジャーバージョンが同じであれば許可
any バージョン指定なし
空白 any同様

参考

doc
https://dart.dev/tools/pub/pubspec

めっしめっし

3~8日目:ポケモン

こちらやってみます
https://zenn.dev/sugitlab/books/flutter_poke_app_handson

Widget系

  • Image.network()
    • ネットの画像読み込み
    • 画像表示に関して色々したい場合は、BoxDecoration&DecorationImageでやりそう
  • Spacer()
    • 残りのスペース埋める
  • Chip
    • M2は円形寄り, M3はRadius8
  • Stack
    • 重ねるWidget。ComposeでいうBox
  • ListView
    • リスト作るやつ
    • 各アイテムはListTileで作ると簡単
      • 複雑なUIは無理では?業務で使うことあるのかな?
      • leading:先頭につくやつ
  • withOpacity
    • 色の透過を指定
  • circular
    • Radiusの指定

Theme

MaterialAppのthemeで指定する

ThemeMode mode = ThemeMode.system; // デバイスのシステムを利用
MaterialApp(
   theme: ThemeData.light(
       useMaterial3: true, // M3の利用
   ),
   darkTheme: ThemeData.dark(), // ダークテーマ
   themeMode: mode, // テーマのモードを指定。
);
  • computeLuminance
    • 暗ければ 0 に近い値が、明るければ 1 に近い値
    • テキストの色の出しわけ等で使える
    • デザインシステムがある場合は使わないかな

State

StatefulWidget

受け取った値をState側で使う場合はinitStateの中でwidget.xxxという形式で指定

@override
  void initState() {
    super.initState();
    _current = widget.themeMode;
  }

ChangeNotifier - ChangeNotifierProvider

非推奨になったらしい
stateの変更があったときに自動で更新される

StateNotifier - StateNotifierProvider

これもRiverpod2.0からちょっと非推奨。AsyncNotifierとNotifierが最近らしい

// Providerの作成
final pokemonThemeProvider =
    StateNotifierProvider<ThemeModeNotifier, ThemeMode>(
        (ref) => ThemeModeNotifier());

class ThemeModeNotifier extends StateNotifier<ThemeMode> {
  // 初期値セット
  ThemeModeNotifier() : super(ThemeMode.system) {
    _init();
  }

  void _init() async {
    // stateがThemeModeになってる
    state = await getThemeMode();
  }

  void update(ThemeMode nextMode) {
    state = nextMode;
  }
}


// 利用側
// ConsumerWidgetのbuildの中で
final themeState = ref.watch(pokemonThemeProvider); // 状態管理
final themeNotifier = ref.read(pokemonThemeProvider.notifier); // メソッドなど呼ぶ

SharedPreferences

データを端末に保存

const _KEY_THEME_MODE = "theme_mode";
// 保存
Future<void> saveThemeMode(ThemeMode mode) async {
  final pref = await SharedPreferences.getInstance(); // Instances作成
  pref.setString(_KEY_THEME_MODE mode.name); // 
}
// 取得
Future<ThemeMode> getThemeMode() async {
  final pref = await SharedPreferences.getInstance();
  return toMode(pref.getString(_KEY_THEME_MODE) ?? ThemeMode.system.name);
}

Classで渡す引数に関して

const ThemeModeSelectionPage({
    Key? key,
    required this.mode,
  }) : super(key: key);
  final ThemeMode mode;

なみかっこ{}にはOptional なパラメーターーという扱いになり、Widget を使用する側は名前付きで値を渡す必要があります。一方で{}なしだと、必須パラメーターになる。名前は不要で順番が大切になる。
実践的には名前付きなので{}があった方が良さそう。
requiredは名前付きの必須のパラメーターを表す。

関数を引数で

final Function(int) callback;

途中です

めっしめっし

9日目 factory

Factory Constructor

ロジックをクライアントに公開することなくオブジェクトを作成し、共通のインターフェイスを用いて新しく作成されたオブジェクトを参照する。

Named Constructor

名前付きコンストラクタがあり、似てる
Named Constructor は、一つのクラスが複数のコンストラクタを持てるようにしたものであり、様々な用途に応じたインスタンスを使い分けることができる。

Named Constructor
class Human {
  String name;
  int age;

  // 通常のコンストラクタ
  Human({required this.name, required this.age});
  
  // Named Constructor①
  Human.sample() : this.name = 'sample', this.age = 10;
  
  // Named Constructor②
  Human.test() : this.name = 'test', this.age = 99;

  // factoryコンストラクタ①
   factory Human.sample() {
     return Human(name: 'sample', age: 10);
   } 
  
    // factoryコンストラクタ②
   factory Human.test() {
     return Human(name: 'test', age: 99);
   }   
}

factoryはサブクラスを返却できる

https://qiita.com/AsahinaKei/items/c11e61e0f62b0da2551e

めっしめっし

10日目 StateNotifierProviderの更新

class SampleStateNotifier
    extends StateNotifier<Map<int, SmapleModel?>> {
  SampleStateNotifier(this.ref) : super(Map());

}

このようなMapの状態を見るStateNotifierを定義した
Map内のあるアイテムを更新する処理にて以下の現象が起きた

state[id] = hogeData; // 更新されない
state = {...state, id: hogeData}; // 更新された

Mapの更新は注意が必要そうだ

めっしめっし

11日目 SQLITE

https://docs.flutter.dev/cookbook/persistence/sqlite

テーブル作成

static Future<Database> openDb() async {
    return await openDatabase(
      join(await getDatabasesPath(), favFileName),
      onCreate: (db, version) {
        return db.execute(
          'CREATE TABLE $favTableName(id INTEGER PRIMARY KEY)',
        );
      },
      version: 1,
    );
  }

onCreateでは初期定義を行います。基本的にはこの中でテーブルを作成

CREATE

static Future<void> create(Favorite fav) async {
    var db = await openDb();
    await db.insert(
      favTableName,
      fav.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

conflictAlgorithmに関して

  • rollback:OR ROLLBACK
    • SQLステートメントを中止して現在のトランザクションをロールバック
  • abort:OR ABORT
  • fail:OR FAIL
    • SQLステートメントを中止sルウが、変更を取り消すこともトランザクションを終了することもしない
  • ignore:OR IGNORE
    • SQLステートメントを中止するが、後続処理を何も問題がないように継続する。
  • replace:OR REPLACE
    • 競合している対象を削除し、SQLステートメントを実行する

READ

static Future<List<Favorite>> read() async {
    var db = await openDb();
    final List<Map<String, dynamic>> maps = await db.query(favTableName);
    return List.generate(maps.length, (index) {
      return Favorite(
        pokeId: maps[index]['id'],
      );
    });
  }

Listの生成でList.generateというものがある

DELETE

static Future<void> delete(int pokeId) async {
    var db = await openDb();
    await db.delete(
      favTableName,
      where: 'id = ?',
      whereArgs: [pokeId],
    );
  }

whereの?がパラメータを示してそう
whereArgsはListなのでまとめて削除もできそう

めっしめっし

12日目 dynamic?

Dartは基本的に静的型付け言語ですが、dynamic型を使用すれば動的な型宣言になる

var a;
a = 0;
a = ''; // エラー

dynamic a;
a = 0;
a = ''; // エラーが起きない

このdynamicをよく使うケースとしては、APIやSQLiteからデータを取得するとき。

めっしめっし

13日目

リップルに関して

  • InkResponse
  • InkWell
  • Ink
    これらがある

InkResponse

ウィジェット中央に円形のsplashが表示される
アイコンボタンなどに利用

InkWell

ウィジェット全体の探検
ボタンに利用

Ink

InkResponseとInkWellがタッチエフェクトを描画するウィジェットだったのに対し、Ink単体ではエフェクトの描画はできない

https://qiita.com/mkosuke/items/e506256515179d0f421b

めっしめっし

14日目

IndexedStack

複数の子ウィジェットをスタック上に積み重ね、指定したインデックスのウィジェットのみを表示する機能を持ってる。
StackとIndexedStackは、どちらも複数のウィジェットを管理するためのツールですが、その動作には重要な違いがあります。Stackは、その子ウィジェットを重ねて表示することができ、ウィジェットはStackのエッジに対して相対的に配置されます。一方、IndexedStackは一度に一つの子ウィジェットだけを表示し、表示されるウィジェットは指定されたインデックスのものです。
ウィジェットの切り替えなどに利用されます。

ウィジェットの再描画を避けることが挙げる
IndexedStackはすべての子ウィジェットをメモリ上に保持しますが、その分だけメモリを消費する

https://flutter.salon/widget/indexedstack/