20200120のAndroidに関する記事は6件です。

Androidアプリ開発でのSonarQube~Java~

はじめに

JavaでAndroidアプリの開発を行うときにSonarQubeを導入した時の手順を記載していきます。
ミニマムで導入した手順です。
Kotlinはもう少しシンプルにできると思います。

環境

OS:Windows 10(64bit)
Java:11
Android Studio:3.5

SonarQube

SonarQubeとは静的解析ツールの一つでJava, Kotlin, Swift, Goなど25以上の言語をサポートしているようです。
JenkinsやAzure DevOpsと連携することでCIツールの強化を図ることができるようです。

詳細は公式ドキュメントを参照してください。

導入手順

基本的には公式のGet Started in Two Minutes Guideに沿って行きます。

1.SonarQube Serverのインストール

以下のリンクからDownloadします。
Download SonarQube

適当な場所に展開します。
今回は以下の構成にします。
C:\SonarQube\SonarQubeServer

2.SonarQubeServerを起動

以下にあるbatを実行するだけです。
C:\SonarQube\SonarQubeServer\bin\windows-x86-64

C:\SonarQube\SonarQubeServer\bin\windows-x86-64>StartSonar.bat

http://localhost:9000/ にアクセスするとSonarQube Serverの画面を確認できます。
sonarqubeserver_notdone.png

3.Gradleの設定

プロジェクト配下のbuild.gradleの編集を行います。
build.gradleの先頭に以下を追記します。

plugins {
  id "org.sonarqube" version "2.7"
}

4.gradle.propertiesの設定

ここからはアプリ側の設定を行っていきます。
公式ではSonarScanner for Gradleを参考にしてます。
gradle.propertiesに以下を追記します。

# gradle.properties
systemProp.sonar.host.url=http://localhost:9000

#----- Token generated from an account with 'publish analysis' permission
systemProp.sonar.login=<token>

tokenはSonarQube Serverから取得します。

5.Tokenの取得

SonarQube Serverに戻ります。
(1) 右上のLog inからログイン

ユーザー名:admin
パスワード:admin

(2) 右上のユーザーアイコンのMy Accountから管理者画面に移動
(3) Securityのタブを選択
(4) Tokensのところにプロジェクト名を入れてTokenを取得

6.実行

以下のいずれかで静的解析を実行
(1) Android SrudioのGradle > app > Tasks > other > sonarqubeをダブルクリック
sonarqube.png

(2) プロジェクト配下で以下を実行

./gradlew sonarqube

7.確認

http://localhost:9000 にアクセスしてappが表示されれば完了
sonarqubeserver.png

参考文献

公式
SONNARQUBEでソースコードの品質を解析する
  最初にSonarQubeを導入するにあたって参考にさせていただきました。Kotlinのプロジェクトはこちらの記事で解析まで行うことができました。

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

[Android] プログレスダイアログ(通信中のぐるぐる)

*2020/1/21追記
API24で実装しています。API26からは非推奨となっているようです。


実装方法

*非同期処理クラスの処理が終了したらBroadcastによりonReceiveが呼ばれる例を使っている。

public class MainActivity extends Activity{

    public ProgressDialog progressDialog_ = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        //通信前にダイアログ表示
        progressDialog_ = new ProgressDialog(this);
        progressDialog_.setMessage("実行中...");
        progressDialog_.show();

        //非同期処理クラスを呼び出して通信処理
    }

    public class MyReceiver extends BroadcastReceiver{


        @Override
        public void onReceive(Context context, Intent intent)  {
            // プログレスダイアログを閉じる
            if (progressDialog_ != null && progressDialog_.isShowing()) {
                progressDialog_.dismiss();
            }
        }
    }
}

結果

以下のダイアログが表示された。
スクリーンショット (10).png

感想

とても簡単なのでぜひ使ってみてほしい。

参考URL

https://barubora3.net/?p=48

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

ダークモードとは何か?各OSの設定方法からiOS/Androidアプリの対応方法まで

はじめに

勉強会をご覧の皆さま

元々のテーマは「10分でわかるダークモード対応」でしたが、あちこち調べているうちにページ数が30を超えてしまい、「10分ではわからないダークモード対応」になりました🙇‍♂️

20分には収めます


ダークモードとは?

OSシステムレベルで設定可能な、画面表示色の設定です。
light_and_dark.png
Light Modeが従来通りの白基調な画面、Dark Modeは黒基調な画面です。


何が明るく/暗くなるの?

特定の何に適用すべきという法則は無いようですが、閲覧者に見せたいコンテンツ以外のウィンドウ枠、背景などに対して適用されることが多いようです。

もちろん、メインコンテンツ(テキスト色など)も背景色に応じて調整する必要があります。


ダークモードにすると何がいいの?

  • 目へのダメージを抑える
    • 眩しい画面を見ることによるドライアイ頭痛、メラトニン抑制による不眠など
  • バッテリーの持ちが長くなる
    • OLEDスクリーンで輝度が100%の場合、DarkはLightと比較して約60%のエネルギーを節約。輝度が50%の場合、約15%節約(YouTubeアプリで計測)
  • 背景は明るいより暗いほうが、メインのコンテンツを目立たせ、作業に集中しやすくする

ダークモードにする場合のデメリットや注意点

  • 直射日光の下などの明るい場所で読みづらい
    • ナイトモード(ブルーライトカットの)と同じように、日没〜日の出までダークモードとする設定が用意されている
  • テキスト色と背景色の逆転にOCRが追いついていない
    • ダークモードで撮ったスクショをGoogle翻訳にかけても(現時点では)翻訳してくれない😭

ダークモード対応OS

  • Windows 10 version 1903〜
  • Mac OS Mojave(10.14)〜
  • Android 10〜
  • iOS 13〜

ダークモード設定方法

  • Windows
    • スタート -> 設定 -> 個人用設定 -> 色
  • macOS
    •  -> システム環境設定 -> 一般 -> 外観モード
  • Android
    • 設定 -> ディスプレイ -> テーマ
    • Zenfoneの場合は 設定 -> ディスプレイ -> システムカラースキーム
    • クイック設定パネルに追加したい場合は 画面上端を下にスワイプ -> 🖋
  • iOS
    • 設定 -> 画面表示と明るさ -> 外観モード
    • コントロールセンターに追加したい場合は 設定 -> コントロールセンター -> コントロールをカスタマイズ

iOS/Androidアプリでのダークモード対応方法(アプリエンジニア向け)


iOS

準備

  • Xcode 11以上のビルド環境
  • iOS 13以上の実機orシミュレーター

iOSダークモード対応は急務!!


まずダークモードの見え方を確認しよう!

StoryboardやXibのInterface Builder上でLight/Darkを切り替える

    • 画面中央下部

デバッグ実行中にLight/Darkを切り替える

    • 画面中央下部

Darkにして、どうなったか。弊社案件の場合

  • テキストが自動で白くなったが背景も白い。文字が読めない!
  • 背景が自動で黒くなったがテキストも黒(
  • 濃緑基調のグラフィックが黒い背景では見えづらい
  • QRコードが真っ黒黒助

何故そうなるのか

  • 場所によってiOS System Colorsを使ったり使わなかったりするため
  • 大人数のプロジェクトでありがちな統一感の甘さ
  • とはいえiOSのStoryboardを完璧な統一感で作った人は見たことありませんが…😅

対応作業

カラーセットを使う場合

  1. ハードコードしている色をAssets CatalogのColor Setに移行する
  2. Color SetのAppearancesにてDarkを追加する
  3. Dark Appearanceにフォーカスを合わせ色設定する

  • 色は従来のsRGB方式以外に、iOS System Colorsも選択可能
    • darkTextColorsystemBackgroundColorなど
    • 使うとOS側が色の濃さを自動調整してくれる。便利!
    • 詳しくは参考記事の実践 iOS13ダークモード対応に、わかりやすく書かれています

カラーセットを使わない場合

  • アプリ起動中にLight/Darkを切り替えられても即反映させたいので、次のいずれかの方法で実装

  • dynamicColor方式
extension UIColor {
    private class func dynamicColor(light: UIColor, dark: UIColor) -> UIColor {
        if #available(iOS 13, *) {
            return UIColor { (traitCollection) -> UIColor in
                if traitCollection.userInterfaceStyle == .dark {
                    return dark
                } else {
                    return light
                }
            }
        }
        return light
    }

    public static var textColor: UIColor {
        return dynamicColor(
            light: UIColor(displayP3Red: 0, green: 9, blue: 11, alpha: 1),
            dark: UIColor(displayP3Red: 255, green: 246, blue: 244, alpha: 1)
        )
    }
}

  • dynamicProvider方式
extension UIColor {
    @available(iOS 13.0, *)
    convenience init(light: UIColor, dark: UIColor) {
        self.init {
            if $0.userInterfaceStyle == .dark {
                return dark
            } else {
                return light
            }
        }
    }

    @available(iOS 13.0, *)
    public static let textColor = UIColor(
        light: UIColor(displayP3Red: 0, green: 9, blue: 11, alpha: 1),
        dark: UIColor(displayP3Red: 255, green: 246, blue: 244, alpha: 1)
    )
}

こちらはSDKバージョンの制限を受けますが、initとして処理を書けて、定数をimmutableにできます。


Android

準備

  • Android Studio 3.3以上のビルド環境
  • Android 10(Q)以上の実機orシミュレーター

まずダークモードの見え方を確認しよう!

Design View上でLight/Darkを切り替える


* Night Mode -> Nightを選択


開発者向けオプションを使う

  • システム -> 詳細設定 -> 開発者向けオプション -> フォースダークのオーバーライド
  • アプリの実装を変えずダークモードをシミュレートできる

Darkにして、どうなったか。弊社案件の場合

  • Design View上では何も変わらなかった…
    • Android版は?android:attr/textColorPrimaryなどのシステムカラーを使っていなかったから
    • Androidチームはデザイン指示書に厳密に従ったので、システムカラーを使わなかった
  • でも、開発者向けオプションだといい感じのダークモードになった

対応作業

  1. styles.xmlcolors.xmlを別々に用意する
    • Light:res/values/*
    • Dark:res/values-night/*
    • 共通のものはLight側にまとめよう
  2. Darkの方には、夜間モードフォースダークを適用する(後述)
    • 2つの重ね掛けはできないらしい

夜間モード機能を使う

  • アプリを新規作成すると自動でTheme.AppCompat.Light系のテーマが適用されているが、Dark側のスタイルにはTheme.AppCompat.DayNightを適用する
res/values-night/themes.xml
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar" />

フォースダーク機能を使う

  • 各ビューを分析して自動でダークカラーに変えてくれる機能
  • 先述の開発者向けオプションを使った確認では、こちらがシミュレートされる
res/values-night/themes.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:forceDarkAllowed">true</item>
</style>
  • 自動で変えたくないビューには、AppTheme以外のstyleを用意して適用すればよい

Androidのダークモードサポートは色々手厚い!

  • より詳しく知りたい場合、こちらを参照

WebViewの場合は?


まとめ

  • ダークモードは目とバッテリーに優しく、集中できる!
  • iOSは2020/04まで(03末?)にダークモードを対応するか、一時しのぎでLightに固定しなければならない
    • まずは動かしてみないと修正規模がわからない
  • Androidはフォースダークで対応するのが手っ取り早そう

最後に大事なこと

  • ダークモード対応はエンジニアファーストで提案しよう!
    • 顧客やPMは、iOS SDKの事情でダークモード対応が必要なんて知る由もない
  • 工数削減のために、自動調整してくれるシステムカラーは最大限活用しよう!
    • 活用すれば、20画面あっても半日で終わるから怖くない(ヤバいAppは知りません)
  • デザイナーにも必要な情報を公開しよう!
    • 一言「ダークモード版の素材と指示書お願いします」で受けてくれるデザイナーは奇跡の存在です(個人の所感)

やっぱり言わせてください

面倒くせー!!!


参考

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

カスタムビューでよく使うヘルパー関数を晒してみる

カスタムビューの実装など、 onDraw の内部の実装を自前で行う場合には、自分で描画位置の計算をしないといけないので計算量が多くなります。

そういった場合に自前でちょっとしたヘルパー関数を用意しておくととても便利なので、自分が使う際によく使う関数をメモしておきます。
Float のバージョンのみを記載していますが、状況に応じて適宜 Int のバージョンなども作ると便利です。)

constrain()

valuemin 〜 max の間に収まるようにする関数です。こいつを一つ挟んでおくことでバリデーションになるので非常に重宝しています。

fun constrain(value: Float, min: Float, max: Float): Float {
    return max(min(value, max), min)
}

map()

srcStart 〜 srcStop の範囲にある値を dstStart 〜 dstStop の範囲に変換します。グラフを描画したいけれど、グラフの座標系と画面上の座標系が違う...といった場合にサクッと変換を書けるので非常に便利です。(後述する lerpnormmap の変化系です。)

fun map(value: Float, srcStart: Float, srcStop: Float, dstStart: Float, dstStop: Float): Float {
    if(srcStop - srcStart == 0f) {
        return 0
    }
    return dstStart + (value - srcStart) * (dstStop - dstStart) / (srcStop - srcStart)
}

lerp()

lerp0.0f 〜 1.0f の範囲の値を start 〜 stop の範囲に変換します。 例えば、2点間を3:1に分割した点の座標を求めたい、といった場合に lerp(0.75f, 0f, 100f) とすれば、すぐに座標を計算できます。

fun lerp(amt: Float, start: Float, stop: Float): Float {
    return start + (stop - start) * amt
}

norm()

normlerp の反対で、 start 〜 stop の範囲にある値を 0.0f 〜 1.0f の範囲に変換します。

fun norm(value: Float, start: Float, stop: Float): Float {
    return value / (stop - start)
}

おわりに

計算関数の部分だけを他の描画ロジックから分離することでコードの見通しが良くなり、テストも容易になるためおすすめです。

自分のプロジェクトではこんなヘルパー関数を使っているよ、などコメントで教えていただけると喜びます :pray:

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

カスタムビューの実装でよく使うヘルパー関数を晒す

カスタムビューの実装など、 onDraw を継承して描画を自前で行う場合には、自分で描画位置の計算をしないといけないので計算量が多くなりがちです。

そういった場合に自前でちょっとしたヘルパー関数を用意しておくと便利なので、自分が使っている関数を紹介します。
Float のバージョンのみを記載していますが、状況に応じて適宜 Int のバージョンなども作ると便利です。)

constrain()

valuemin 〜 max の間に収まるようにする関数です。こいつを一つ挟んでおくことでバリデーションになるので非常に重宝しています。

fun constrain(value: Float, min: Float, max: Float): Float {
    return max(min(value, max), min)
}

map()

srcStart 〜 srcStop の範囲にある値を dstStart 〜 dstStop の範囲に変換します。グラフを描画したいけれど、グラフの座標系と画面上の座標系が違う...といった場合にサクッと変換を書けるので非常に便利です。(後述する lerpnormmap の変化系です。)

fun map(value: Float, srcStart: Float, srcStop: Float, dstStart: Float, dstStop: Float): Float {
    if(srcStop - srcStart == 0f) {
        return 0
    }
    return dstStart + (value - srcStart) * (dstStop - dstStart) / (srcStop - srcStart)
}

lerp()

lerp0.0f 〜 1.0f の範囲の値を start 〜 stop の範囲に変換します。 例えば、2点間を3:1に分割した点の座標を求めたいといった場合に lerp(0.75f, 0, 100f) のように書けば、すぐに座標を計算できます。

fun lerp(amt: Float, start: Float, stop: Float): Float {
    return start + (stop - start) * amt
}

norm()

normlerp の反対で、 start 〜 stop の範囲にある値を 0.0f 〜 1.0f の範囲に変換します。

fun norm(value: Float, start: Float, stop: Float): Float {
    return value / (stop - start)
}

おわりに

ヘルパー関数の部分だけを他の描画ロジックから分離することでコードの見通しが良くなり、テストも容易になるのでおすすめです。

自分のプロジェクトではこんなヘルパー関数を使っているよ、などコメントで教えていただけると喜びます :pray:

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

LiveData Builderを使ってコードをスッキリさせる

はじめに

LiveData Builderを勉強したのでメモ代わりに投稿します。

LiveData Builderとは?

androidx.lifecycle:lifecycle-livedata-ktxに含まれる拡張機能で、liveData 関数内でsuspend関数を呼び出し、LiveDataに値を直接反映させることができます。

Android Developers内に解説があります。

また、こちらに公式のコードラボがあります。

リポジトリ

GitHubからリポジトリ情報を取得してリスト表示するサンプルをこちらに上げてあります。

導入

LiveData Builderの導入にはlivedata-ktxの導入が必要です。

build.gradle
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-rc03"

LiveData Builderを使わない場合

通常、LiveDataを扱う場合は MutableLiveData を用いて以下のように記述すると思います。

ViewModel
    // MutableLiveDataで取得したいオブジェクトをラップ
    val data = MutableLiveData<Repos>()

    // 関数を定義
    fun getRepos() {
        viewModelScope.launch {
            try {
                // APIからデータ取得 ※getReposはsuspend関数
                val repos = api.getRepos()
                // postValueでdata内の値を更新
                data.postValue(repos)
            }
            catch (e: Exception) {
                ...
            }
        }
    }
MainActivity
    // viewModelを定義
    private val viewModel: ViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // 関数呼び出し
        viewModel.getRepos()

        // dataの値を監視
        viewModel.data.observe(this, Observer {
            // 値が更新された時の処理
        })
    }

LiveData Builderを使った場合

LiveData Builderを使うと以下のように記述することが出来ます。

ViewModel
    // LiveData Builder
    val data: LiveData<Repos> = liveData {
        try {
            val repos = api.getRepos()  // getReposはsuspend関数
            emit(repos) // emit()で値を更新する
        }
        catch (e: Exception) {
            ...
        }
    }
    // fun getRepos()は削除
MainActivity
    // viewModelを定義
    private val viewModel: ViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        // dataの値を監視
        viewModel.data.observe(this, Observer {
            // 値が更新された時の処理
        })
    }

ViewModel内の記述がかなりスッキリしたことが分かります。
liveData { ... }はいつ呼ばれるのか?という疑問があると思いますが、
Viewが値をobserveした時になります。

LiveData Builderを使うことによって、

  • コード量が削減できる
  • MutableLiveDataをImmutableなLiveDataに置き換えることができる
  • functionをActivity・Fragmentから呼び出さずに済む

などのメリットがあります。

Viewのロード時に一度だけAPIを叩くような場合は、MutableLiveDataを使わずにLiveDataで十分になるかと思います。

おわりに

まだLiveData Builderを触り慣れてないので、他にもこんなメリットがあるよ!などのご意見お待ちしております。

今回の記事を書くにあたって、以下のリンクを参考にさせて頂きました。

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