eaglesakuraの技術ブログ

技術的な話題とか、メモとか。

flutter build ipaのad-hocビルドが異様に遅い問題の対応

発生した問題

  • AppStore用ビルドは20分前後でビルドが完了する
  • 検証用のAd-Hocビルドが60分以上時間がかかる
  • XCode 13.2.1, Flutter 2.10.4

試したこと

  • Swift/Clangの最適化レベルを変更 -> 効果なし
  • dSYM出力無効化 -> 効果なし

突き止めた問題

  • --export-options-plist でAd-Hoc用ビルドにするとビルド時間が3倍に伸びる
  • 試しにビルド20分のAppStore用ビルドをAdHoc用のplistに切り替えるとビルド時間が同じくらいに遅くなった
  • ログを観察しても、ipa出力時に異様に時間がかかっているので間違いなさそう

plist仕様を確認

  • plist仕様を確認し、AdHoc用途で不要そうでdefault trueの項目をチェック
  • これらを明示的にfalseにしたところ、ビルド時間が20分まで早くなった
 <key>uploadBitcode</key>
    <false/>
    <key>compileBitcode</key>
    <false/>
  • compileBitcode が一番怪しいけど、そこまで切り分けはツラいのでこれでヨシとする
  • なんでこのオプションで異様に時間がかかるのか(appstoreビルドではtrueでも影響ないのか)はよくわからない

dartのFreezedとPImplイディオムでメンバ保護を行う

前提

Flutterの開発でReduxアーキテクチャを採用している。

Stateの管理に freezed を使うと、簡単な代わりに全てのメンバがpublicになってしまう。

一部のメンバはprivateにしたり、計算済みの値をキャッシュしたい。

改善

概念。

@freezed
class ExampleStateImpl with _$ExampleStateImpl {
  factory ExampleStateImpl({
    /// このデータはpublicにしたくない.
    /// dataListの合計計算は必要になる時まで計算を遅延したい
    required List<int> dataList,
  }) = _ExampleStateImpl;

  const ExampleStateImpl._();
}
class ExampleState {
   // Stateの実装を隠す
   final ExampleStateImpl _impl;

   factory ExampleStateImpl() {
       return ExampleStateImpl._(ExampleStateImpl(dataList: []));
   }

   ExampleStateImpl._(this._impl);

   final int? _dataListSumCache;
   
   int get dataListSum {
      // 合計値を計算して返す。
      // 計算済みのキャッシュがあればそっちを返す
   }

   ExampleState addData(int newValue) {
      return ExampleState(_impl.copyWith(
          // Freezedの機能を使ってデータコピーを返す
      ));
   }
}

実際はもっと複雑なパターンとなるが、Freezedのメンバーを直接見せたくないけどcopyWithの恩恵は欲しい場合に使う。

dartでKotlin.internal funっぽいことをする

dartのinternal fun

dart 2.15には)ない(たぶん)。

特定packageだけでアクセスしたいメソッドとか作れない。

作ったらみんなアクセスされてしまう。

解決方法

// src/example.dart
class Example {
  String _value = '';

  String get value => _value;
}

extension ExampleSetValue on Example {
  void setValue(String s) {
    _value = s;
  }
}
library example;

// exportするときに、Extensionを隠してやる
export 'src/example.dart' hide ExampleSetValue;

これで行儀良く使っている限りは外部のpackageはExample.setValue()をコールできない。

package内は 'src/example.dart' を直接importする。

無理矢理importすれば使えなくもないので、lintと併用で。

ついにGSuiteの無料版がシャットダウンされるので移行した

GSuite無料版

  • @gmail.com ではなく、独自ドメインのGSuiteが条件付きで無料で使えていた
    • 10年ほど前まで(いまほどGSuiteが普及してない時期)は、無料で10アカウントまで独自ドメインのGSuiteを利用することができた
  • 5年前時点ではもう無料では作れず、有料プランのみに切り替わってた
    • 普及したんだね!

2022/5月にシャットダウン

  • 無料登録したGSuiteアカウントは2022年2月現在まだ無料で使える
  • ただし、無料提供が5月で終了すると発表された(5月までに切り替えが必要で、その後7月までは無料で使える)

さよなら開発アカウント

  • 開発専用を含めて、4つのアカウントを運用していた
    • これだと毎月2500円以上
    • ネトフリの4Kプランよりも高い
  • Admin用と副業用の2アカウントを残し、アカウントを削除
    • これで毎月1300円

無料はいつか終わる

  • あいつも有料化する
  • こいつも有料化する
  • けど、今日じゃない

と思い続けて10年、ついに有料化のビッグウェーブにのまれたという話

Kotlin 1.6に変更してproguardをかけたら通信周りが死んだので直した

事象

  • アプリにログインできない

調査

  • APIcurlすると、正常に動作する(サーバーは正常動作してる)
  • Debugビルドで実行すると、正常に動作する(プログラム自体は正常である)
  • サーバーのログを確認すると、送信値のValidationに失敗している

Releaseビルドでの動作

  • APIRetrofit2 を経由でコールしている
  • JSON変換は moshi を利用している
  • このどちらかに問題が発生した可能性が高い

APIサーバーにどんな値が送られているのか

  • 送信値の直接的なログはない
    • 値のValidationに失敗した、という情報しか出してない
  • まずは手元のアプリでどんな値が実際に送信されているかLogcat

github.com

  • 結果として、snake_caseとして送信しなければならない値がcamelCaseとしてJSON化されていることがわかった
    • moshiには @Json("user_name") のようにAnnotationを付与しているので、Annotationが殺されているのがわかった
data class ExampleLogin(
   @Json("user_name")
   val userName,
)
  • 器用にsnake_caseがcamelCase化処理されるとは考えにくいので、おそらくdata classのプロパティ名がそのまま使われているのだろうと予想する

proguardルールを変更した

  • どうやってもダメ
  • 全部のclassやinterfaceをkeepしてもAnnotationが剥がれてしまう

Annotationを付け替えた

  • filedに付与すると、proguardを通してもAnnotationが生きている
  • なんとなくだが、Proguardの最適化によってField/Methodの扱いが変わってしまったように思う
  • ただし、このままだとProguard前のデバッグで不正になる
data class ExampleLogin(
   @JvmField
   @field:Json("user_name")
   val userName,
)

Annotationをさらにつけた

  • モリモリ全部盛りで、FieldとMethodに付与した。
  • これでDebugもReleaseもOK
  • 変換用Classは全部Generatorが吐き出しているので、テンプレートだけ対応してあげてヨシ.
data class ExampleLogin(
   @JvmField
   @field:Json("user_name")
   @Json("user_name")
   val userName,
)

Kotlin 1.6はなかなか色々大変だったが、これでひと段落のはず

Flutter iOSでファイルの書き込みが静かに失敗する不具合の対応

flutter doctor

$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.5.3, on macOS 12.0.1 21A559 darwin-arm, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 2020.3)
[✓] Android Studio (version 2020.3)
[✓] IntelliJ IDEA Ultimate Edition (version 2021.3)
[✓] IntelliJ IDEA Ultimate Edition (version 2021.3)
[✓] IntelliJ IDEA Ultimate Edition (version 2021.2.3)
[✓] VS Code (version 1.62.3)
[✓] Connected device (2 available)

発生した事象

iOSのアプリCacheディレクトリにファイルを書き込むと、低確率でファイル書き込みが静かに失敗する。

file.writeAsBytes( binary, flush: true);

Consoleログ出力にはエラーはなく、例外が発生しない。 書き込みは失敗するため、書き込んでもファイルが存在しない(File.existsがfalseとなる)し、appendモードの場合はファイル長(File.length())が想定よりも短くなる。

Issue

おそらくこれ

github.com

対応

たまに静かにランダムに失敗するので、成功が確定するまで書き込み続ける。

ログファイルのように次々appendするようなパターンでは二重書き込みが怖いけど、今回の自分のユースケースでは書き込みをリトライすることでワークアラウンドした。

その他

正しい対応の仕方があれば教えてほしい(マジで困る)

AndroidアプリのKotlin 1.4/1.5から1.6へのマイグレーション

AndroidX Roomのコンパイラが不正なJavaクラスを吐き出す

  • Entity(だいたいdata class)に複数のコンストラクタがある場合(Entityをnewする際のサポートのために作ったコンストラクタ)、Kotlin 1.6からRoom Compilerが不正なコードを吐き出すようになった
  • RoomのR/Wに使われないコンストラクタに @Ignore アノテーションをつければ良い
    • メッセージからして、多分1.4までが不正な状態で、1.6で想定通りに機能するようになったと思われる

String.toLowerCase(Locale)が非推奨になった

  • ガイドに従って String.lowercase(Locale) に書き換えれば良い

when分岐でに足りない候補がある場合に(seled classとか)警告が出るようになった

  • elseブロックを追加する
  • 丁寧にやるなら、全部の候補を列挙した方が将来自分が困らなくていいかもしれない(意図しないwhen分岐漏れを防ぐための警告なので)

Channel.offerがtrySendに変更された

  • 戻り値がObjectになり、詳細な結果が取れるようになった

Koinが内部で参照しているClassが消された

  • Time/Duration系の参照が一部変更されたらしい
  • KoinのLoggerレベルを下げてワークアラウンド
  • 追記: Square Wireライブラリ(Proto3のSquare実装)も同じくtime.Instantに関わる問題でクラッシュする
    • 3.7.1では対応済なのでバージョン更新

stackoverflow.com

改善策

  • Android Desugaringライブラリが古いとProguardかけた場合だけ問題が発生する場合がある
  • 1.1.5に更新したら治った

developer.android.com