【Android】WorkerFactoryをDaggerでDIする
経緯
プロダクトで定期的に実行する機能の開発があり、Workerというものを採用することになりました
その際にいくつか詰まったところがあったので、使い方や使用感をまとめたいと思います
Workerとは
困ったところ
WorkerはWorkManagerというAndroidフレームワークによって生成されます
その際にコンストラクタで受け取れる引数はContextとWorkerParametersのみで、他のオブジェクトを受け取ることはできません
それを実現するためには、独自のWorkerFactoryを作り、WorkManagerに渡してあげる必要があります
手順
- Workerを作成する
今回は独自のLogユースケースクラスを引数に取るWorkerを作ります
class LogWorker( context: Context, params: WorkerParameters, private val logUseCase: LogUseCase ) : Worker(context, params) { override fun doWork(): Result { logUseCase.start() return Result.success() } }
- WorkerFactoryを継承したLogFactoryを作成
class LogWorkerFactory( private val logUseCase: LogUseCase ) : WorkerFactory() { override fun createWorker( appContext: Context, workerClassName: String, workerParameters: WorkerParameters ): ListenableWorker? { return if (workerClassName == LogWorker::class.java.name){ LogWorker(appContext, workerParameters, logUseCase) } else { null } } }
- 作成したLogWorkerFactoryを呼び出したいクラスからWorkManager.initialize()メソッド経由でWorkerManagerへ登録
class MyApplication : Application() { override fun onCreate() { super.onCreate() WorkManager.initialize( this, Configuration.Builder().setWorkerFactory(LogWorkerFactory()) .build() ) } }
- デフォルトのWorkManagerInitializerを削除する旨をマニフェストファイルに記載
<manifest ...> <application ...> ... <provider android:name="androidx.work.impl.WorkManagerInitializer" android:authorities="${applicationId}.workmanager-init" tools:ignore="ExportedContentProvider" tools:node="remove" /> </application> </manifest>
- 通常通り呼び出しを行う()
今回は定期実行ということでPeriodicWorkRequestクラスを利用します
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val workManager = WorkManager.getInstance() val logPeriodicWorkRequest = PeriodicWorkRequest.Builder( LogWorker::class.java, Duration.ofMinutes(15) ).build() val operation = workerManager.enqueue(logPeriodicWorkRequest) workManager.enqueue(OneTimeWorkRequest.from(LogWorker::class.java)) } }
通常はここで終わりなんですが、プロダクトではDaggerを使っているので、provide メソッドによる管理を行いたいと思います
WorkerをDIしようとすると結構な手順が必要になるので、今回はWorkerではなく、WorkerFactoryのみDIしたいと思います
WorkerごとDIしたい場合は下記を参照
手順は簡単です
- provideメソッドをモジュールにセット
@Provides @Singleton fun provideWorkerFactory( logUseCase: LogUseCase ): WorkerFactory { return LogFactory(logUseCase) }
- 呼び出しもとでInjectする
class MainActivity : AppCompatActivity() { @Inject lateinit var workerFactory: WorkerFactory override fun onCreate(savedInstanceState: Bundle?) { (省略) } }
まとめ
今回はDagger環境でWorkerを使う方法について解説しました
定期実行したいという際にまず出てくるのがWorkerという選択肢だと思うので、今後も使う機会があるのではないでしょうか
今回参考にさせていただいたページは下記です
今回も最後まで読んでいただいてありがとうございました
https://proandroiddev.com/dagger-2-setup-with-workmanager-a-complete-step-by-step-guild-bb9f474bde37
【storybook】使ってみたけど、フロントエンジニアではない私にとっては使いにくかった
経緯
技術調査の一環で、storybookというwebアプリのデザインカタログを作れるライブラリがあるとのことで使ってみた
storybookとは
上にも書いたとおり、webアプリのデザインカタログを作れるライブラリで、HTMLやJSはもちろん、Vue・React・AngularなどのSPAたちにも対応している
デザイナーとのやり取りや、Componentの重複を防ぐことを目的として作られることが多いようだ
結論
それに適したアーキテクチャのアプリでかつ、慣れないと少し使いづらい
使っている人の間ではかなり絶賛されているので、使える人からすれば素晴らしい機能なんだと思うんですが、
そもそもフロントエンドエンジニアではない私にとってはちょっと使いこなすのが難しかったです
もう少し詳しく説明していきたいと思います
使いにくいポイント① 最新版(v6)のドキュメントが少ない
私の調べた限りだとこのくらい
この記事を書いている時点での最新がv6系なのですが、出回っている記事のほとんどが古いバージョンに関しての記事
v5→v6でファイル構成だったり、アドオンの種類だったり、コマンドだったり結構変わっているので、使ったことある人じゃなければv5以前の記事を見ながらv6を使っていくのは至難の業だと思います
しかしv6になってから結構立つのになんで記事が少ないんでしょうか?
古いバージョンのまま使っている人がほとんどなのか、もしくはもう誰も使っていないのか
使いにくいポイント② エラーが出ない
私の場合は別のプロジェクトですでにできているアプリを使ってstoryを書いてたんですが、なぜかstoryを読み取ってくれない
エラーとかExceptionとか出してくれたらいいのにそれもない
だから何がだめなのかわからない
しかもドキュメントもない・・・\(^o^)/オワタ
使いにくいポイント③ 既存のアプリケーションに導入する場合はアプリを選ぶ
上記も書いた通り、アトミックデザインのような再利用を前提としたデザイン・Component構成になっていない限りあまり使う旨味はないと思います
使うためには既存のソースコード+storyを記載するファイルをComponent単位で作る必要があります
これだけでかなり工数が増えるのに加え、storyファイルには依存関係も全て記載していかなければならず、もとのComponentの依存関係が変わったらStoryも書き換えなければいけないため、開発だけではなく、メンテナンスの工数もかかってしまいます
ただ、きっちり再利用できるComponentごとに分割して開発できているプロジェクトってそんなにないんじゃないでしょうか?(偏見)
言い換えてみれば、今から作るプロジェクトに関してはこれを導入することで、強制的にアトミックデザインに持っていくようなガード的な役割もできるかもしれないですね
最後に
今回は私の専門外の話なので少し短いですが終わりたいと思います
有識者の方で、もし私の記事の中で間違っている所があればご指摘いただけると幸いです
今回も最後まで読んでいただきありがとうございました
それでは失礼致します
【GithubActions】Androidアプリの署名設定
経緯
Androidアプリのビルド時、現状はビルド後に別で署名処理を走らせているのですが、ビルドと署名を同時に行うよう設定したいと思って調べていました
いくつか方法は出てきたのですが、何回やってもkeytoolで「署名付きJARファイルではありません」という状態でうまくいきませんでした
一応解決したので備忘録として記録しておきます
方法
結論としては、「v1Signを有効にする必要があった」ということです
見てもらったほうが早いと思うのでいかにサンプルを記載します
サンプル
まずはbuild.gradle(appの方)にキー情報を記載します
android { ... signingConfigs{ release{ storeFile rootProject.file('release.keystore') storePassword System.getenv('KEYSTORE_PASSWORD') keyAlias System.getenv('KEY_ALIAS') keyPassword System.getenv('KEY_PASSWORD') v1SigningEnabled true v2SigningEnabled true } } buildTypes { release { signingConfig signingConfigs.release } ... } ... }
ここで「v1SigningEnabled trueと「v2SigningEnabled true」を忘れないでください
そしてリリースビルド時に署名を行いたい場合は、BuildTypeにその旨を記載します
また、個々に記載した署名鍵の情報はGithubのシークレットに記載する必要があります
手順としては、Githubの「Settings」→「Secrets」で設定します
その後にymlファイルを修正していきます
name: ANDROID_CI on: push: branches: - develop - master pull_request: branches: - develop - master jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: release build & sign run: | echo "${{ secrets.KEYSTORE_FILE }}" | base64 -d > release.keystore export KEYSTORE_PASSWORD="${{ secrets.KEYSTORE_PASSWORD }}" export KEY_ALIAS="${{ secrets.KEY_ALIAS }}" export KEY_PASSWORD="${{ secrets.KEY_PASSWORD }}" ./gradlew assembleRelease - name: upload release build uses: actions/upload-artifact@v2 with: name: release_apk path: app
以上です
お役に立てると幸いです
今回も最後まで見てくださってありがとうございました
【GithubActions】ブランチ名に特定の文字列が入っているかどうか確認する
経緯
私の携わっているプロジェクトでは、release/〇〇.〇〇みたいな感じでバージョンごとにブランチ切ってるんですが、ブランチに記載しているバージョンとgredleに記載しているバージョンが違うというケースが発生しました
まぁ原因は私のバージョン累進忘れなんですけどね・・・
ということで、バージョンチェックもActionsでやろうということになったので今回はそのお話です
サンプル
name: ANDROID_CI on: push: branches: - develop - release/** - master pull_request: branches: - develop - release/** - master jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set env to develop if: endsWith(github.ref, '/develop') || endsWith(github.base_ref , 'develop') run: echo "FLAVOR=develop" >> $GITHUB_ENV - name: Set env to release if: contains(github.ref, '/release') || contains(github.base_ref , 'release') # バージョン累進チェックを行う run: | if echo ${{github.ref}} | grep "${APK_VERSION}" > /dev/null || echo ${{github.base_ref}} | grep "${APK_VERSION}" > /dev/null ; then echo "FLAVOR=release" >> $GITHUB_ENV else exit 1 fi - name: Set env to production if: endsWith(github.ref, '/master') || endsWith(github.base_ref , 'master') run: echo "FLAVOR=prod" >> $GITHUB_ENV ...
このようにgrep検索を条件にすることで実現できます
検索結果がある場合はそのまま環境変数を設定する処理を行い、なかったら「exit 1」で強制終了
grep部の変数はなぜかクオーテーションつけないと「unary operator expected」というエラーが発生したので必須のようです
/dev/nullに関してはなんのためにいるのかわかっていないですが、これがないと動かなかったです
参考:
bash何もわからん
今回も最後まで見てくださってありがとうございました
【GithubActions】Githubの環境変数の設定・使用方法
経緯
ブランチ名をGithub変数に登録し、それをbash・アップリード処理で使用したいケースが起こりました
結構ややこしい部分なので、備忘録としてメモしておきます
サンプル
name: ANDROID_CI on: push: branches: - develop - master pull_request: branches: - develop - master jobs: build: runs-on: ubuntu-latest steps: #事前設定 - uses: actions/checkout@v2 #環境変数に設定 - name: Set env to develop if: endsWith(github.ref, '/develop') || endsWith(github.base_ref , 'develop') run: echo "FLAVOR=develop" >> $GITHUB_ENV - name: Set env to production if: endsWith(github.ref, '/master') || endsWith(github.base_ref , 'master') run: echo "FLAVOR=prod" >> $GITHUB_ENV #リリースビルド&署名(prodの場合はバージョン番号がAPK名になる) - name: release build & sign run: ./gradlew assemble${FLAVOR}_Release - name: upload release build uses: actions/upload-artifact@v2 with: name: release_apk path: app/build/outputs/apk/${{env.FLAVOR}}_/release
こんな感じで使用することができます
echo "FLAVOR=develop" >> $GITHUB_ENV
とすると、githubの環境変数としてbash外でも使用できるようになります
階層の深さによって${〇〇}として使う場合と${env.〇〇}として使う場合があるので気をつけてください
ここで結構時間取られました・・・
今日は以上です
最後まで読んでいただいてありがとうございます
【GithubActions】前のJobの実施状況によって次のジョブを実施するか判定する
経緯
前のJobが終わったとき(スキップされていない場合)のみ実行したいケースが何回かありました
当然前のJobと同じ条件をコピペすればいいのですが、このケースが何回も続けば変更箇所が増えるのでメンテナンス性が落ちてしまいます
なんで、できるだけコピペじゃないやり方ないかなと探して見つかったのでまとめておきたいと思います
方法
見てもらったほうが早いと思うので、サンプルを記載します
name: ANDROID_CI on: push: branches: - develop - master pull_request: branches: - develop - master jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: debug build if: endsWith(github.ref, '/develop') || endsWith(github.base_ref , 'develop') id: debug_build run: ./gradlew assembleDebug - name: upload debug build if: steps.debug_build.conclusion != 'skipped' uses: actions/upload-artifact@v2 with: name: debug_apk path: app
デバッグビルドはdevelopブランチでのみ実施 そのビルド処理が走ったときだけアップロード処理を走らせたい、というケースになっています
手順としては、ベースとしたい処理にidを振り、次に実施する処理の分岐として、steps.(ID名).conclusion != 'skipped'を登録する
そうすることで、実行されたときのみ実施されるようになります
ちなみにsteps.(ID名).conclusion == 'skipped'とすればスキップされたときだけ実行されるので、疑似if-else文を書くことができます
まとめ
こちらもよくあるケースかなと思い、備忘録としてまとめておきたいと思います
お役に立てると幸いです
今回も最後まで見てくださってありがとうございました
【GithubActions】特定のブランチでのみ処理を実施したい場合にif文で分岐させるやり方
経緯
GithubActionsであるブランチへのPusu(PR)時のみ実施したい処理というのがいくつか出てきた
とはいえ、ブランチごとにymlファイルを作っちゃうと、変更するときの影響範囲が大きくなるのでやりたくない
一つのファイルの中で分岐させるようにしたい
方法
結論、containsやendsWithを利用することで条件分岐をさせることができる
ブランチの情報はgithub.refやgithub.base_refで取得できる
github.ref
Push時のブランチ名を取得することができます
そのままブランチ名が出てくるわけではなく、refs/heads/(ブランチ名)という値になっています (タグのときはrefs/tags/(ブランチ名)となるようです)
なので、末尾が一致しているかどうかを判定するendsWithを使うことでチェックを行います
github.base_ref
PullRequest先のブランチ名を取得できます
これはgithub.refとちがってブランチ名をそのまま取得することができます
こちらはそのままイコールで比較すればいいんですが、めんどうなので同じようにendsWith使っています
endsWithとcontains
endsWithは上記のとおりですが、今回の私のケースだと末尾がバージョンになっていて、毎回名前が変わるという状況でした(例:release/〇〇.〇〇)
その時は文字列が含まれているかどうかを確認するcontainsを用います
この2つ以外にも、頭を比較するstartsWithもあります
サンプル
name: ANDROID_CI on: push: branches: - develop - release/** - master pull_request: branches: - develop - release/** - master paths-ignore: - .idea/** - README.md jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: only release and master if: contains(github.ref, '/release') || contains(github.base_ref , 'release') || endsWith(github.ref, '/master') || endsWith(github.base_ref , 'master') run: (実施したい処理)
まとめ
よくあるケースだとは思うのですが、案外調べて引っかからなかったので今回備忘録としてまとめておきました
お役に立てると幸いです
今回も最後まで見てくださってありがとうございました