20201201のAndroidに関する記事は11件です。

Unity as a LibraryによるUIネイティブ化

こんにちは、mizogucheです。2015年9月にクラスターに入ったのでもう6年目です。

この記事はクラスター Advent Calendar 2020 - Qiitaの2日目の記事です。

Unity UIでの課題

UnityでUIを実装すると普段使っているネイティブアプリの操作性と違った触り心地になります。

ネイティブアプリで簡単に実装できるUIの操作性をUnityで再現するコストはかなり高く、たとえばiOSにおける戻るジェスチャ、Androidにおけるバックボタンなど、OS固有の操作はネイティブUIだと何も実装しなくても実現できる1一方で、Unityではそれぞれのプラットフォームごとに独自に実装していく必要があります。

感覚的なものでいうと、スクロールビューのスクロールの感触なんかもUnity、iOS、Androidでそれぞれ違うので、Unity製のアプリだと他のネイティブアプリと比較して触り心地がよくないのが正直なところです。

Unityのランタイムの起動に時間がかかる分、アプリの起動時間が遅くなってしまうというのも大きな問題でした。

Unity as a Library

Unityでビルドしたアプリを起動したらすぐにUnityのランタイムが実行されますが、Unity as a Library(= UaaL)という技術で、アプリの一部分だけでUnityを利用することができます。

これによりUnityでの再現が難しいネイティブアプリのUIの操作感を実現しつつ、必要なところでUnityの表現力を発揮することができます。

メンテナンスコストの増加

すべてがUnity製のアプリであれば1つのコードからiOS/Android両プラットフォームのアプリをビルドすることができます。

しかし、UaaLを使うことでiOS/Android両プラットフォームのネイティブUI部分を実装する必要が生まれます。

つまり、UaaLの採用によって実質的に2つのアプリを開発・メンテナンスする必要が生まれます。

clusterではUaaLをどう利用したか

ネイティブUIのoutroom/Unityのinroom

ワールド・イベントに入るまでの世界をoutroom、ワールド・イベントに入った後のUnityの世界をinroomと呼んでいます2

モバイルアプリ版clusterで、outroomをネイティブ、inroomでUnityを使うようアップデートしたものが11月にリリースした v1.85です。

これにより、Unity UIでの課題を解決してネイティブの触り心地を実現つつ、ワールド・イベントのクリエイティブな領域ではUnityの表現力を使うことができるようになりました。

この動画をご覧いただくと、普通にネイティブUIで実装し直すだけでどれだけ改善されたかがおわかりいただけるかと思います。

今後

outroomをネイティブUIにして体験を改善することができました。

しかしまだまだ改善することは山積みなため、クラスターではiOS/Androidエンジニアを募集しています。

というわけで2日目の記事は以上です。

明日は__0xyさんがなんかかくそうです。どんなことが書かれるんやろ…… ? お楽しみに!!

クラスター Advent Calendar 2020 - Qiita

参考リンク


  1. Androidのバックボタンは遷移先の制御を考えると何も実装しなくてもいいは過言 :innocent:  

  2. roomというのは古来よりクラスター内部で使われてきた用語です。 

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

開発者ごとでなく、プロジェクトごとにFlutterのバージョンを管理する

Flutterのアップデート、まだまだ激しいですよね。
以前チーム開発していた時も、

  • 他の開発者とのFlutterのバージョン違いによりAPI定義が異なりエラーの嵐→無駄なコミュニケーションの発生
  • 自分のPCのFlutterのバージョンを上げてしまったところ、ビルドできなくなったといった報告の対応

など、プロジェクトごとでなく開発者ごとにFlutterのバージョンを管理しているために起きてしまう問題がいくつかありました。また、OSSや個人開発などでも使用するFlutter SDKのバージョンを固定したいこともあるかと思われます。

本記事では、開発者ごとでなく、プロジェクトごとにFlutterのバージョンを管理する方法を紹介します。

FVMを用いる場合

FVMはFlutter SDKバージョン管理ツールです。

https://github.com/leoafarias/fvm

導入についてはこちらの記事が日本語で分かりやすいので、説明は割愛します。

https://qiita.com/Kurusu_ima/items/2dfd067f6e79f520198f

バージョンをプロジェクトごとに固定する方法

ターミナルを開いてプロジェクトのルートに移動しておいてください。

インストール可能なリリース済みのバージョン一覧を確認しましょう。

$ fvm releases

次に現在インストールされているバージョンの一覧を確認してみましょう。

$ fvm list

お目当てのバージョンがなければ、以下のコマンドでバージョン名またはチャンネル名からFlutter SDKが手に入ります。

$ fvm install <バージョン名またはチャンネル名>

例えばstableを使いたい人は以下のコマンドです。

$ fvm install stable

最後に、以下のコマンドで、プロジェクトのFlutter SDKのバージョンを固定することができます。

$ fvm use <バージョン名またはチャンネル名>

そうすると、プロジェクト内に.fvmが作成されます。.fvm内には以下が入っています。

  • プロジェクトで固定されるFlutter SDKバージョン情報が記載された.fvm/fvm_config.json
  • ~/fvm/versions/<バージョン名>のシンボリックリンクとなっている.fvm/flutter_sdk

すでにFVMによりバージョン固定されているプロジェクトで開発する時

バージョンを指定せず以下のコマンドを実行すると、.fvm/fvm_config.jsonのバージョンをインストールできます。

$ fvm install

固定されたバージョンのFlutter SDKを使用する

flutterコマンドの前にfvmをつけるだけです。例としてflutter runしたい場合は以下のようになります。

$ fvm flutter run

IDEの設定

IDEの操作でも固定したFlutter SDK下で実行できるようにします。デバッガーなどIDEの機能を使いたい時のために必要です。

Android Studioの場合

Preferences > Languages & Frameworks > FlutterのFlutter SDK pathを{対象プロジェクトのルート}/.fvm/flutter_sdkに変更してください。

VSCodeの場合

{対象プロジェクトのルート}/.vscode/settings.jsonに以下を追加してください。

{
    "dart.flutterSdkPath": [".fvm/flutter_sdk"],
}

VSCodeを再起動し、コマンドパレットでFlutter: Change SDKと入力してバージョンを選んでください。

Flutter Wrapperを用いる場合

更新が一年以上止まっているのですが、特定のバージョンのFlutter SDKをダウンロード&実行するシェルスクリプトによってFlutterのバージョンをプロジェクトで固定できるFlutter Wrapperを用いる方法もあります。

https://github.com/passsy/flutter_wrapper

導入

ターミナルでバージョンを固定したいプロジェクトのルートに移動した後、以下を実行するだけです。

$ sh -c "$(curl -fsSL https://raw.githubusercontent.com/passsy/flutter_wrapper/master/install.sh)"

以降はflutterの代わりに./flutterwのコマンドを用いることで、固定されたFlutter SDK下でコマンドを実行することができます。

アップグレード

特に新しいコマンドを覚える必要はありません。これもflutterの代わりに./flutterwになるだけで、いつものFlutterのアップグレードと同じです。

IDEの設定

各IDEで設定されているFlutter SDKのパスを変更しておくことで、FVMの時と同様、IDEの操作でも固定したFlutter SDK下で実行できるようにします。

Android Studioの場合

Preferences > Languages & Frameworks > FlutterのFlutter SDK pathを{対象プロジェクトのルート}/.flutterに変更してください。

VSCodeの場合

{対象プロジェクトのルート}/.vscode/settings.jsonに以下を追加してください。

{
    "dart.flutterSdkPath": ".flutter",
}

まとめ

Flutter Wrapperは更新があまりされていないので、現在のところFVMがスタンダードなのかなという感じです。FVMの方がバージョン切り替えもスムーズかと思います。強いて言うなら、Flutter WrapperはFVMのインストールを必要としないので、コマンド一発で使えるというところがメリットでしょうか。

参考

https://qiita.com/tetsufe/items/8ffa296c22a2dc8b9b51
https://qiita.com/Slowhand0309/items/0767abee120fcb3ba0b4

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

AndroidStudioメソッドのパラメータを確認する方法

動機

プログラミング学習していて、ふと気になったことが。
メソッドを入力した時に表示されるこれ。何をパラメータとして持つのか、ぱっと見で分かるので便利。
2020-12-01 (5).png
しかし、入力し終えると直ぐに消えてしまう。
どうにかしたいものだ。調べると、解決策は見つかった。
2020-12-01 (4).png

解決策

調べたいメソッドにカーソルを合わせて「Ctrl + p」以上。※Macの方は、「Cmd + p」
また、他にもViewタブから「Parameter Info]によっても可能だ。ちなみに、その横にショートカットキーも表示されていて、ここでも「Ctrl + p」であることが確認できるはずだ。
2020-12-01 (9).png
2020-12-01 (3).png

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

Android開発者向けオプションを使ったチート対策

PONOS Advent Calendar 2020 5日目の記事です。

はじめに:eyeglasses:

Androidチート対策の一つとして
開発者向けオプションを使用した対策を紹介します

この記事の対象者:eyeglasses:

・アプリ開発者
・Android開発者

開発者向けオプションが有効かの判定

チート端末では開発者向けオプションが有効になっていることが多いです。
アプリ側で開発者向けオプションが有効か無効か判定してみましょう。

android.provider.Settings.Systemクラスを使用します。
システム環境設定関連の値を取得するなど便利な機能があります。

Settings.Secure.getInt()メソッド
Settings.Global.DEVELOPMENT_SETTINGS_ENABLEDフィールド
を使用します。

boolean isEnable = Settings.Secure.getInt(this.getContentResolver(),
 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED , 0) == 1;
        if(isEnable){
            //開発者向けオプション 有効時の処理
            //例:タイトル画面でロックして、開発者向けオプションをOFFにするように促すポップアップ表示
        }else{
            //開発者向けオプション 無効時の処理
        }

メリット
アプリのセキュリティーレベルを上げることができる。
実装コストが軽い

デメリット
主に開発者など日常的に開発者向けオプション有効にしているユーザーにとっては迷惑

導入する場合
端末操作で開発者向けオプション有効にする方法は
「設定」アプリ内で「ビルド番号」を7回連続してタップ
と一般ユーザーはやらない操作だと思われるが、トレードオフが必要です

USBデバッグモードの判定

フィールド名だけを変えた同様の方法でadbのチェックも可能です。
USB経由のadbが有効かどうか判定できます。

boolean isAdb = Settings.Secure.getInt(this.getContentResolver(),
 Settings.Global.ADB_ENABLED , 0) == 1;

チート対策としては有効ですが
こちらも導入する場合は、トレードオフが必要です。

まとめ

今回紹介した方法は「小を捨てて大に就く」手法で完璧なものではありません。
他にも有効なチート対策があるので合わせて検討するのがお勧めです。
チート対策
・メモリシャッフル(メモリハック対応)
・root権限チェック
・不正端末名、不正エンジン名のチェック(エミュレーター対策)
・ソースコード難読化
・リソースなどの暗号化
・サーバー側でのバリデーションチェック
・サーバー通信時の内容を暗号化
・SafetyNetの導入
などなど

今回の手法の最大のメリットは実装コストが軽いことなのですぐ実装して
・複数端末テストの結果をみて導入を検討する
・得られた情報をユーザーデータとして保存してデータ分析してから検討する
などのやり方がベターかなと考えております。

明日は、@nissy_gpさんの記事です。
お楽しみに!!

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

Airtestを使ったAndroidアプリの自動操作

はじめに

ソフトウェアテストの業界向けに役に立つ話、第一回目です。
Airtest IDEというUI自動化ソフトウェアを使用して、Androidアプリのかんたんな自動化をしてみました。
スマホを持つ猫

1. Airtestをダウンロード

Airtest Projects
https://airtest.netease.com/

公式サイトからソフトウェアをダウンロードし、インストールします。

2. 起動


ソフトウェアを起動すると、ログインを求められますが「Skip」しても問題ないようです。

3. 新規作成

メニューバーから「File」>「New」>「.air Airtest Project」を選択し、新規のプロジェクトを作成します。
スクリーンショット 2020-12-01 17.35.08.png

4. スマホの接続


スマホをPCと接続し、右側の「Devices」にある「refresh ADB」を押します。すると、デバイス名が表示されますので、「connect」ボタンを押して接続します。

接続の際に以下の操作が事前に必要です。

  • Android開発者向けオプションが「ON」になっている
  • USBデバッグが「ON」になっている

5. テストアプリをインストールする

https://github.com/AirtestProject/Airtest/tree/master/playground/blackjack_example

今回はAirtestのGitHubにあるブラックジャックのサンプルアプリ「blackjack-release-signed.apk」をインストールしてみました。

6. タッチしたい箇所を選択する

test2.gif
ツールボックスにある「touch」のボタンを押し、タッチしたい箇所をドラッグして切り取ります。すると、コードが自動的に生成されます。

7. 一連の操作を書く


コードはPythonで記述するようです。今回は以下のような一連の操作を書きました。

  1. スタートボタンを押す
  2. 掛け金をベットする
  3. ブラックジャックを開始する
  4. カードを交換しない
  5. 勝利だったら、タイトル画面へ それ以外はもう一度プレイする

結果

test.gif
勝利するまで、ゲームを自動で続けてくれました。:clap:

さいごに

スマートフォンのアプリゲームでは、バトルがオートで行えることが多いのでさまざまなタイトルで活用できるかと思います。自社開発したアプリゲームのテストなどで参考にしていただければと思います。

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

【Android】画像にRGBコードで色設定してみた

主流は16進数カラーコード(#FF0022)だと思いますが、
RGBカラーコード(255, 0, 34)の形式で色変更を行いました。

実際の業務でやった内容の備忘録っす!

・backGroundcolor()をセットするのではなく、colorFilterを適用する!
・RGB値はColor.rgb()でRGB値を変換して適用する!

クラス:カラーマスタ(MstColor)

データベースのカラム情報をそのまま使います。

・カラーコード(番号)
・カラー名
・R値
・G値
・B値

本当はもっとあるけど...とりあえず必要なものだけ。

data class MstColor(
    val color_code: String,
    val color_name: String,
    val r_code: Int,
    val g_code: Int,
    val b_code: Int
)

レイアウトと画像

サンプルのレイアウトにImageView追加しただけです。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SecondFragment">

    <ImageView
        android:id="@+id/triforce"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:orientation="horizontal"
        android:src="@drawable/triforce"
        app:layout_constraintBottom_toTopOf="@id/button_second"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button_second"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/previous"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/triforce" />
</androidx.constraintlayout.widget.ConstraintLayout>

使う画像はこれ。

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="450dp"
    android:height="450dp"
    android:viewportWidth="450"
    android:viewportHeight="450">
  <path
      android:pathData="M215.8,41.8c-3.1,5.3 -25.4,44.1 -49.7,86.1 -24.3,42 -44.1,76.5 -44.1,76.7 0,0.2 44.8,0.4 99.5,0.4 54.7,-0 99.5,-0.4 99.5,-0.8 0,-0.8 -6.3,-11.9 -19.7,-34.7 -3.8,-6.6 -23.1,-39.9 -42.8,-74 -19.6,-34.1 -36,-62.3 -36.4,-62.7 -0.4,-0.4 -3.2,3.6 -6.3,9z"
      android:fillColor="#000000"
      android:strokeColor="#00000000"/>
  <path
      android:pathData="M121,206.7c0,1.4 -97,169.6 -98.4,170.5 -0.6,0.4 -0.8,0.9 -0.5,1.3 0.3,0.3 45.2,0.4 99.8,0.3l99.2,-0.3 -6.4,-11c-52.4,-90.6 -88,-152.1 -90.5,-156.5 -1.8,-3 -3.2,-5 -3.2,-4.3z"
      android:fillColor="#000000"
      android:strokeColor="#00000000"/>
  <path
      android:pathData="M273.9,292.8l-49.5,85.7 49.9,0.3c27.4,0.1 72.1,0.1 99.3,-0l49.5,-0.3 -6.4,-11c-3.5,-6.1 -25.6,-44.3 -49.1,-85 -23.6,-40.7 -43.1,-74.3 -43.5,-74.7 -0.4,-0.4 -22.9,37.8 -50.2,85z"
      android:fillColor="#000000"
      android:strokeColor="#00000000"/>
</vector>

画像見ればわかるけど。。

まぁまぁまぁ...焦るでない.....

ColorFilterを適用する

まずはFragmentの実際のソース内容を貼り付けます。

class SecondFragment : Fragment() {

    // カラーマスタを初期設定
    private val mstColorList: List<MstColor> = listOf(
        MstColor("1", "blue", 30, 60, 162),
        MstColor("2", "yellow", 255, 219, 79),
        MstColor("3", "red", 231, 87, 53),
        MstColor("4", "gray", 170, 170, 170),
        MstColor("5", "black", 51, 51, 51),
    )

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_second, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val triforce = view.findViewById<ImageView>(R.id.triforce)

        var num = 0

        // ボタンを押す度にトライフォースの色を変える
        view.findViewById<Button>(R.id.button_second).setOnClickListener {
            val color = mstColorList[num]

            // 色設定
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                triforce.colorFilter = BlendModeColorFilter(Color.rgb(color.r_code, color.g_code, color.b_code), BlendMode.SRC_ATOP)
            } else {
                triforce.setColorFilter(Color.rgb(color.r_code, color.g_code, color.b_code), PorterDuff.Mode.SRC_ATOP)
            }

            // ループ設定
            if (num + 1 == mstColorList.size) {
                num = 0
            } else {
                num += 1
            }
        }
    }

}

 
 
ポイントとしては
・setBackgroundColorは使えない
・Color.rgbでRGBカラーコードを適用すること
・colorFilterを使って画像に指定した色で塗りつぶしを行うこと

あとsetColorFilterは非推奨になった!!
API29以降では BlendModeColorFilter を使うこと。

BlendModeColorFilterって画像の色フィルターを適用するイメージかな。。

○URL
・BlendModeColorFilter
https://developer.android.com/reference/android/graphics/BlendModeColorFilter
・BlendMode
https://developer.android.com/reference/android/graphics/BlendMode
 
 
【結果】

16進数のカラーコードを使わずに、
RGB値だけで色が変わるようになりました!

やっとSwitch買ってゼル伝の「ブレスオブザワイルド」やってます!

小一時間くらいで作ったサンプルですが、
色連続で変わるだけでトライフォース良い感じになるなw
もっと色追加してゲーミングPC的な感じにするとよくなるかも!?

てことで、みなさんも一緒に。

せーーーーーのっ.....
 
 
 
 
 
 
 
 
  
 

 エ・バ・ラ・の・ご・ま・だ・れ♪

 

...(やっとパラセール取ったとこ。モリブリンに勝てんw)

【参考記事】
https://stackoverflow.com/questions/56716093/setcolorfilter-is-deprecated-on-api29

https://qastack.jp/programming/1309629/how-to-change-colors-of-a-drawable-in-android

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

AndroidでのWidget実装における落とし穴(画像編)

はじめに

おはこんにちは!
2020年度XTechグループアドベントカレンダーの5日目を担当します、エキサイト株式会社の21卒内定者、奥田です?‍♂️
5日目はAndroidでWidgetを実装する際の画像の扱い方について書いていきます。
(今回の記事では画像の表示に絞り記事を書いていきます。Widgetの実装方法などには触れないため他の記事と併用してください?‍♂️)

そもそもWidgetってなんだ?という人のために公式のドキュメントを貼っておきます!?
Widget公式ドキュメント

問題点

WidgetではレイアウトはRemoteViewsをベースとしています!
今回の作業内容ではリモートの画像を取得し、Widget(RemoteViews)に表示する必要がありました。

画像の取得も確認でき、後は表示するだけで楽勝じゃん!っと思い下記を実装しました?

Widget.kt
val views = RemoteViews(context?.packageName, R.layout.sample_widget)

views.setImageViewUri(R.id.imageView, item.imageURL)

しかし結果は。。。
なにも表示されませんでした?

解決策

setImageViewUriで画像が表示されない原因の特定のためにAndroidのソースコードを見ることにしました。

setImageViewUri公式ドキュメントを載せておきますので詳しく見たい方はどうぞ!

RemoteViews.java
/**
 * Equivalent to calling {@link ImageView#setImageURI(Uri)}
 *
 * @param viewId The id of the view whose drawable should change
 * @param uri The Uri for the image
 */
public void setImageViewUri(int viewId, Uri uri) {
    setUri(viewId, "setImageURI", uri);
}

Equivalent to calling {@link ImageView#setImageURI(Uri)

→ImageViewのsetImageURI(Uri)まで飛んでみる✈️

ImageView.java
/**
 * Sets the content of this ImageView to the specified Uri.
 * Note that you use this method to load images from a local Uri only.
 * <p/>
 * To learn how to display images from a remote Uri see: <a href="https://developer.android.com/topic/performance/graphics/index.html">Handling Bitmaps</a>
 * <p/>

 * <p class="note">This does Bitmap reading and decoding on the UI
 * thread, which can cause a latency hiccup.  If that's a concern,
 * consider using {@link #setImageDrawable(Drawable)} or
 * {@link #setImageBitmap(android.graphics.Bitmap)} and
 * {@link android.graphics.BitmapFactory} instead.</p>

 * <p class="note">On devices running SDK < 24, this method will fail to
 * apply correct density scaling to images loaded from
 * {@link ContentResolver#SCHEME_CONTENT content} and
 * {@link ContentResolver#SCHEME_FILE file} schemes. Applications running
 * on devices with SDK >= 24 <strong>MUST</strong> specify the
 * {@code targetSdkVersion} in their manifest as 24 or above for density
 * scaling to be applied to images loaded from these schemes.</p>
 *
 * @param uri the Uri of an image, or {@code null} to clear the content
 */
@android.view.RemotableViewMethod(asyncImpl="setImageURIAsync")
public void setImageURI(@Nullable Uri uri) {
    if (mResource != 0 || (mUri != uri && (uri == null || mUri == null || !uri.equals(mUri)))) {
        updateDrawable(null);
        mResource = 0;
        mUri = uri;

        final int oldWidth = mDrawableWidth;
        final int oldHeight = mDrawableHeight;

        resolveUri();

        if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
            requestLayout();
        }
        invalidate();
    }
}

Note that you use this method to load images from a local Uri only.

→リモートの画像はできないことを確認!!

On devices running SDK < 24, this method will fail to
apply correct density scaling to images loaded from
{@link ContentResolver#SCHEME_CONTENT content} and
{@link ContentResolver#SCHEME_FILE file} schemes.

→代案としてsetImageDrawableかsetImageBitmapを使うことができるようです?

参考ドキュメント(setImageViewUri)
https://developer.android.com/reference/android/widget/ImageView#setImageURI(android.net.Uri)

Widget(RemoteViews)ではImageViewを扱うメソッドとして、

  • setImageViewUri
  • setImageViewResource
  • setImageViewBitmap
  • setImageViewIcon

上記の4点があります。

代案としてWidget(RemoteViews)で使用できるのはsetImageViewBitmapのためこのメソッドで実装します!
つまりはRemoteViewsにおいてリモートの画像を表示するにはBitmapに変換しsetImageBitmapにセットしなければいけないことが分かりました☺️

実装

ここまでの調査結果を踏まえて下記のように書き直しました!

Widget.kt
val views = RemoteViews(context?.packageName, R.layout.sample_widget)

// 画像URLからBitmapに変換する処理
val bitmap = getBitmapFromUrl(item.imageURL)
views.setImageViewBitmap(R.id.imageView, bitmap)

結果は無事に画像の表示に成功しました✨

最後に

今回はWidgetでの画像表示について書かせていただきました?‍♂️
稚拙な文章でしたが最後まで読んでいただき幸いです!
ここが間違ってるぞ!?や画像の真髄を伝授してやろう!っという方がいましたらコメントしていただけると幸いです!!!

また弊社では採用もバシバシ実施しているので興味のあるかたがいましたらご応募ください?‍♂️
https://www.wantedly.com/companies/excite

XTechグループ Advent Calendar 2020 6日目の執筆担当はkiwamunet_newさんです。
引き続きお楽しみください✨

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

Pepper SDK入門(19) SilentからListenへ

今回の目標

Listenを使ってPepperに簡単なフレーズを聞いてもらえるようにする回です。
Pepperが聞き手となることで、ひとり言は会話の始点に変わるでしょう。

//フレーズを決める(ここでは「Hello」)
PhraseSet phraseSet = PhraseSetBuilder.with(qiContext)
                                      .withTexts("Hello")
                                      .build();

//アクションをビルドする
Listen listen = ListenBuilder.with(qiContext)
                             .withPhraseSet(phraseSet)
                             .build();

//アクションを同期実行する
listen.run();

これが今回の学習内容になります。

Freeze!

Pepperは聞き取り中、上半身が微妙に動いています。既視感ですね。BodyLanguageは止められるのです。

Listen listen = ListenBuilder.with(qiContext)
                             .withPhraseSet(phraseSet)
                             .withBodyLanguageOption(BodyLanguageOption.DISABLED)
                             .build();

listen.run();

言語を変える

デフォルトでは優先言語を使うPepperですが、Localeを使用することで異なる言語を選ぶことができます。
例えばPepperにフランス語を聞き取って欲しい場合には、Language.FRENCHとRegion.FRANCEを使えば良いのです。C’est du gâteau〜

Locale locale = new Locale(Language.FRENCH, Region.FRANCE);
Listen listen = ListenBuilder.with(qiContext)
                             .withPhraseSet(phraseSet)
                             .withLocale(locale)
                             .build();

listen.run();

Localeについてのより詳細な情報はAPIのドキュメントを参照してください。

Q.Listenしたら何ができる?

A.音声コマンドができる

$\style{background-color:yellowgreen;}{音声コマンドしてみよう}$
1.フレーズを決める
Pepperに聞いて欲しいフレーズを定義します。
一つのフレーズだけを定義することも可能ですが、ユーザーが全く同じことを台詞のように言ってくれるとは限りません。
あらかじめ、いくつかのバリエーションを設定しておきましょう。

PhraseSet phraseSetYes = PhraseSetBuilder.with(qiContext)
                                         .withTexts("yes", "OK", "alright", "let's do this") //Pepperに聞き取ってもらうフレーズ
                                         .build(); 

PhraseSet phraseSetNo = PhraseSetBuilder.with(qiContext)
                                        .withTexts("no", "Sorry", "I can't") //Pepperに聞き取ってもらうフレーズ
                                        .build(); 

2.Listenの実行

Listen listen = ListenBuilder.with(qiContext)
                             .withPhraseSets(phraseSetYes, phraseSetNo)
                             .build();

ListenResult listenResult = listen.run();

3.聞こえたことを教えてもらう
聞き取った人声に該当するフレーズを取得します。

Log.i(TAG, "Heard phrase: " + listenResult.getHeardPhrase().getText()); // ここで"Heard phrase:"の後ろに聞き取り結果を出力する
PhraseSet matchedPhraseSet = listenResult.matchedPhraseSet 

これでPepperも手のひらの上です。実機のサイズは変わりません。

ListenとChatの二者択一

Listenは短い対話に適したものです。対してChatは、もう少し長い会話のラリーに向いています。
そしてSayやListenを実行している時にChatが機能しないように、SayやChatを実行している時にListenは機能しないので注意してください。排反事象。

Not the Microscope but the Microphone〜音の波を電気信号に変えて〜

Pepperのマイクは単一指向性です。全指向性でも超指向性でも双指向性でもないため、前方からの声しか聞き取れません。Pepperに話しかけたい時には、まずPepperの前に立ちましょう。

Listenについてもっと知りたい場合は、例によってAPIのドキュメントを参照してください。

ここまでの内容はこれの"Catch few words"で体験できますので、是非。

___pepper_________.png
またね。

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

【Android】Single ActivityにおけるsharedViewModelのスコープについて【Koin】

はじめに

KoinでViewModelをインジェクトする際には、viewModel()sharedViewModel()の二つのやり方がある。
僕が担当しているアプリでは基本Fragment-ViewModelで一対一の関係になっているのだけど、ViewPagerを使用していたりするとViewModelをFragment間で共有したい場面が多くて、そういう時にsharedViewModel()が大活躍してくれる。

ただ、今までなんとなくsharedViewModel()を使ってきてしまったが故に、こないだちょっと沼にハマってしまった。
今回はそんなsharedViewModel()で共有したViewModelのスコープについてご紹介。

sharedViewModelとは?

前述の通り、ViewModelをFragment間で共有できるViewModelをインジェクトする。
と、今まで思っていた。

というのも実はこのsharedViewModel()、インスタンスを共有するスコープがデフォルトでActivityになっているらしい。
中の実装をチラッと覗いてみると、こんな感じになっている。

/**
 * Lazy getByClass a viewModel instance shared with Activity
 *
 * @param qualifier - Koin BeanDefinition qualifier (if have several ViewModel beanDefinition of the same type)
 * @param from - ViewModelStoreOwner that will store the viewModel instance. Examples: "parentFragment", "activity". Default: "activity"
 * @param parameters - parameters to pass to the BeanDefinition
 */
inline fun <reified T : ViewModel> Fragment.sharedViewModel(
        qualifier: Qualifier? = null,
        noinline from: ViewModelStoreOwnerDefinition = { activity as ViewModelStoreOwner },
        noinline parameters: ParametersDefinition? = null
): Lazy<T> = kotlin.lazy { getSharedViewModel<T>(qualifier, from, parameters) }

見て欲しいのは、第二引数のfromのところ。
コメントでViewModelStoreOwner that will store the viewModel instance. Examples: "parentFragment", "activity". Default: "activity"とある通り、デフォルトではViewModelのインスタンスを保存している場所がactivityとなっている。

そのため、「ViewModelをFragment間で共有したい時に使用する」という認識は誤りで、「Activity内で共有できるViewModelをインジェクトする」という認識の方が正しかったりする。

スコープを正しく認識していないことによる問題

sharedViewModel()のスコープをきちんと認識できていなかったことがわかったわけだけども、これが原因であるバグが発生していた。

それが、下の画像のパターン。
こんな感じでFragmentAFragmentBが乗っている画面を作りたくて、二つのFragmentでViewModelを共有するためにsharedViewModel()を使用していた。

で、この画面が実は下の画像のように、同じ画面に遷移する導線を持っていた。

この時ViewModelは画面遷移の際に新たに生成される、と思っていた

実はこのViewModel、sharedViewModel()を使っているせいで、画面遷移をしても同じインスタンスのViewModelが使用されている。
自分のプロジェクトはSingleActivityで作られているので、基本的にアプリが起動している間ずっとViewModelのインスタンスが破棄されない状態になっていた。
このままでは、前のデータが残っていたり無駄にインスタンスが残っていたりしてしまう。

解決方法

解決方法はそんなに難しくない。
ViewModelを保持しておくスコープを、各Fragmentにしてあげればいいだけ。

さっき見たsharedViewModel()の第二引数のデフォルト引数が{ activity as ViewModelStoreOwner }となっていたので、こいつを変えてあげる。

private val viewModel: ViewModel by sharedViewModel()

こいつを

private val viewModel: ViewModel by sharedViewModel(from = { requireParentFragment() })

こう!

まとめ

SingleActivityでアプリを作っている場合は、スコープをActivityにしたい場面ってあんまりないと思うので、もしかしたら基本parentFragmentを指定してあげるのがいいのかもしれない。
Daggerもそうだけど、DIは設定が難しくてなんとなく使っちゃいがちだけど、やっぱりこういうのきちんとドキュメント読まなきゃだめだね。

おわり。

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

Androidの自作ライブラリの処理実行時にNoClassDefFoundErrorが起きる

概要

  • 自作ライブラリの処理の実行時に、NoClassDefFoundErrorが起きる
  • 直接の原因は、自作ライブラリをMavenリポジトリにデプロイする際に、自作ライブラリが依存しているライブラリの情報が欠落すること
  • 依存ライブラリの情報が欠落するのは、Maven Publishプラグインの設定が間違っているから
  • Android公式のMaven Publishプラグインの設定にしたら問題は解決した
  • 特に理由がないなら、公式の設定におとなしく従っておきましょう

前提

アプリX、自作ライブラリY、外部ライブラリZがある。
アプリX -> 自作ライブラリY -> 外部ライブラリZの方向に依存関係がある。
自作ライブラリYも外部ライブラリZもMavenリポジトリにホストしている。

遭遇したこと

アプリXのビルドは通るが、自作ライブラリY内の外部ライブラリZを使う処理の実行時に、NoClassDefFoundErrorが発生する。

Errorログの例

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.app, PID: 31546
    java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/activity/result/contract/ActivityResultContracts$StartActivityForResult;
        at com.exmaple.app.MainActivity.<init>(MainActivity.kt:<73>)
        ...

原因

直接の原因:ライブラリの依存関係の情報の欠落

アプリXの依存ライブラリとして、外部ライブラリZを記述すると、この問題は起きなくなった。
問題は起きなくなったので解散!、と言いたいところだが、謎が残った。

Android Xなどの普通のライブラリでは、このような問題が起きないということだ。
たとえば、"recyclerview"というライブラリは、"androidx.core:core-ktx"というライブラリに依存している1
ただ、アプリの依存関係には"recyclerview"を書くだけで、実行時にNoClassDefFoundErrorは起きない。
これは、"recyclerview"が"androidx.core:core-ktx"に依存しているということが、アプリにも伝わっているということだ。

よって、自作ライブラリYが外部ライブラリZに依存していることがアプリに伝わっていないことが問題の原因であることが分かる。
実際、自作ライブラリYのpomファイルとrecyclerviewのpomファイルを見ると、前者にはの情報がない2

自作ライブラリYのpom

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>libraryY</artifactId>
  <version>1.0.0</version>
  <packaging>aar</packaging>
</project>

Android Xのrecyclerviewのpomの抜粋

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>androidx.recyclerview</groupId>
  <artifactId>recyclerview</artifactId>
  <version>1.1.0</version>
  <packaging>aar</packaging>
  ...
  <dependencies>
    <dependency>
      <groupId>androidx.core</groupId>
      <artifactId>core</artifactId>
      <version>1.1.0</version>
      <type>aar</type>
      <scope>compile</scope>
    </dependency>
    ...
  </dependencies>
</project>

自作ライブラリから依存ライブラリの情報が欠落する原因

単刀直入に言って、Maven Publishプラグインの設定に問題がある。

問題解決前の自作ライブラリYのMaven Publishプラグインの設定

apply plugin: 'maven-publish'
...
publishing {
    publications {
        release(MavenPublication) {
            groupId 'com.example'
            artifactId 'libraryY'
            version '1.0.0'
            artifact source: file("${project.buildDir}/outputs/aar/${project.name}-release.aar")
        }
    }
    repositories {
        maven {
            url "<maven repoisitory url>"
        }
    }
}

artifact sourceにaarを指定しているのが問題で。aarにはライブラリの依存関係の情報は含まれない。

問題の対処方法

ライブラリの依存関係が書いてあるのはgradleのファイルなので、gradleのファイルに記述した依存情報をpomに出力する必要がある。こう書くと面倒そうだが、Android公式ページの設定に従うと、gradleからpomへの依存関係の転写は勝手にやってくれる。問題解決後の自作ライブラリYのMaven Publishのプラグインの設定を示す。

問題解決後の自作ライブラリYのMaven Publishプラグインの設定

apply plugin: 'maven-publish'
...
afterEvaluate {
    publishing {
        publications {
            release(MavenPublication) {
                from components.release // この記述が重要
                groupId 'com.example'
                artifactId 'libraryY'
                version '1.0.0'
            }
        }
        repositories {
            maven {
                url "<maven repoisitory url>"
            }
        }
    }
}

"from components.release"の記述の記述によって、BuildVariantが"Release"のGradleのビルド設定から、aarをビルドしたり、ライブラリの依存情報をpomのに出力してくれる。

それにより、アプリXに外部ライブラリZの依存関係を記述しなくても、実行時にNoClassDefFoundErrorは発生しなくなる。

最後に

「特に理由がないなら、Android公式の設定に従うのが楽」ということだ。


  1. https://github.com/androidx/androidx/blob/androidx-master-dev/cardview/cardview/build.gradle 

  2. pomファイルは、"~/.gradle"配下を探すと見つかる。Mavenプロジェクトの情報が書かれたファイル。詳細は、http://maven.apache.org/guides/introduction/introduction-to-the-pom.html。 

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

もしあなたが急にAndroidアプリを業務で作るはめになった場合の選択肢(2021年初頭版)

本記事はAndroid Advent Calendar 2020の2020/12/01分です。

初っ端ということなので、2020年末と2021年頭でのAndroidエンジニアとして初めて業務でやる場合に抑えておいたほうが良い最低限の部分を書いていこうと思います。(ツッコミ待ちです)

対象

  • 2021年3月ぐらいまでに !!業務!! でAndroidアプリを作らされる事になった可愛そうな人が居たとします
  • この人は手続き型言語でオブジェクト指向プログラミングができる知識があり、Androidアプリもなんとなく趣味で作ったこともあるぐらいのレベル感です(なので上長からいきなりお前Android担当なと言われた)
  • 最低限のAndroidアプリの作成の知識はあるものとします(画面の表示にはActivityがいるよとかは書かない)
  • ゲームは対象外です
  • 業務でAndroidアプリを作ることを想定しています(≠趣味)
  • toB toCどちらを作るにしても必要な部分だけの抜粋なので特にtoC向けには不足が多いと感じられる方もいるかも知れません
  • あくまでこれから新規に作るアプリの話であり、すでに歴史あるアプリは対象外です

マルチプラットフォーム対応フレームワークは使わない(絶対に死守しましょう)

もし、Android/iOS両方のプラットフォームでアプリをリリースする必要があったとしても、マルチプラットフォーム対応用フレームワークは使ってはいけません。
ちょっと詳しい上長なんかだと、Flutterがどうだとか、Reactがどうだとか言ってくると思いますが絶対に流されてはいけません。
あれはそれぞれのプラットフォームでの開発のプロが居る上での選択肢なので、急でかつ自分ひとりチームのような場合には絶対に使わないことがおすすめです。
取り敢えずネイティブで作ってみて、自分にAndroidの業務用知識がついてから検討してでも遅くはありません。

もしどうしてもマルチプラットフォームでと強く言われたら、PWA(Progressive Web Apps)に話を持っていきましょう。

開発用IDEはAndroidStudio一択(基本使用しましょう)

頑張ればいろいろなIDEや最悪テキストエディタでも開発ができますが、よっぽどマゾな行為が好きなのでなければAndroidStudioのリリース版(2020/12/01時点では4.1)を使いましょう。
ベータやRCは痛くても泣かない人用なので、業務で使うには向いていません。業務ではリリースを使うのが安定です。スクリーンショット 2020-11-30 18.43.19.png
あと、一度使い始めたら、新しいリリースがでてもぽんぽんとAndroidStudioをアップデートしてはいけません。
アップデートを適応するとビルドができなくなって泣いている人がtwitterなどで複数観測できます。そういうことです。
アップデート後2,3週間してビルドできない問題などへの知見が貯まってからアップデートするのがおすすめです。

Min SDK versionは26(基本死守しましょう)

基本Android8以上サポートで仕様を詰めましょう。
最悪Android7.1.1までを死守しましょう。
8未満と8以上でバックグラウンドでのアプリの挙動などに大きな違いがあり、最初から8以上を考えて設計・実装・テストを行えるかどうかで、あなたの3ヶ月後の休日の取得率にダイレクトで影響します。

上から「とはいえ古い端末のユーザーさんが〜」みたいなこと言われたら下記のデータを突きつけて納得させましょう。
スクリーンショット 2020-11-30 18.40.19.png
これはAndroidStudioで新しいプロジェクトを作る際に"Help me choose"ボタンを押した場合に見れるデータです。
このデータは海外もあわせているので、古いバージョンの割合が高く出がちです。

説得が難しそうであれば、スマタブさんのデータを突きつけましょう。
現時点で8以上のユーザーが83%を超えています。

Android Version リリース日 シェア
11 2020/09/09 2.5%
10 2019/09/04 38.8%
9 2018/08/07 25.0%
8.1 2017/12/06 6.2%
8.0 2017/08/22 10.6%
リリースから3年も経ってますし、もう2年縛りで買い替えてますよ〜
古いのを使い続けるのは無理やりスマホにされた高齢者がほぼ全てなので僕らのサービスのユーザーにはマッチしないですよ〜

あたりがオススメのワードです。

高齢者が対象のサービスだった場合には諦めてプロを雇いましょう。

開発言語はKotlin(なるべく使用しましょう)

2020/12現在、基本的にAndroidアプリ開発において、Javaでできることはほぼ全てKotlinでもできるのでKotlinを使って開発しましょう。
ネットにあるサンプルコードや情報なんかもKotlinが多いので、使用していて損は全く無いです。

UIの作成はXMLで(基本使用しましょう)

最近Jetpack ComposeなるコードでUIを作れるツールがでましたが、まだAlpha版です。
jetpack-compose-hero.png

これまでの知見の量や、動作の安定性などを鑑みてもXMLでの作成が安全です。
AndroidStudioで新しいプロジェクトを作成した場合も、まだXMLでのUI作成がデフォルトで行われていますので、長いものには巻かれましょう。

Android Support LibraryはAndroidXに(絶対に使用しましょう)

2年ぐらい前まではAndroidプラットフォームの複数バージョンでの挙動の統一化などを楽にするためにAndroidSupportLibraryというものがありました。
最近になってAndroidXと名前を変え、AndroidSupportLibraryについてはdeprecatedになったので今後作るものについては基本的にAndroidXを使います。

AndroidStudio4.1で新しいプロジェクトを作ると標準でAndroidXを使ったプロジェクトになります。スクリーンショット 2020-11-30 21.42.06.png
間違ってもこのチェックボックスにチェックを入れてはいけません。

開発もかなり活発的に行われており、すぐに最新バージョンが変わっていくので下記のリリースノートページをブックマークしておくのがおすすめです。
AndroidX releases https://developer.android.com/jetpack/androidx/versions

ちょっとした非同期処理はKotlin Coroutinesで(基本使用しましょう)

Androidはユーザーの操作感を担保するために、UIを更新するスレッドで重い処理を行ってはいけないという確固たるポリシーがあります。
昔は無視できていたんですが、最近は普通にエラーを出したりするので気をつけます。

  • ネットワークアクセス
  • DB操作
  • 重い計算処理

だいたいここら辺は全部非同期処理にします。

Androidで非同期処理は大昔はThreadとかAsyncTaskでした。

ちょっと前はRxJavaが使われてたように思います。
しかし、RxJavaはRxというかなり特殊で難しい概念を理解しなければならず、また、あとからコードを読んだ際に処理がどう行われていくかがすぐに理解しづらいという問題があります。

なので、よっぽどややこしい事をしたいというのでなければ非同期処理は全部Kotlin Coroutinesが良いと思います。

かんたんな使い方は、それぞれのモジュールのbuild.gradleでimplementationします。

build.gradle
dependencies {
    ~~~~~~
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1"
    ~~~~~~

}

すでに最近のASでプロジェクトを作った場合には、Activityクラスなんかでこんな感じに実行できます。
これでActivityのライフサイクルに合わせて処理のキャンセルなんかをしてくれます(実際には色々とあるのでちゃんと調べて使用するのが良いです)

MainActivity.kt
    private fun doHoge(){
        lifecycleScope.launch(Dispatchers.IO) { 
            //なにか重い処理
            hoge()
            withContext(Dispatchers.Main){
                // UI操作処理
                updateText()
            }
        }
    }

Google公式コードラボをやると結構わかっていいと思います。
Google公式コードラボ

APIアクセスはRetrofit2ライブラリで(基本使用しましょう)

AndroidでHTTPアクセスする際に、公式標準APIでHttpUrlConnectionがあります。
しかし、使いづらいのでライブラリを使うのが一般的です。

Volley

https://developer.android.com/training/volley/index.html

  • Android developers公式に載っている
  • 歴史があるので知見もネット検索ででてくる
val textView = findViewById<TextView>(R.id.text)
// ...

// Instantiate the RequestQueue.
val queue = Volley.newRequestQueue(this)
val url = "https://www.google.com"

// Request a string response from the provided URL.
val stringRequest = StringRequest(Request.Method.GET, url,
        Response.Listener<String> { response ->
            // Display the first 500 characters of the response string.
            textView.text = "Response is: ${response.substring(0, 500)}"
        },
        Response.ErrorListener { textView.text = "That didn't work!" })

// Add the request to the RequestQueue.
queue.add(stringRequest)

Android Developers Volleyサンプルページ https://developer.android.com/training/volley/simple.htmlから抜粋

Retrofit

https://square.github.io/retrofit/

  • Squareが作っている
  • 最近の人気
  • 基本的にRESTに特化しているのでそれ以外は別のものが必要
private val builder: Retrofit.Builder = Retrofit.Builder().baseUrl("https://www.example.com/")
private val okHttpClient = OkHttpClient.Builder().build()

interface Api {
    @GET("users")
    fun getUser(@Query userId: Long):Call<User>
}

builder.client(okHttpClient).build().create(Api::class.java).getUser(1000).enqueue(object : Callback<User>{
    override fun onResponse(call: Call<User>, response: Response<User>) {
        if(!response.isSuccessful){
            return // エラー
        }
        // 正常処理
    }

    override fun onFailure(call: Call<User>, t: Throwable) {
        // エラー処理
    }

})

上記のKotlin Coroutineにも対応していてすごく楽にかける。

private val builder: Retrofit.Builder = Retrofit.Builder().baseUrl("https://www.example.com/")
private val okHttpClient = OkHttpClient.Builder().build()

interface Api {
    @GET("users")
    suspend fun getUser(@Query userId: Long):User
}

lifecycleScope.launch {
    val user = try{
        builder.client(okHttpClient).build().create(Api::class.java).getUser(1000)
    }catch (e:Exception){
        // if(e is HttpException) でエラーハンドリングできる
        return@launch 
    }
    // 処理の続き
}

最近人気で知見も多くあるので、RESTでのアクセスなら基本Retrofit2を使えばいいと思います。

ServiceじゃなくてWorkManagerを使いましょう(可能な限り使用しましょう)

ちょっと前まではアプリがユーザーから見えないタイミングで処理をしたい場合、Serviceを使っていました。
しかし、8以降でアプリのバックグラウンド実行に対してかなり厳しい変更が入ったので今後はSeviceではなくWorkManagerを使いましょう。

ただし、音楽再生のような特定の場合にはServiceを使う必要もあります。

バックグラウンドでの処理については8からの変更が非常に多いので、アプリの設計時にGuide to background processingを読み込むことを強くおすすめします。

アプリの設計は公式のアプリアーキテクチャガイドを参考に(可能な限り使用しましょう)

Androidは公式で推奨のアーキテクチャがあります。
公式ページのアプリ アーキテクチャ ガイドで公開されています。
final-architecture.png

非常に凝ったアプリを最初から作らない限り、このページで紹介されているアーキテクチャ(及びライブラリ等)を参考に設計するのが楽でかつ安全だと思います。

公式で用意されているアーキテクチャコンポーネントに含まれる便利なクラスたち(可能な限り使用しましょう)

上のアプリ設計にも関係しますが、アーキテクチャコンポーネント https://developer.android.com/jetpack?hl=ja#architecture-componentsには非常に便利なクラスが多数含まれているのでどんどん使ったほうがいいです。

その中から使用推奨なものをいくつかピックアップします。

ViewModel

昔は何でもかんでも処理をActivity(もしくはFragment)クラスに書いてFatActivityなどという蔑称が生まれていました。
そんな中、今までActivity/Fragmentでやっていたような処理を主に置くためのViewModelクラスという便利なものができたので、処理はそっちに分けていくほうが良いです。

画面の回転などで画面内で保持しておいて欲しいデータなどをonSaveInstanceStateなんかで頑張って保持していたのがそういう事をしなくてよいというメリットもあります。

LiveData

自身で値を保持しつつ、Androidのややこしいライフサイクルに合わせて値の更新などを通知してくれる便利なクラスです。
ViewModelクラスとDataBinding機能と組み合わせてAPIの返り値から自動でUIの表示を変えるみたいなことが、少ないコードでわかりやすくかつメモリリークの危険性少なくできます。

Room

今までデータの永続化に使っていたSQLiteの代わりとなるORMライブラリです。
裏はSQLiteですが、大体はモダンな感じでSQLを意識しなくていいですし、LiveDataと組み合わせてDBの変更通知を受け取れたりと非常に便利なのでローカルにデータを永続化するなら使って損はありません。

まあ、公式ページのアプリ アーキテクチャ ガイドに沿って設計したら全部使うことになると思います。

操作ログはGoogle アナリティクスで(可能な限り使用しましょう)

業務で作るアプリである以上、ユーザーの動向調査は必須機能になってきます。
多様なサービスがリリースされていますが、まずはGoogle アナリティクス(旧Firebaseアナリティクス)でいいと思います。

また、Google アナリティクスは取得したデータをそのままBigquery(高速なデータ基盤)に流すことができるので、Googleアナリティクスを使うなら確実に接続設定をしておいたほうがいいです。

あとから操作ログの検索がしたくなった時の楽さが全然違います。

Hiltを使ってDI(可能な限り使用しましょう)

AndroidでのDIについてはHilt https://developer.android.com/training/dependency-injection/hilt-android?hl=jaというライブラリが登場したため非常に使いだしが簡単になりました。

ちょっと前まではDagger+Dagger Androidサポートみたいなのが多かったですが、使い出すまでに大変だしボイラープレートコードみたいなのを延々と書かないとだめで面倒でしたが、大体そういうのがなくなったので、導入が非常に楽になりました。

なので、DIの概念を理解していて、それに沿った設計とコーディングができるならぜひとも使ったほうがいいと思います。
ただ、まだAlpha版までしか無いので、将来入れることを見越して設計コーディングしつつ、今は入れないという選択肢もあるかと思います。

アプリのテスト配布はGooglePlayConsoleの内部テストでもOK

アプリがある程度できてきて、社内でドッグフーディング的に使って欲しい時に、アプリをどうやって配布するかという課題があります。
取り敢えずそんなに人数が多くなく、取り敢えず触って欲しいみたいなときにはGooglePlayConsoleの内部テストで配布するのもありだと思います。

もしもうちょっとテスターチームが多いとか、課題が出てきたらDeployGate https://deploygate.com/などを検討してもいいかもしれません。

HW機能を使うアプリ機能は避ける(なるべく死守しましょう)

HW機能を使うアプリ機能はなるべく初回リリースに含めないか、代替手段で実現できないかを検討したほうがいいです。
AndroidでHW機能(カメラ・Bluetooth・GPS等)を使う場合には非常に落とし穴が多く、手持ちのデバイスで動いたからといって他のデバイス・環境でうまく動くかの保証がしづらいです。
ですので、可能な限り代替の手段で機能を実現できないか検討し、もし無理な場合にはテストのための工数を非常に多く持つ・リリース後の不具合対応のためのリソース調整を行っておくなどの下準備をしたほうが良いです。

まとめ

  • とりあえずAndroidDevelopersの開発者ガイドをくまなく読むのがオススメ

もし今自分が業務として初めてAndroidアプリを作るはめになってしまった時に知りたいであろうことをつらつらと書いてみました。

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