20210303のAndroidに関する記事は5件です。

error Runner.app/Info.plist does not exist. The Flutter "Thin Binary" build phase must run after "Copy Bundle Resources".が出たら

ここを参考にした
ちなみに自分はAndroid StudioでClean Build Folderだけでなおった

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android Studio 電卓アプリ制作

出来るだけコードが少なくなるように電卓アプリを作成しました。
改善点などご教授いただけますと幸いです。
YouTubeに作っている動画をアップしています。
https://www.youtube.com/watch?v=kvz7gLP3SHI&list=PLhg2PHSq8bjisIZGg-cLe4TegqJTCBXhS

strings.xml
<resources>
    <string name="app_name">CalculateApp</string>
    <string name="btn_zero">0</string>
    <string name="btn_one">1</string>
    <string name="btn_two">2</string>
    <string name="btn_three">3</string>
    <string name="btn_four">4</string>
    <string name="btn_five">5</string>
    <string name="btn_six">6</string>
    <string name="btn_seven">7</string>
    <string name="btn_eight">8</string>
    <string name="btn_nine">9</string>
    <string name="btn_plus">+</string>
    <string name="btn_minus">-</string>
    <string name="btn_times">×</string>
    <string name="btn_divide">÷</string>
    <string name="btn_clear">C</string>
    <string name="btn_point">.</string>
    <string name="btn_equal">=</string>
</resources>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:weightSum="7"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text_formula"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="right"
        android:textSize="36sp"></TextView>

    <TextView
        android:id="@+id/text_result"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="right"
        android:textSize="36sp"
        android:textStyle="bold"></TextView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal"
        android:weightSum="4">

        <Space
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"></Space>

        <Space
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"></Space>

        <Space
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"></Space>

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:text="@string/btn_clear"
            android:onClick="tapClear"
            android:textSize="36sp"></Button>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal"
        android:weightSum="4">

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:text="@string/btn_seven"
            android:onClick="tapSeven"
            android:textSize="36sp"></Button>

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:text="@string/btn_eight"
            android:onClick="tapEight"
            android:textSize="36sp"></Button>

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:text="@string/btn_nine"
            android:onClick="tapNine"
            android:textSize="36sp"></Button>

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:text="@string/btn_divide"
            android:onClick="tapDivide"
            android:textSize="36sp"></Button>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal"
        android:weightSum="4">

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:text="@string/btn_four"
            android:onClick="tapFour"
            android:textSize="36sp"></Button>

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:text="@string/btn_five"
            android:onClick="tapFive"
            android:textSize="36sp"></Button>

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:text="@string/btn_six"
            android:onClick="tapSix"
            android:textSize="36sp"></Button>

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:text="@string/btn_times"
            android:onClick="tapTimes"
            android:textSize="36sp"></Button>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal"
        android:weightSum="4">

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:text="@string/btn_one"
            android:onClick="tapOne"
            android:textSize="36sp"></Button>

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:text="@string/btn_two"
            android:onClick="tapTwo"
            android:textSize="36sp"></Button>

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:text="@string/btn_three"
            android:onClick="tapThree"
            android:textSize="36sp"></Button>

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:text="@string/btn_minus"
            android:onClick="tapMinus"
            android:textSize="36sp"></Button>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal"
        android:weightSum="4">

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:text="@string/btn_zero"
            android:onClick="tapZero"
            android:textSize="36sp"></Button>

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:text="@string/btn_point"
            android:onClick="tapPoint"
            android:textSize="36sp"></Button>

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:text="@string/btn_equal"
            android:onClick="tapEqual"
            android:textSize="36sp"></Button>

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:text="@string/btn_plus"
            android:onClick="tapPlus"
            android:textSize="36sp"></Button>
    </LinearLayout>

</LinearLayout>
MainActivity.java
package com.example.calculateapp;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    TextView formula, result;
    List<String> symbols;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        formula = findViewById(R.id.text_formula);
        result = findViewById(R.id.text_result);
        symbols = new ArrayList<String>();
    }

    public void tapZero(View view) {
        formula.setText(formula.getText().toString() + "0");
    }

    public void tapOne(View view) {
        formula.setText(formula.getText().toString() + "1");
    }

    public void tapTwo(View view) {
        formula.setText(formula.getText().toString() + "2");
    }

    public void tapThree(View view) {
        formula.setText(formula.getText().toString() + "3");
    }

    public void tapFour(View view) {
        formula.setText(formula.getText().toString() + "4");
    }

    public void tapFive(View view) {
        formula.setText(formula.getText().toString() + "5");
    }

    public void tapSix(View view) {
        formula.setText(formula.getText().toString() + "6");
    }

    public void tapSeven(View view) {
        formula.setText(formula.getText().toString() + "7");
    }

    public void tapEight(View view) {
        formula.setText(formula.getText().toString() + "8");
    }

    public void tapNine(View view) {
        formula.setText(formula.getText().toString() + "9");
    }

    public void tapPoint(View view) {
        formula.setText(formula.getText().toString() + ".");
    }

    public void tapPlus(View view) {
        formula.setText(formula.getText().toString() + "+");
        symbols.add("+");
    }

    public void tapMinus(View view) {
        formula.setText(formula.getText().toString() + "-");
        symbols.add("-");
    }

    public void tapTimes(View view) {
        formula.setText(formula.getText().toString() + "×");
        symbols.add("×");
    }

    public void tapDivide(View view) {
        formula.setText(formula.getText().toString() + "÷");
        symbols.add("÷");
    }

    public void tapClear(View view) {
        formula.setText("");
        result.setText("");
        symbols.clear();
    }

    public void tapEqual(View view) {
        BigDecimal tmp;
        BigDecimal numResult = new BigDecimal(0);
        String TextFormula = formula.getText().toString();
        List<String> nums = Arrays.asList(TextFormula.split("[\\+\\-×÷]", -1));
        List<BigDecimal> numbers = new ArrayList<BigDecimal>();
        try {
            if ((nums.get(0).length() == 0) && (symbols.get(0) == "-")) {
                nums.set(0, "0");
            }
            for (String num : nums){
                numbers.add(new BigDecimal(num));
            }
            for (int i = 0; i < symbols.size(); i++) {
                switch (symbols.get(i)) {
                    case "×":
                        tmp = numbers.get(i).multiply(numbers.get(i+1));
                        numbers.set(i,new BigDecimal(0));
                        numbers.set(i + 1, tmp);
                        break;
                    case "÷":
                        tmp = numbers.get(i).divide(numbers.get(i+1),2,BigDecimal.ROUND_HALF_UP);
                        numbers.set(i,new BigDecimal(0));
                        numbers.set(i + 1, tmp);
                        break;
                    case "-":
                        numbers.set(i+1,numbers.get(i+1).multiply(new BigDecimal(-1)));
                        break;
                }
            }
            for (BigDecimal num : numbers) {
                numResult = numResult.add(num);
            }
            result.setText(numResult.toString());
        } catch (Exception e) {
            Log.e("エラー", e.getMessage());
        }
    }
}

縦画面に固定したいので、マニフェストに追記

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.calculateapp">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.CalculateApp">
        <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait">//縦画面固定
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

YouTubeに作っている動画をアップしています。
https://www.youtube.com/watch?v=kvz7gLP3SHI&list=PLhg2PHSq8bjisIZGg-cLe4TegqJTCBXhS
以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android StudioでリリースAPK(署名付き)を作成してみた。

Android StudioでリリースAPK(署名付き)を作成してみた。

最初に

Build Variantをrelease設定してBuild APK(s)を実行して署名付きリリースAPKを作成する時の手順の記事となります。
また今回の記事はリリースAPKを作成して端末にインストールしてみたので自分の覚書用に
作成した物なので、参考になるかどうかは保証しかねるのでご了承の上お読みください。
*画面スクショは気が向いたら後日追加するかも?

1.KeyStoreを作成

Build -> Generate Signed Bundle or apk をクリック
*もしかしたら...で省略されてるかも

開いたウィンドウでapk を選んで次へ

「key store path」の編集ボックスの下のボタンをクリック(Create New ...)

keyStoreの作成画面が開くので以下の項目を入力してOKをクリック
・Key Store path :KeyStoreの保存場所(path/xxx.jks)
  *pathだけでなくkeyStore名もここで記述する必要あり
  *gradleにstorePasswordの変数名?で記述する
・Password :キーストアのパスワード。署名設定時に必要。6文字以上必要。
・Alias : キーを識別する任意の名前。
  *gradleにkeyAliasの変数名?で記述する
・PassWord(key) : キーのパスワード。
  *gradleにkeyPasswordの変数名?で記述する
・Validity(Yeas) : 有効年数
・Certificate :証明書の所有者に関する情報。どれか一つに入力が必要。今回は「First and ...」に入力した
*他にも入力項目はあるが上記の物を入力したらとりあえずは作成できた

とりあえずエラーが出るが、無視して入力したpathにkeyStoreが作成されている事が確認できたら
ウィンドウを閉じてしまってOK(APKはgradle設定後にBuild Apkで作成するから)

2.Build.gradle(app)に追記

android{
    (略)
     ・
     ・
    signingConfigs {
        release {
            storeFile file(“xxx/xxx.jks”)
            // KeyStore作成時に設定した値を覚えておく必要有り
            storePassword ‘XXXXX’
            keyAlias ‘XXXX’
            keyPassword ‘XXXX’
        }
    }

    buildTypes {
       release {
             (略)
              ・
              ・
          signingConfig signingConfigs.release
       }
    }
    // 署名付きAPKを作ろうとするとLintエラーが出るのでその対策
    lintOptions {
        checkReleaseBuilds false
        abortOnError false
    }

}

上記の物を追加したらSyncを実行する

3.署名付きリリースAPKを作成

Build Variant を releaseに設定
Build-> Build Bundle(s)/APK(s)->Build APK(s)をクリックして
署名付きリリースAPKを作成
作成が終了したらAndroid studioの何処かにBuild APK(s)の通知が出る。
成功時にはlocate(ハイパーリンク)が通知の中にあるのでそれをクリックするとAPKが作られたディレクトリが開く。


参考・引用元

Qita記事
Android studioにおけるbuild.gradleの設定について
Android Studioで署名付きAPK作成時にLintエラーが出た場合の対処法
外部サイト
なんちゃって情シス/【Android Studio】Key store pathを初めて作成する

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Azure PipelinesのYAMLでAndroidアプリのCI環境を構築する方法

「Azure PipelinesのYAMLでAndroidアプリのCI/CD環境を構築する」は3部構成です。
記事を順番に読み進めると、Azure PipelinesでAndroidアプリのCI/CD環境が構築できるようになります。

  • 第一部:CI環境の構築 ←イマココ
  • 第二部:App Center配布パイプラインの構築(未投稿)
  • 第三部:Google Play Console配布パイプラインの構築(未投稿)

はじめに

Azure Pipelinesを使い、Androidアプリのビルドと単体テスト、静的解析を行うCIを構築します。

本記事で説明しないこと

設定ファイルの構成

Azure PipelinesのYAMLでiOSアプリのCI環境を構築する方法 と同様なので省略します。

各項目の紹介

各項目を上から順に紹介します。

trigger

Azure PipelinesのYAMLでiOSアプリのCI環境を構築する方法 と同様なので説明は省略します。

設定のみ紹介します。

trigger:
  batch: true
  branches:
    include:
    - main
    - develop
  paths:
    exclude:
    - docs
    - README.md
    - LICENSE

schedules

Azure PipelinesのYAMLでiOSアプリのCI環境を構築する方法 と同様なので省略します。

設定のみ紹介します。

schedules:
- cron: "0 15 * * *"
  displayName: Daily midnight build
  branches:
    include:
    - main
    - develop
  always: true

variables

今回は使っていないので省略します。

jobs

今回はジョブを3つ用意しており、順番に紹介します。

test

ビルドと単体テストを実行するジョブです。

GitHub ActionsでAndroidアプリのCIを構築する方法 とほぼ同様なので、全体図のみ紹介します。

ちなみにビルドタイプとプロダクトフレーバーは以下の想定です。
環境によって読み替えてください。

  • ビルドタイプ: debug
  • プロダクトフレーバー: production
jobs:
- job: test
  pool:
    vmImage: 'ubuntu-latest'

  steps:
  # JDKのセットアップ
  - task: JavaToolInstaller@0
    inputs:
      versionSpec: '8'
      jdkArchitectureOption: 'x64'
      jdkSourceOption: 'PreInstalled'

  # 依存関係の出力
  - script: ./gradlew androidDependencies
    displayName: Displays the Android dependencies of the project

  # コンパイル
  - script: ./gradlew assembleDebug
    displayName: Run Compile

  # テスト
  - script: ./gradlew testProductionDebugUnitTest
    displayName: Test with Gradle

  # アーティファクトのステージングへコピー
  - task: CopyFiles@2
    inputs:
      Contents: |
        **/build/reports/tests/**/*
        **/build/reports/test-results/**/*
      TargetFolder: '$(Build.ArtifactStagingDirectory)'
    condition: succeededOrFailed()

  # アーティファクトへアップロード
  - task: PublishBuildArtifacts@1
    inputs:
      pathtoPublish: '$(Build.ArtifactStagingDirectory)'
      artifactName: 'drop'
      publishLocation: 'Container'
    condition: succeededOrFailed()

  # コードカバレッジの取得
  - script: ./gradlew jacocoProductionDebugTestReport
    displayName: Get code coverage

  # コードカバレッジのアップロード
  - task: PublishCodeCoverageResults@1
    inputs:
      codeCoverageTool: 'jacoco'
      summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/build/reports/jacoco/jacoco.xml'
      reportDirectory: '$(System.DefaultWorkingDirectory)/**/build/reports/jacoco/html'
      failIfCoverageEmpty: true
コードカバレッジの確認

「コードカバレッジのアップロード」タスクに成功すると、CIの結果に[Code Coverage]タブが追加され、コードカバレッジを確認できます。
スクリーンショット_2021-03-03_10_58_54.png

画像が表示されないのはセキュリティの理由だそうです。
Base64変換すれば表示できるとのことですが、そこまではやっていません。

reportDirectory: を指定しないとCIの結果からコードカバレッジを確認できないので注意です。
スクリーンショット 2021-03-02 18.26.26.png

注意
  • 「JDKのセットアップ」タスクが必要かわからない
  • jacocoProductionDebugTestReport タスクは自作
    • 別記事で説明する予定
  • テスト結果は成否にかかわらず確認したいため、アーティファクトのアップロードタスクで condition: succeededOrFailed() を指定している

lint

Android Lintを使って静的解析するジョブです。

GitHub ActionsでAndroidアプリのCIを構築する方法 とほぼ同様なので、全体図のみ紹介します。

- job: lint
  pool:
    vmImage: 'ubuntu-latest'

  steps:
  # 静的解析
  - script: ./gradlew lint
    displayName: Run Inspection

  # アーティファクトのステージングへコピー
  - task: CopyFiles@2
    inputs:
      Contents: |
        **/build/reports/lint-results.html
        **/build/reports/lint-results.xml
      TargetFolder: '$(Build.ArtifactStagingDirectory)'
    condition: succeededOrFailed()

  # アーティファクトへアップロード
  - task: PublishBuildArtifacts@1
    inputs:
      pathtoPublish: '$(Build.ArtifactStagingDirectory)'
      artifactName: 'drop'
      publishLocation: 'Container'
    condition: succeededOrFailed()

Android Lintの結果は静的解析の成否にかかわらず確認したいため、アーティファクトのアップロードタスクで condition: succeededOrFailed() を指定しています。

detekt

detektを使って静的解析するジョブです。

GitHub ActionsでAndroidアプリのCIを構築する方法 とほぼ同様なので、全体図のみ紹介します。

- job: detekt
  pool:
    vmImage: 'ubuntu-latest'

  steps:
  # 静的解析
  - script: ./gradlew detekt
    displayName: Lint with detekt

  # アーティファクトのステージングへコピー
  - task: CopyFiles@2
    inputs:
      Contents: |
        **/build/reports/detekt/**/*
      TargetFolder: '$(Build.ArtifactStagingDirectory)'
    condition: failed()

  # アーティファクトへアップロード
  - task: PublishBuildArtifacts@1
    inputs:
      pathtoPublish: '$(Build.ArtifactStagingDirectory)'
      artifactName: 'drop'
      publishLocation: 'Container'
    condition: failed()

失敗時のみ結果を詳細に確認したいため、アーティファクトのアップロードタスクで condition: failed() を指定しています。

設定ファイルの全体図

最後に設定ファイルの全体図を載せます。

trigger:
  batch: true
  branches:
    include:
    - main
    - develop
  paths:
    exclude:
    - docs
    - README.md
    - LICENSE

schedules:
- cron: "0 15 * * *"
  displayName: Daily midnight build
  branches:
    include:
    - main
    - develop
  always: true

jobs:
- job: test
  pool:
    vmImage: 'ubuntu-latest'

  steps:
  # JDKのセットアップ
  - task: JavaToolInstaller@0
    inputs:
      versionSpec: '8'
      jdkArchitectureOption: 'x64'
      jdkSourceOption: 'PreInstalled'

  # 依存関係の出力
  - script: ./gradlew androidDependencies
    displayName: Displays the Android dependencies of the project

  # コンパイル
  - script: ./gradlew assembleDebug
    displayName: Run Compile

  # テスト
  - script: ./gradlew testProductionDebugUnitTest
    displayName: Test with Gradle

  # アーティファクトのステージングへコピー
  - task: CopyFiles@2
    inputs:
      Contents: |
        **/build/reports/tests/**/*
        **/build/reports/test-results/**/*
      TargetFolder: '$(Build.ArtifactStagingDirectory)'
    condition: succeededOrFailed()

  # アーティファクトへアップロード
  - task: PublishBuildArtifacts@1
    inputs:
      pathtoPublish: '$(Build.ArtifactStagingDirectory)'
      artifactName: 'drop'
      publishLocation: 'Container'
    condition: succeededOrFailed()

  # コードカバレッジの取得
  - script: ./gradlew jacocoProductionDebugTestReport
    displayName: Get code coverage

  # コードカバレッジのアップロード
  - task: PublishCodeCoverageResults@1
    inputs:
      codeCoverageTool: 'jacoco'
      summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/build/reports/jacoco/jacoco.xml'
      reportDirectory: '$(System.DefaultWorkingDirectory)/**/build/reports/jacoco/html'
      failIfCoverageEmpty: true

- job: lint
  pool:
    vmImage: 'ubuntu-latest'

  steps:
  # 静的解析
  - script: ./gradlew lint
    displayName: Run Inspection

  # アーティファクトのステージングへコピー
  - task: CopyFiles@2
    inputs:
      Contents: |
        **/build/reports/lint-results.html
        **/build/reports/lint-results.xml
      TargetFolder: '$(Build.ArtifactStagingDirectory)'
    condition: succeededOrFailed()

  # アーティファクトへアップロード
  - task: PublishBuildArtifacts@1
    inputs:
      pathtoPublish: '$(Build.ArtifactStagingDirectory)'
      artifactName: 'drop'
      publishLocation: 'Container'
    condition: succeededOrFailed()

- job: detekt
  pool:
    vmImage: 'ubuntu-latest'

  steps:
  # 静的解析
  - script: ./gradlew detekt
    displayName: Lint with detekt

  # アーティファクトのステージングへコピー
  - task: CopyFiles@2
    inputs:
      Contents: |
        **/build/reports/detekt/**/*
      TargetFolder: '$(Build.ArtifactStagingDirectory)'
    condition: failed()

  # アーティファクトへアップロード
  - task: PublishBuildArtifacts@1
    inputs:
      pathtoPublish: '$(Build.ArtifactStagingDirectory)'
      artifactName: 'drop'
      publishLocation: 'Container'
    condition: failed()

シンプルなYAMLファイルなので、慣れれば読みやすいと思います。

おまけ: PR時にCIを回す

Azure PipelinesのYAMLでiOSアプリのCI環境を構築する方法 と同様なので省略します。

他にやりたいこと

実現できていないことを備忘録として残します。

  • キャッシュの取得
  • [Test]タブの追加
  • [Code Coverage]タブで画像の表示

おわりに

Azure PipelinesのYAMLで基本的なAndroidアプリのCIを回すことができました!

キャッシュが取れていなかったりと改善点もありますが、参考になれば幸いです。

参考リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LottieをリソースIDでデータバインディングしてコードをスッキリ

Lottieは簡単に素敵なアニメーションを追加できるAirbnbのライブラリです。
更に使いやすくresourceIdでデータバインディングできるようにカスタムBindingAdapterを用意して使ってみました。

Lottie

カスタムBindingAdapterを作成

LottieAnimationViewBindingAdapter.java
import androidx.annotation.RawRes;
import androidx.databinding.BindingAdapter;
import androidx.databinding.BindingMethod;
import androidx.databinding.BindingMethods;

import com.airbnb.lottie.LottieAnimationView;

@BindingMethods({
        @BindingMethod(type = com.airbnb.lottie.LottieAnimationView.class, attribute = "lottie_rawRes", method = "setAnimation"),
})
public class LottieAnimationViewBindingAdapter {

    @BindingAdapter("lottie_rawRes")
    public static void setAnimation(LottieAnimationView view, @RawRes final int rawRes) {
        view.setAnimation(rawRes);
        view.playAnimation();
    }
}

ViewModelでResId用のLiveDataを作成

HogeViewModel.kt
val animationResId: LiveData<Int> = Transformations.map(hoge) {
        when {
            hoge.value!! > THRESHOLD -> R.raw.rocket_in_space
            else -> R.raw.lighthouse
        }
    }

レイアウトにバインド

hoge_fragment.xml
<data>
        <variable
            name="vm"
            type="com.hoge.HogeViewModel" />
    </data>

...

<com.airbnb.lottie.LottieAnimationView
            android:id="@+id/animationView"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:lottie_autoPlay="true"
            app:lottie_loop="true"
            app:lottie_rawRes="@{vm.animationResId}" />
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む