見出し画像

Flutterで文字列補間にnullableな型が入らないようlintルールを追加しました


はじめに


みなさんお元気ですか。 SHIFT DAAEグループ所属のsakuraiです。

先日、Flutterアプリで画面に「null」と表示されるケースがありました。
Dartのバージョンは3以降を利用しているのでnull-safeに対応しており、
nullableな値を文字列で出力する場合は警告が出そうなのになぜ?と原因を確認したところ、文字列補間(Interpolation)でnullableな型の変数を使っている部分がありました。
例にすると以下のような感じです。

class ExampleWidget extends StatelessWidget {
  const ExampleWidget({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final obj = ExampleObject();
    return Text('nullableString :${obj.nullableString}'); // このような場合はnullableな値でも警告やエラーは出ない
  }
}

class ExampleObject {
  String? nullableString;
  ExampleObject({this.nullableString});
}

Dartでは現在、文字列補間(Interpolation)でnullableな型を利用していた場合警告やエラーなどが発生しないようです。 このため、文字出力部分にnullableな文字列が設定され、「null」が画面に出力されていました。

対策として、nullableな型を文字列補間(Interpolation)する場合は事前に
nullチェックを実施したり、??演算子を利用することが考えられますが、
うっかり入れてしまったり、後からnullableに変更した場合など後からの変更した場合の検知が難しそうです。
やはり何かしらの方法で機械的にチェックできる方が良いですよね。

というわけで独自のlintルールを作成することで対応できないか調べてみました。
Flutterではcustom_lint というパッケージを利用することでlintの追加が可能でしたので、これを利用してチェックを行う対応を行いました。
以下にその手順を記載いたします。

custom_lintの導入


参考

custom_lintの導入はパッケージのドキュメントと以下の記事を参考にしました。
https://pub.dev/packages/custom_lint
https://zenn.dev/altiveinc/articles/flutter-custom-lint-rule-creation

導入前提

環境は以下の通りです。

% flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.22.2, on macOS 14.5 23F79 darwin-arm64, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.3)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.1)
[✓] VS Code (version 1.90.2)
[✓] Connected device (5 available)
[✓] Network resources

ディレクトリ構成

cutom_lintはlint用のパッケージを作成し、アプリ側のdependenciesに追加することで利用可能となります。
このため、今回はlintパッケージとサンプルアプリの2つを作成します。
ディレクトリ構成は最終的に以下のようになります。

// ディレクトリ構成 ※lint関連のないファイルは省略
custom_lint_sample
└packages
  ├── app_sample // サンプルアプリ
  │   ├── analysis_options.yaml
  │   ├── lib
  │   │   └── main.dart
  │   └── pubspec.yaml
  └── sakurai_lint // 独自のlintパッケージ
      ├── lib
      │   ├── lints
      │   │   └── avoid_nullable_interpolation.dart
      │   └── sakurai_lint.dart
      └── pubspec.yaml

lint用のパッケージの作成

まずlintパッケージを作成します。
今回はsakurai_lintというパッケージにしてみました。
みなさんもそれぞれお気に入りの名前で作成してください。

// ホームにサンプルプロジェクトを作成し、サンプルプロジェクト内にlint用パッケージを作成する
% cd 
% mkdir custom_lint_sample
% cd custom_lint_sample 
% flutter create -t package packages/sakurai_lint --project-name sakurai_lint 

cutom_lintのUsage に従って以下のパッケージを追加します。
バージョン等は環境に合わせて変更してください。

environment:
  sdk: ">=3.0.0 <4.0.0"

dependencies:
  analyzer:
  analyzer_plugin:
  custom_lint_builder:

lintルールの作成

lintパッケージ内にルールを作成します。
avoid_nullable_interpolation.dartというファイルを作成し、こちらにルールを記載します。

import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';

class AvoidNullableInterpolation extends DartLintRule {
  const AvoidNullableInterpolation() : super(code: _code);

  static const _code = LintCode(
    name: 'avoid_nullable_interpolation',
    problemMessage: 'Interpolationでnull許容型を利用しない方がいいですよ!!',
  );

  @override
  void run(
    CustomLintResolver resolver,
    ErrorReporter reporter,
    CustomLintContext context,
  ) {
    // 文字列補間に埋め込まれた式の型がnullableであるか確認
    context.registry.addInterpolationExpression((node) {
      final type = node.expression.staticType;
      if (type != null &&
          type.nullabilitySuffix == NullabilitySuffix.question) {
        reporter.reportErrorForNode(_code, node);
      }
    });
  }
}

次にlibディレクトリ直下にある<パッケージ名>.dartに先ほど追加したルールを登録します。

library sakurai_lint;

import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:sakurai_lint/lints/avoid_nullable_interpolation.dart';

PluginBase createPlugin() => _ShiftLintPlugin();

class _ShiftLintPlugin extends PluginBase {
  @override
  List<LintRule> getLintRules(CustomLintConfigs configs) => [
        // 追加したルールを登録
        const AvoidNullableInterpolation(),
      ];
}

これでlintの作成は完了です。
つづいてサンプリのアプリを作成し、lintが動作するか確認を行いましょう。

サンプルアプリの作成・lint の確認

lintパッケージと同じ階層にサンプルアプリを作成します。

// サンプリのアプリの作成
% cd ~/custom_lint_sample/package
% flutter create --platforms ios,android -t app --org com.example.sakurai --project-name app_sample ./app_sample

こちらの手順 に従って analysis_options.yamlpubspec.yamlにcutom_lintを利用する設定を追加します。

analyzer:
  plugins:
    - custom_lint

pubspec.yamlには先ほど作成したsakurai_lintパッケージをdev_dependenciesに追加してください。

pubspec.yaml
environment:
  sdk: '>=3.0.0 <4.0.0'

dev_dependencies:
  flutter_test:
    sdk: flutter
  custom_lint:
  sakurai_lint:
    path: ../sakurai_lint

問題なければflutter pub getが正常終了するはずです。

% flutter pub get

これでlintルールの適用完了です。
app_sample/lib/main.dartで
nullableな値を文字列補間に設定するとlintで指摘されるようになりました!

Tips: VSCodeでのデバッグ

lintルール作成中デバッグを行いたい場合は、
app_sampleのプロジェクトで以下コマンドを実行し

custom_lint --watch

カスタムlintパッケージ側のプロジェクトで
cmd+shift+pを押下しDebug: attach to Dart processを選択して実行することでapp_sample側のプロジェクトでlintパッケージから出力したログの確認やlintパッケージ側の更新を検知してホットリロードすることができます。
ルール作成中は助かりますね。

終わりに


ということで、文字列補間にnullableな型を利用した場合に気づけるようになりました。
ちなみにこのLintルールですが、DCM 同じルール あり、有償ですが、そちらを使った方がよいかもしれません😅
DCMについては別の機会に利用してみたいと思います。

と最後に少し脱線してしまいましたが、独自のlintルールを追加できる準備が整っていると、今後の開発品質の改善も行いやすくなると思いますので今後もうまく活用できればと思います。
最後までお読みいただきありがとうございました。

\もっと身近にもっとリアルに!DAAE公式X/​


執筆者プロフィール:sakurai
SHIFT DAAEグループ所属の開発エンジニアです。
FlutterNinjas行ってきました。

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/