Open27

Flutterメモ

やまだーやまだー
  // Flutter frameworkで捕捉できないエラーを捕捉する
  runZonedGuarded(() async {
  }, (Object error, StackTrace trace) {});
やまだーやまだー
WidgetsFlutterBinding.ensureInitialized();
  • Flutter EngineとFlutter frameworkを紐付ける役割。
  • runAppを呼び出す前にFluttter Engineの機能を利用したい場合にrunAppの前で呼び出す。
やまだーやまだー

— dart-define=Flavor=devで指定された環境変数を読み込む

String.fromEnvironment('Flavor')

class Environment {
  static Future<void> load() async {
    final flavor = Flavor.values.byName(String.fromEnvironment('Flavor'));
  }
}
やまだーやまだー
flutter packages pub run flutter_launcher_icons:main

コマンドで

Cannot open file, path = 'assets/images/app_icon_prod.png' (OS Error: No such file or directory, errno = 2)
pub finished with exit code 2

というエラーが表示された。

原因は

  image_path: "assets/images/app_icon_dev.png"

lib/assets/...から指定されていないこと

やまだーやまだー
String.fromEnvironment('Flavor')

で値がnullだった場合の対処法

const String.fromEnvironment('Flavor')

constをつける。

やまだーやまだー

WebやmacOSをプロジェクトからoffにするコマンド

flutter config --no-enable-web
flutter config --no-enable-macos-desktop
やまだーやまだー

Flutter Lint

  • prefer_relative_imports
    相対インポートと絶対インポートを混在させると、同じメンバーが 2 つの異なる方法でインポートされるという混乱が生じる。ディレクトリ内のファイルに対して一貫して相対インポートを使用するようにすることです 。

  • invalid_annotation_target
     参考

やまだーやまだー

FlutterのTextButtonのパディングをなくす方法

 TextButton(
      child: const Text('ボタン'),
      style: ButtonStyle(
        padding: MaterialStateProperty.all(EdgeInsets.zero),
        minimumSize: MaterialStateProperty.all(Size.zero),
        tapTargetSize: MaterialTapTargetSize.shrinkWrap,
      ),
やまだーやまだー

ListViewで
Vertical viewport was given unbounded height.
が出た時にチェックすること。
Columnの中にListViewを定義していた場合、ExpandedFlexibleでラップする。
参考

やまだーやまだー

FireStore導入時にXCode Buildで時間がかかってしまう場合の対処法

①Podfileのコメントを外す

# Uncomment this line to define a global platform for your project
platform :ios, '11.0'

pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '10.3.0'の指定をPodfileに追記する

target 'Runner' do
  use_frameworks!
  use_modular_headers!
  pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '10.3.0'
  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

Podfile.lockを削除し、Pod install --repo-updateを実行する

やまだーやまだー

Splash画面表示後、非同期の初期化処理を終えて次の画面に遷移したい場合。
mainメソッドの中でrunAppを複数回呼び出して切り替える。

main() async {
  runApp(new SplashScreen());
  var foo = await init();
  runApp(new FullApp(foo: foo));
}
やまだーやまだー

freezed.g.dartファイルが生成されないときの確認事項

  1. pubspec.yamljson_serializableの追加をし忘れ
  2. .fromJsonを記述していない
  3. partディレクティブを記述していない
  4. pubspec.lockを削除し、再度パッケージをインストールする
やまだーやまだー

TabBarで選択時のインディケーターをタイトル部だけにするか、選択タブ全体にするかを決定する方法

TabBarindicatorSizeで指定する。

      bottom: TabBar(
        indicatorSize: TabBarIndicatorSize.tab,
        tabs: PurchasedTabItem.tabs(context),
      ),
やまだーやまだー

ListTileでカード型のレイアウトの際、Exapande Columnの子要素が上寄せされない時に確認すること。

RowcrossAxisAlignment: CrossAxisAlignment.startを指定する

Row(
      crossAxisAlignment: CrossAxisAlignment.start,  // ここ
      mainAxisSize: MainAxisSize.min,
      children: [
        Padding(
          padding: const EdgeInsets.only(top: 8, bottom: 8, right: 16),
          child: Image.asset('images/sample.jpg'),
        ),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Padding(
                padding: EdgeInsets.only(top: 10, right: 2),
                child: Text('ホゲホゲ'),
              ),
              Padding(
                padding: EdgeInsets.only(
                  top: 18,
                  right: 44,
                ),
                child: Text('ふがふが'),
              ),
            ],
          ),
        ),
      ],
    )

例

やまだーやまだー

flutterで配列の要数数が少ない時に画面サイズを満たすContainerで、要素数が増えた場合にはスクロールするContainerを実装したい時はどうすれば良いか。

CustomScrollViewを使用する。

    return Scaffold(
      body: SafeArea(
        child: CustomScrollView(
          slivers: [
            SliverFillRemaining(
              hasScrollBody: false,
              child: Container(
                padding: const EdgeInsets.all(16),
                color: context.colorScheme.surface,
                child: Container(
                  decoration: BoxDecoration(
                    color: context.colorScheme.primary,
                    borderRadius: BorderRadius.circular(10),
                  ),
                  child: Column(
                    children: strList2
                        .map(
                          (str) => SizedBox(
                            width: double.infinity,
                            height: 50,
                            child: Text(str),
                          ),
                        )
                        .toList(),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
やまだーやまだー

Riverpod generator使用時のkeepAliveをtrueにすべき時の要点

  • 画面に紐づかなくて、いつでも参照しておきたい情報をkeepAliveでtrueにする
  • iOSでいうところのAppdelegateに保存しておくような情報
やまだーやまだー

Riverpod ref.refreshref.invalidateの違いについて

  • ref.invalidate
    プロバイダを破棄(プロバイダに保持している値のキャッシュを破棄)し、新しい値を取得する
    プロバイダの保持している値を単に更新した時に用いる
  • ref.refresh
    プロバイダを破棄(プロバイダに保持している値のキャッシュを破棄)し、新しい値を取得する。invalidateとの違いは、refreshでは更新された値を返却し、ローディングなどの状態遷移を行うので、例えばプロバイダの更新中のローディング状態中に何かしたい(インディケーターを表示する等)の処理を行いたい場合に使用する。
やまだーやまだー

Stack使用時にRowColumnなどで謎のマージンが取れない場合、確認すべき箇所
1、IconButtonはマージンを持っているので、IconButtonpaddingzeroにする。
2、SizedBoxwidth/heightを指定する

  @override
  Widget build(BuildContext context) {
    final listTile = Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      mainAxisSize: MainAxisSize.min,
      children: [
        _buildPackageView(context),
        _buildListTileContentView(context),
      ],
    );

    return Stack(
      children: [
        listTile,
        Positioned.fill(
          child: Material(
            type: MaterialType.transparency,
            child: InkWell(
              onTap: () {
                widget.onTap(widget.item);
              },
            ),
          ),
        ),
        Positioned(
          bottom: 0,
          right: 0,
          child: _buildActions(context),
        ),
      ],
    );
  }

  Widget _buildActions(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        if (widget.item.sampleMovieUrl != null &&
            widget.item.sampleMovieUrl!.isNotEmpty)
          buildSampleButton(widget.item.sampleMovieUrl!),
        // 2.の例
        SizedBox(
          width: 24,
          height: 24,
          child: buildFavoriteIcon(),
        ),
      ],
    );
  }

  Widget buildFavoriteIcon() {
    final colors = Theme.of(context).extension<FavoriteStarIconColor>();
    return IconButton(
      iconSize: 24,
     // 1.の例
      padding: EdgeInsets.zero,
      onPressed: () {
        widget.onHeartTap(widget.item);
        _animationController
            .forward()
            .then((value) => _animationController.reverse());
      },
      icon: ScaleTransition(
        scale: _animation,
        child: Icon(
          widget.item.isFavorite ? Icons.star : Icons.star_border,
          color: widget.item.isFavorite
              ? colors?.afterStarColor
              : colors?.beforeStarColor,
        ),
      ),
    );
  }

  Widget buildSampleButton(String url) {
    final sampleMovieButton = SvgPicture.asset(
      Assets.icMonthlySampleButton,
      width: 99,
      height: 24,
    );
    return IconButton(
      padding: EdgeInsets.zero,
      icon: sampleMovieButton,
      onPressed: () => widget.onSampleButtonTap(url),
    ).paddingOnly(right: 16);
  }