- 投稿日:2021-03-21T16:58:10+09:00
github actionsでunityビルドしてiOSとAndroidのストアに自動的に提出する
こんにちは。virapture株式会社のもぐめっとです。
この記事を書いてるときは冬なんですけど、もう自分の写真がなさすぎて夏までさかのぼってしまいました。
本日はCI/CD with Unity, GitHub Actions, and Fastlaneという記事を参考にUnityでの自動ビルドとストア提出を作ってみたのでそのメモ書きを残しておきます。
概要
UnityにはUnity Cloud Buildという公式のCIがあるのですが、いかんせんfastlaneとの連携ができないので、証明書を引っ張ってきたり、ストアにアップロードというのができません。
そこで探してみたところ、GameCIが出しているunity-builderというgithub actionsを使ってビルドを行い、デプロイにはfastlaneを使用することでうまくできました。
このunity-builderが結構優秀で、ライセンスの認証とかもやってくれる。そして、ビルドが終わった後はライセンスをリターンしてくれるという優れもの。
もはやUnity Cloud Build使わなくてもCIができちゃいます。構築手順
github actions workflowの設置
記事の人のリポジトリのファイルをベースにしながら作りました。
ios,android以外にもmac, windowsなどもあったのですが、邪魔だったのでそのへんは抹消しました。
こんなワークフローになってます。
iOSのビルド長すぎぃぃ。。
お待ちかね実体ファイルはこんな感じになってます。name: Test, Build, and Release CGS on: # push: { branches: [master] } #masterにpushされたときにやりたい人はこちらをお使いください release: { types: [published] } env: UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} BUILD_NUMBER: ${{ github.run_number }} jobs: tests: name: Test Code Quality runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: Checkout Repository uses: actions/checkout@v2 with: fetch-depth: 0 - name: Cache Library uses: actions/cache@v2 with: path: Library key: Library - name: Run EditMode and PlayMode Tests uses: game-ci/unity-test-runner@main - name: Publish Test Results if: ${{ always() }} # Avoid skipping on failed tests uses: davidmfinol/unity-test-publisher@main with: githubToken: ${{ secrets.GITHUB_TOKEN }} buildWithLinux: name: Build for ${{ matrix.targetPlatform }} by Unity runs-on: ubuntu-latest timeout-minutes: 90 needs: tests strategy: fail-fast: false matrix: targetPlatform: - Android - iOS steps: - name: Checkout Repository uses: actions/checkout@v2 with: fetch-depth: 0 lfs: true - name: Cache Library uses: actions/cache@v2 with: path: | Library build/${{ matrix.targetPlatform }} key: Library-${{ matrix.targetPlatform }}- restore-keys: Library- - name: Free Disk Space for Android if: matrix.targetPlatform == 'Android' run: | sudo swapoff -a sudo rm -f /swapfile sudo apt clean docker rmi $(docker image ls -aq) df -h - name: Build Unity Project uses: game-ci/unity-builder@main with: customParameters: -buildNumber ${{ github.run_number }} targetPlatform: ${{ matrix.targetPlatform }} buildMethod: Cgs.Editor.BuildCgs.BuildOptions androidAppBundle: true androidKeystoreName: keystore.keystore androidKeystoreBase64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} androidKeystorePass: ${{ secrets.ANDROID_KEYSTORE_PASS }} androidKeyaliasName: ${{ secrets.ANDROID_KEYALIAS_NAME }} androidKeyaliasPass: ${{ secrets.ANDROID_KEYALIAS_PASS }} - name: Upload Build uses: actions/upload-artifact@v2 if: github.event.ref != 'refs/heads/develop' with: name: cgs-${{ matrix.targetPlatform }} path: build/${{ matrix.targetPlatform }} releaseToGooglePlay: name: Release to the Google Play Store runs-on: ubuntu-latest timeout-minutes: 60 needs: buildWithLinux if: github.event.action == 'published' env: GOOGLE_PLAY_KEY_FILE: ${{ secrets.GOOGLE_PLAY_KEY_FILE }} GOOGLE_PLAY_KEY_FILE_PATH: ${{ format('{0}/fastlane/api-finoldigital.json', github.workspace) }} ANDROID_BUILD_FILE_PATH: ${{ format('{0}/build/Android/Android.aab', github.workspace) }} ANDROID_PACKAGE_NAME: com.sample.app # 自分のアプリのパッケージ名に合わせよう! RELEASE_NOTES: ${{ github.event.release.body }} steps: - name: Checkout Repository uses: actions/checkout@v2 - name: Download Android Artifact uses: actions/download-artifact@v2 with: name: cgs-Android path: build/Android - name: Prepare for Upload run: | echo "$GOOGLE_PLAY_KEY_FILE" > $GOOGLE_PLAY_KEY_FILE_PATH echo "$RELEASE_NOTES" > fastlane/metadata/android/en-US/changelogs/default.txt - name: Upload to Google Play uses: maierj/fastlane-action@v1.4.0 with: lane: 'android playstore' buildIOS: name: Build Archive for iOS runs-on: macos-latest timeout-minutes: 60 needs: buildWithLinux if: github.event.action == 'published' env: APPLE_CONNECT_EMAIL: ${{ secrets.APPLE_CONNECT_EMAIL }} APPLE_DEVELOPER_EMAIL: ${{ secrets.APPLE_DEVELOPER_EMAIL }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_TEAM_NAME: ${{ secrets.APPLE_TEAM_NAME }} APPLE_ITC_TEAM_ID: ${{ secrets.APPLE_ITC_TEAM_ID }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} MATCH_PERSONAL_ACCESS_TOKEN: ${{ secrets.MATCH_PERSONAL_ACCESS_TOKEN }} IOS_APP_ID: com.sample.app # bundle idを指定します IOS_BUILD_PATH: ${{ format('{0}/build/iOS', github.workspace) }} PROJECT_NAME: onenitejinro RELEASE_NOTES: ${{ github.event.release.body }} MATCH_REPOSITORY_ACCOUNT: ${{ secrets.MATCH_REPOSITORY_ACCOUNT }} USYM_UPLOAD_AUTH_TOKEN: 'fake' # ビルドが途中でこけるのでfake用に環境変数を追加。 steps: - name: Checkout Repository uses: actions/checkout@v2 - name: Cache restore for debug uses: actions/cache@v2 with: path: | Library build/iOS key: Library-iOS- restore-keys: Library- - name: Download iOS Artifact uses: actions/download-artifact@v2 with: name: cgs-iOS path: build/iOS - name: Cache restore cocoapods # firebase使ってるとcocoapodsを使うのですが、cocoapodsのキャッシュとらないと毎回時間かかるのでキャッシュしておきます。 uses: actions/cache@v2 if: ${{ always() }} with: path: | build/iOS/iOS/Pods ~/.cocoapods/repos key: Pods-${{ hashFiles('**/Podfile') }} restore-keys: Pods- - uses: actions/setup-ruby@v1 with: ruby-version: '2.7.2' - name: Prepare for fastlane # GateKeeper対策 run: | sudo spctl --master-disable - name: Archive iOS uses: maierj/fastlane-action@v2.0.1 with: lane: 'ios build' - name: run if fail_step failed # ビルドがコケた原因がわかるようにcatしておきます if: failure() run: cat /Users/runner/Library/Logs/gym/*Unity-iPhone.log - name: Upload Build uses: actions/upload-artifact@v2 if: github.event.ref != 'refs/heads/develop' with: name: ipa path: | ${{ github.workspace }}/*.ipa ${{ github.workspace }}/*.dSYM.zip releaseToAppStore: name: Release to the App Store runs-on: macos-latest timeout-minutes: 60 needs: buildIOS if: github.event.action == 'published' env: APPLE_CONNECT_EMAIL: ${{ secrets.APPLE_CONNECT_EMAIL }} APPLE_DEVELOPER_EMAIL: ${{ secrets.APPLE_DEVELOPER_EMAIL }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_TEAM_NAME: ${{ secrets.APPLE_TEAM_NAME }} APPLE_ITC_TEAM_ID: ${{ secrets.APPLE_ITC_TEAM_ID }} FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }} FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} MATCH_PERSONAL_ACCESS_TOKEN: ${{ secrets.MATCH_PERSONAL_ACCESS_TOKEN }} ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} ASC_KEY_CONTENT: ${{ secrets.ASC_KEY_CONTENT }} IOS_APP_ID: com.sample.app # bundle idを指定します IOS_BUILD_PATH: ${{ format('{0}/build/iOS', github.workspace) }} PROJECT_NAME: onenitejinro RELEASE_NOTES: ${{ github.event.release.body }} MATCH_REPOSITORY_ACCOUNT: ${{ secrets.MATCH_REPOSITORY_ACCOUNT }} steps: - name: Checkout Repository uses: actions/checkout@v2 - name: Download iOS Artifact uses: actions/download-artifact@v2 with: name: ipa path: | ${{ github.workspace }}/*.ipa ${{ github.workspace }}/*.dSYM.zip - name: Upload to the App Store uses: maierj/fastlane-action@v1.4.0 with: lane: 'ios release'このファイルのポイントを話しておくと、linuxでビルドが終わった後、fastlaneを使ってandroid/iosともにビルドもしくはアップロードを行っています。
iosは結構曲者で、UnityでCLIビルドが結構大変なので随所随所にビルドできるような仕組みを置いてます。
fastlaneの設置
fastlaneで使うファイルを参考ファイルをベースにカスタマイズした下記を設置しています。
keychain_name = "temporary_keychain" keychain_password = SecureRandom.base64 platform :android do desc "Upload a new Android version to the Google Play Store" lane :playstore do upload_to_play_store( aab: "#{ENV['ANDROID_BUILD_FILE_PATH']}", track: 'internal', skip_upload_screenshots: true, skip_upload_images: true ) end end platform :ios do desc "Push a new release build to the App Store" lane :release do api_key = app_store_connect_api_key( key_id: ENV['ASC_KEY_ID'], # your key id issuer_id: ENV['ASC_ISSUER_ID'], # your issuer id key_content: ENV['ASC_KEY_CONTENT'], # your secret key body # ex) key_content: '-----BEGIN PRIVATE KEY-----\nfoobar\n-----END PRIVATE KEY-----' ) upload_to_app_store( api_key: api_key, # pass api_key force: true, skip_screenshots: true, skip_metadata: true ) end desc "Submit a new Beta Build to Apple TestFlight" lane :beta do api_key = app_store_connect_api_key( key_id: ENV['ASC_KEY_ID'], # your key id issuer_id: ENV['ASC_ISSUER_ID'], # your issuer id key_content: ENV['ASC_KEY_CONTENT'], # your secret key body # ex) key_content: '-----BEGIN PRIVATE KEY-----\nfoobar\n-----END PRIVATE KEY-----' ) upload_to_testflight( api_key: api_key, # pass api_key skip_waiting_for_build_processing: true ) end desc "Create .ipa" lane :build do cocoapods(podfile: "#{ENV['IOS_BUILD_PATH']}/iOS/Podfile") disable_automatic_code_signing(path: "#{ENV['IOS_BUILD_PATH']}/iOS/Unity-iPhone.xcodeproj") certificates update_project_provisioning( xcodeproj: "#{ENV['IOS_BUILD_PATH']}/iOS/Unity-iPhone.xcodeproj", target_filter: "Unity-iPhone", profile: ENV["sigh_#{ENV['IOS_APP_ID']}_appstore_profile-path"], # より動的にみるようにしています code_signing_identity: "Apple Distribution: #{ENV['APPLE_TEAM_NAME']} (#{ENV['APPLE_TEAM_ID']})" ) gym( workspace: "#{ENV['IOS_BUILD_PATH']}/iOS/Unity-iPhone.xcworkspace", scheme: "Unity-iPhone", clean: true, #clean: false, skip_profile_detection: true, codesigning_identity: "Apple Distribution: #{ENV['APPLE_TEAM_NAME']} (#{ENV['APPLE_TEAM_ID']})", export_method: "app-store", export_options: { method: "app-store", provisioningProfiles: { ENV["IOS_APP_ID"] => "match AppStore #{ENV['IOS_APP_ID']}" } } ) end desc "Synchronize certificates" lane :certificates do cleanup_keychain create_keychain( name: keychain_name, password: keychain_password, default_keychain: true, lock_when_sleeps: true, timeout: 3600, unlock: true ) match( type: "appstore", readonly: true, keychain_name: keychain_name, keychain_password: keychain_password ) end lane :cleanup_keychain do if File.exist?(File.expand_path("~/Library/Keychains/#{keychain_name}-db")) delete_keychain(name: keychain_name) end end after_all do if File.exist?(File.expand_path("~/Library/Keychains/#{keychain_name}-db")) delete_keychain(name: keychain_name) end end end元ファイルからcocoapodsのインストールや、api keyまわりの設定、provisioning profileの指定方法などが若干違います。
また、問題を切り分けやすくするためにビルドとapp storeにリリースする処理はわけたりしています。
参考元ファイルでは現在、apikey周りは証明書をおいていい感じにやるようにしているみたいです。
他にfastlaneに付随するファイルも置いていきます
- Appfile
for_platform :android do package_name(ENV["ANDROID_PACKAGE_NAME"]) json_key_file(ENV["GOOGLE_PLAY_KEY_FILE_PATH"]) end for_platform :ios do app_identifier(ENV["IOS_APP_ID"]) apple_dev_portal_id(ENV["APPLE_DEVELOPER_EMAIL"]) # Apple Developer Account itunes_connect_id(ENV["APPLE_CONNECT_EMAIL"]) # App Store Connect Account team_id(ENV["APPLE_TEAM_ID"]) # Developer Portal Team ID itc_team_id(ENV["APPLE_ITC_TEAM_ID"]) # App Store Connect Team ID end
- Deliverfile
submit_for_review false automatic_release true force true skip_screenshots true release_notes({ 'default' => ENV["RELEASE_NOTES"], 'en-US' => ENV["RELEASE_NOTES"] }) run_precheck_before_submit false submission_information({ add_id_info_uses_idfa: false, export_compliance_compliance_required: false, export_compliance_encryption_updated: false, export_compliance_app_type: nil, export_compliance_uses_encryption: false, export_compliance_is_exempt: false, export_compliance_contains_third_party_cryptography: false, export_compliance_contains_proprietary_cryptography: false, export_compliance_available_on_french_store: false });
- Matchfile
git_url("https://github.com/iosのcertificateおいてるgitリポジトリ") # sshのurlではなく、httpsで指定してます。 git_basic_authorization(Base64.strict_encode64("#{ENV['MATCH_REPOSITORY_ACCOUNT']}:#{ENV['MATCH_PERSONAL_ACCESS_TOKEN']}")) storage_mode("git") type("appstore") # The default type, can be: appstore, adhoc, enterprise or development app_identifier(["com.sample.app"]) # bundleIDを指定しよう username("apple@example.com") # Your Apple Developer Portal usernameprojectの準備
BuildCgs.csをAssets/Scripts/Cgs/Editorディレクトリに置いておく。
このファイルがビルド番号の設定などをしてくれている。もぐめっとの場合、CIのビルド番号とアプリのビルド番号を紐付けしたかったので冒頭部分だけbuildNumberを参照するように少し修正しました。
修正後BuildCgs.csPlayerSettings.macOS.buildNumber = options["buildNumber"]; PlayerSettings.iOS.buildNumber = options["buildNumber"]; PlayerSettings.Android.bundleVersionCode = int.Parse(options["buildNumber"]); PlayerSettings.WSA.packageVersion = new Version(options["buildVersion"]);metadataの準備
metadataがないとfastlane途中でこけるため準備しておく。
androidの準備
fastlane run download_from_play_store json_key:<json path>jsonファイルはストアにアクセスするために必要なものになるが、こちらの記事に解説は委ねます。
iosの準備
fastlane deliver download_metadata上記で生成されたファイルをコミットしておく
githubのsecrets設定
先術したymlやfastfileなどを見てもらったとおり、環境変数をふんだんに使うので、その準備をしていきます。
game-ci/unity-builder周りで使う環境変数
細かいことはGame CIのドキュメントをご参照ください
Unityのアカウント周りの情報は下記の変数になります
- UNITY_EMAIL
- UNITY_PASSWORD
- UNITY_SERIAL
unityのserialについてはunityのwebサイトにログインして確認します。
Androidの公開設定周りはこちらの変数になります
- ANDROID_KEYALIAS_NAME
- ANDROID_KEYALIAS_PASS
- ANDROID_KEYSTORE_BASE64
- ANDROID_KEYSTORE_PASS
上記四点はandroidの公開設定でも使うこの辺の情報ですね。
keystoreの作り方は公式に委ねます
fastlaneのアップロード周りで使ってる環境変数
アップロードするときなどに使われるfastlaneのDeliverで使われるAppfileで定義して使ってる一覧です。
- APPLE_CONNECT_EMAIL
App Store Connect Accountにアクセスできるアカウントを指定します
- APPLE_DEVELOPER_EMAIL
Apple Developer Accountにアクセスできるアカウントを指定します。
- APPLE_TEAM_ID
- APPLE_TEAM_NAME
上記2点はDeveloper Portalのサイトでアクセスできる項目を確認します
- APPLE_ITC_TEAM_ID
APPLE_TEAM_IDではなくて、app store connectで使うアカウントのほうのIDになるらしい。とりあえずfastlane使えるならspaceship使えば取得できます。
- ASC_ISSUER_ID
- ASC_KEY_CONTENT
- ASC_KEY_ID
Appleの2段階認証を突破してアップロードできるようにする用の変数です。解説は下記記事に委ねます。
- FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD
- FASTLANE_PASSWORD
先程のAppStoreConnect API Keyを使った方法でできると思うので、おそらくこの2点は設定しなくてもいいと思いますが、念の為設定しておきます。
設定方法はこちらに。
- GOOGLE_PLAY_KEY_FILE
metadataをダウンロードする時お話した、jsonファイルの中身をはっつけます。
再掲)
fastlane match周りの環境変数
fastlane matchについてはこちらをご参照ください。証明書を管理するやつです。
- MATCH_PASSWORD
matchに設定してるパスワードを指定します。
- MATCH_PERSONAL_ACCESS_TOKEN
- MATCH_REPOSITORY_ACCOUNT
上記2つはMatchfileで指定したgit_basic_authorizationに使うものなのですが、repositoryのcloneがgithub action上でできるようにするために使っています。
MATCH_PERSONAL_ACCESS_TOKENはgithubのaccess tokenを指定します。
MATCH_REPOSITORY_ACCOUNTはMatchのリポジトリにアクセスできるアカウントを指定します。(access tokenを発行したアカウント名ですね)
使い方
ようやく設定がおわりました。
このworkflowはreleaseを切ったときに初めて発火されます。
Draft a new releaseボタンからリリースを作りましょう!
成功すればreleaseで書いた内容でappstoreとplay storeにリリースされるはずです!
まとめ
というわけでgame ciさんのunity-builderを使うことでgithub actionsでunityのCIを回すことができるようになりました!
unityはビルドに時間かかるので結構めんどくさかったのですがこの辺が省略できるようになるのはとても楽になりました。みなさんの開発にあてる時間がこれで増えたら幸いです!
最後に、ワンナイト人狼オンラインというゲームを作ってます!よかったら遊んでね!
他にもcameconやoffchaといったサービスも作ってるのでよかったら使ってね!
- 投稿日:2021-03-21T16:22:11+09:00
VLC for Android:楽曲管理画面の解析
はじめに
VLC for Androidの楽曲管理画面を解析する。ゴールは登場するクラスの洗い出しと関連性のざっくりとした把握。
各種リンク
楽曲管理画面のスクリーンショット
WIP
楽曲管理画面のオブジェクト図
こんな感じ。赤いオブジェクト図は楽曲管理部分
ざっくりいうとリポジトリクラスの状態を監視して、各モデルクラスのインスタンスを取り出してUIに反映している。楽曲管理画面を構成する主なクラス
UI部分
- AudioBrowserFragment:楽曲管理を表現するフラグメント
- AudioPagerAdapter:ジャンル、楽曲、アルバム、アーティスト用画面を切り替えるViewPager用のアダプター
- AudioBrowserAdapter:ビューページャが管理するRecyclerViewに紐づくアダプター。ジャンル、楽曲、アルバム、アーティスト用の4つある。
- AudioBrowserViewModel:〜Providerクラスのインスタンスを管理するViewModel
- ArtistsProvider:MediaLibraryを監視してArtistを供給する。
- AlbumsProvider:MediaLibraryを監視してAlbumを供給する。
- TracksProvider:MediaLibraryを監視してTrackを供給する。
- GenresProvider:MediaLibraryを監視してGenreを供給する。
モデル部分
- MediaWrapper:音楽ファイルを表現する。
- Album:アルバムを表す。
- Arist:アーティストを表す。
- Genre:ジャンル
- MediaLibrary:上記4つのインスタンスを供給する。所謂リポジトリクラス。シングルトン。
- 投稿日:2021-03-21T14:30:12+09:00
[Android] Jetpack DataBinding
Android Studio 4.1.3(windows版) での流れとなります
Jetpack DataBindingでボタンイベントを設定する方法を例にして説明します
Android DeveloperのJetpack DataBindingの説明は以下です
準備
以下を参考にJetpack Navigation + ViewModelを作成してください
DataBindingを使用するための設定
build.gradleに以下を追加します
DataBindingを有効にするとBindingクラスが自動生成されますbuild.gradleandroid { dataBinding { enabled = true } }ViewModelでの準備
ボタンイベントを受けるメソッドを用意します
MainViewModel.javapublic void onClickButton() { }FragmentのLayoutの準備
ここではFrameLayoutをConstraintLayoutにしてid:buttonのボタンを追加します
※ Convert FraneLayout to ConstraintLayoutでも変更できますCodeを開きConstraintLayoutタグの箇所でwindowsの場合はAlt+Enterなどで
Convert to data bindinglayout を選択します
追加されたdataタグにViewModelを追加します
main_fragment.xml<data> <variable name="viewModel" type="com.xxx.sample.MainViewModel" /> </data>ボタンのonClickに以下を追加します
※DesignまたはCodeで直接追加してください@{() -> viewModel.onClickButton()}Fragmentでの準備
DataBindingの準備とDataBindingにViewModelをセットします
MainFragment.java// ViewModelはFragment作成時のテンプレートで自動生成されたものです private MainViewModel mViewModel; // 自動生成されます(main_fragment.xml) private MainFragmentBinding binding; @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { binding = DataBindingUtil.inflate(inflater, R.layout.main_fragment, container, false); final View view = binding.getRoot(); return view; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mViewModel = new ViewModelProvider(this).get(MainViewModel.class); // TODO: Use the ViewModel binding.setViewModel(mViewModel); }この記事は以下の記事の補足です
- 投稿日:2021-03-21T14:30:12+09:00
[Android/Java] Jetpack DataBinding
Android Studio 4.1.3(windows版) での流れとなります
Jetpack DataBindingでボタンイベントを設定する方法を例にして説明します
Android DeveloperのJetpack DataBindingの説明は以下です
準備
以下を参考にJetpack Navigation + ViewModelを作成してください
DataBindingを使用するための設定
build.gradleに以下を追加します
DataBindingを有効にするとBindingクラスが自動生成されますbuild.gradleandroid { dataBinding { enabled = true } }ViewModelでの準備
ボタンイベントを受けるメソッドを用意します
MainViewModel.javapublic void onClickButton() { }FragmentのLayoutの準備
ここではFrameLayoutをConstraintLayoutにしてid:buttonのボタンを追加します
※ Convert FraneLayout to ConstraintLayoutでも変更できますCodeを開きConstraintLayoutタグの箇所でwindowsの場合はAlt+Enterなどで
Convert to data bindinglayout を選択します
追加されたdataタグにViewModelを追加します
main_fragment.xml<data> <variable name="viewModel" type="com.xxx.sample.MainViewModel" /> </data>ボタンのonClickに以下を追加します
※DesignまたはCodeで直接追加してください@{() -> viewModel.onClickButton()}Fragmentでの準備
DataBindingの準備とDataBindingにViewModelをセットします
MainFragment.java// ViewModelはFragment作成時のテンプレートで自動生成されたものです private MainViewModel mViewModel; // 自動生成されます(main_fragment.xml) private MainFragmentBinding binding; @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { binding = DataBindingUtil.inflate(inflater, R.layout.main_fragment, container, false); final View view = binding.getRoot(); return view; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mViewModel = new ViewModelProvider(this).get(MainViewModel.class); // TODO: Use the ViewModel binding.setViewModel(mViewModel); }この記事は以下の記事の補足です
- 投稿日:2021-03-21T12:38:28+09:00
Firebaseの関連するユーザデータ削除を簡単に実現してみる【Authentication,Firestore,Functions】
前置き
Firebase Authenticationで、クライアント側から簡単にユーザ登録できるんだから、削除も簡単でしょうと思ってた愚か者のお話です。
ログイン中のユーザインスタンスからdeleteメソッドを叩けばユーザ削除も簡単だよね!やってみよう!
mAuth.getCurrentUser().delete().addOnCompleteListener(task -> { if (task.isSuccessful()) { Log.d("DEBUG","Successful to delete user."); } else { Log.e("DEBUG", "Failed to delete user.", task.getException()); } });2021-03-13 11:04:38.569 6841-6841/com.crabsan.anshare.debug E/DEBUG: Failed to delete user. com.google.firebase.auth.FirebaseAuthRecentLoginRequiredException: This operation is sensitive and requires recent authentication. Log in again before retrying this request. at com.google.android.gms.internal.firebase-auth-api.zztt.zza(com.google.firebase:firebase-auth@@20.0.2:22) at com.google.android.gms.internal.firebase-auth-api.zzvb.zza(com.google.firebase:firebase-auth@@20.0.2:9) at com.google.android.gms.internal.firebase-auth-api.zzvc.zzk(com.google.firebase:firebase-auth@@20.0.2:1)上記コードだとエラーに。ログを読むと、ユーザ削除等のセキュリティ上重要な操作には再認証が必要とのこと。
再度ドキュメントを読み直してみると、がっつり書いてましたw
ログ通りの実装に変更するにはSSO毎に別途トークンの取得処理が必要のため、複雑なロジックとなりそうです。
また、その他下記理由から別の方法を取りました。
- ユーザの再認証(トークン取得等)をすれば実現可能だが、例外処理のパターンが増えクライアント側の処理が複雑になる
- Authenticationで管理しているユーザ削除と同時に、Firestore(DB)に保存しているユーザデータも削除したい
- ユーザ削除した履歴も同時に残したい
この記事では、これらを実現するためにしたことをまとめています。
結論
Firebase AuthenticationやFirestoreに登録されたユーザデータを一括で削除するため、下記構成にしてみました。
こちらの構成にすることで以下メリットがありました。
- クライアント側でFirebase Authenticationの再認証が不要
- クライアント側の例外処理が簡易(ここが1番のメリット
)
- Backgroundで全ユーザ情報を削除できるため、クライアント側の負担減
- ユーザ削除履歴も処理の流れで生成できる
① 削除するUIDをFirestoreに登録
クライアント側はFirestoreに削除するユーザIDと削除日を書き込む。
これだけです!!
関連データの削除等もFirebase Cloud Functionsに委譲しており、簡単に実装可能です。// 削除履歴データ生成 final Map<String, Object> deleteData = new HashMap<>(); deleteData.put("uid", uid); deleteData.put("ctAt", FieldValue.serverTimestamp()); // Firestoreにデータ登録 mFirestore .collection('deleted_users') .add(deleteData) .addOnCompleteListener(mThreadExecutor, task -> { if (task.isSuccessful()) { // ユーザ削除完了後の処理 } else { // ユーザ削除失敗のため、UIへ失敗通知 } });{ uid: "xxx" createdAt: 1615600018000 }注意点としては、Firestoreのセキュリティールールをしっかり導入しておくこと。
Firestoreの特定コレクションにデータ追加さえすればユーザ削除ができてしまうので、悪用される懸念は当然あります。
自分は以下ルールを設定し、リクエストしたユーザしか自身のデータを追加できない制約を与えています。
- 登録するドキュメントのuidとリクエストのuidが同じであること
- ドキュメント内のデータ数が同じであること
- 登録日時がサーバ時刻と極端に離れていないこと
②③ ドキュメント登録をトリガーに、Function経由でAuthenticationからユーザ削除
Firestore Functionsに関数を登録します。
①のドキュメント追加(onCreate)をトリガーに、Authenticationのユーザ情報を削除する流れです。
とても簡単ですね。exports.deleteUser = functions .region('asia-northeast1') .firestore .document('deleted_users/{docId}') .onCreate(async (snap, context) => { const deleteDocument = snap.data(); const uid = deleteDocument.uid; await auth.deleteUser(uid); })④⑤ Authenticationのユーザ削除をトリガーに、Function経由でユーザ情報削除
②③でAuthenticationのユーザが削除された事をトリガーに、Firestore(DB)に保存されているユーザ情報をFunctionsで全削除します。
こちらは、Firebase側が提供している Delete User Data Extensionsを利用します。
Delete User Data Extensionsを利用することで、Authenticationのユーザが削除されたをトリガーに、UIDに紐づく指定したサブコレクションをキレイに削除可能です。
また、下図のようにUI上で削除対象を登録することができます。
②③のような、Functionsのコードを自前で管理する必要もないので、オススメです!まとめ
Firebase Authentication、Firestoreのユーザデータ削除方法をまとめてみました。
個人Androidアプリ開発で困った箇所なので、同じ悩みを持たれている方の参考になれば嬉しいです!※ 内容に誤りやこの構成マズイんじゃない?というご意見あれば、コメントやTwitterでご連絡いただけるととても助かります
- 投稿日:2021-03-21T11:11:34+09:00
Android ナビゲーションバー透過
app/res/values/themers/themes.xml<item name="android:navigationBarColor">@android:color/transparent</item> <item name="android:windowFullscreen">true</item> <item name="android:windowDrawsSystemBarBackgrounds">true</item>MyActivity.ktoverride fun onCreate(savedInstanceState: Bundle?) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { window.setFlags( WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS ) } }
- 投稿日:2021-03-21T06:58:13+09:00
AndroidStudioでメモリ容量が足りないときの対処法
AndroidStudioでメモリ容量が足りないときの対処法
筆者がAndroidStudioでアプリを作成しているときに、AndroidStudio側から「メモリの容量が少なくなっている」との指摘がありました。
そのときに作っていたアプリですが、変数の数が非常に多いアプリだったので、作業中に打ち込んだコードの読み込み時間が長くなってしまって、効率よく作業を行うことができなくなってしまいました。
原因を調べてみると、改善策がいくつか見つかりましたが、その中で実際に行ったら読み込み時間が改善されて、最も簡単だった方法を載せます。AndroidStudio起動時の「Welcome to Android Studio」ダイアログ右下の「Configure」をクリックして、「Preferences」を選択する。
「Appearance & Behavior」内の「System Settings」から、「Memory Settings」を選択する。
「IDE max heap size:」から、AndroidStudio使用時に使えるメモリの最大容量を変更できるので、現在よりも大きい値を選ぶ。(デフォルト値は1280だったので、2048を選択してみました)
変更するまでコードを記述するごとに読み込みが発生してしまって、作業が遅くなっていましたが、上記を変更することで読み込み時間が短くなりました。
参考になれば幸いです。