20200721のAndroidに関する記事は8件です。

React Native for AndroidでデバイスIDの有無で違うタスクが実行される

はじめに

React Nativeのrun-androidコマンドはPCに接続したAndroid端末すべてにapkをインストールします。
一方で--deviceIdでデバイスIDを指定すると特定のAndroid端末だけにapkをインストールできます。
ですがデバイスID指定ありとなしでは実行されるGradleタスクに違いがあったので調べた結果をまとめました。

バージョン

  • @react-native-community/cli-platform-android: 4.9.0

実行されるタスクの調査方法

react-native コマンドの --verbose オプションを指定すると実行されるコマンドが分かるのでこれで確認します。

デバイスIDを指定しない場合

$ react-native run-android --verbose
info Running jetifier to migrate libraries to AndroidX. You can disable it using "--no-jetifier" flag.
Jetifier found 1041 file(s) to forward-jetify. Using 8 workers...
info JS server already running.
info Installing the app...
debug Running command "cd android && gradlew.bat app:installDebug -PreactNativeDevServerPort=8081"

app:installDebugタスクが実行されています。これはアプリをビルドして全Android端末にapkをインストールします。

デバイスIDを指定する場合

$ react-native run-android --verbose --deviceId xxxxx
info Running jetifier to migrate libraries to AndroidX. You can disable it using "--no-jetifier" flag.
Jetifier found 1391 file(s) to forward-jetify. Using 8 workers...
info JS server already running.
info Building the app...
debug Running command "gradlew.bat build -x lint"

buildタスクが実行されています。これはアプリのビルド、テスト、Javadocのビルドなどが実行されます。
更に--tasksオプションでタスクを指定しても反映されないのでビルド以外のタスクを除外できません。

apkビルドだけで特定の端末にインストールしたい

現状はオプションなどで変更できないので以下のどちらかしかなさそうです。

  • インストールしたくないAndroid端末の電源を切るかUSBケーブルを抜く
  • node_modules/@react-native-community/cli-platform-android/build/commands/runAndroid/index.jsbuildApk内の"build""assembleDebug"に書き換える

以下のPull Requestが取り込まれるとビルドコマンドが統一されてassembleDebugタスクに置き換わるので将来的には解消しそうです。

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

React Nativeのrun-androidはデバイスIDの有無で実行されるタスクが違う

はじめに

React Nativeのrun-androidコマンドはPCに接続したAndroid端末すべてにapkをインストールします。
一方で--deviceIdでデバイスIDを指定すると特定のAndroid端末だけにapkをインストールできます。
ですがデバイスID指定ありとなしでは実行されるGradleタスクに違いがあったので調べた結果をまとめました。

バージョン

  • @react-native-community/cli-platform-android: 4.9.0

実行されるタスクの調査方法

react-native コマンドの --verbose オプションを指定すると実行されるコマンドが分かるのでこれで確認します。

デバイスIDを指定しない場合

$ react-native run-android --verbose
info Running jetifier to migrate libraries to AndroidX. You can disable it using "--no-jetifier" flag.
Jetifier found 1041 file(s) to forward-jetify. Using 8 workers...
info JS server already running.
info Installing the app...
debug Running command "cd android && gradlew.bat app:installDebug -PreactNativeDevServerPort=8081"

app:installDebugタスクが実行されています。これはアプリをビルドして全Android端末にapkをインストールします。

デバイスIDを指定する場合

$ react-native run-android --verbose --deviceId xxxxx
info Running jetifier to migrate libraries to AndroidX. You can disable it using "--no-jetifier" flag.
Jetifier found 1391 file(s) to forward-jetify. Using 8 workers...
info JS server already running.
info Building the app...
debug Running command "gradlew.bat build -x lint"

buildタスクが実行されています。これはアプリのビルド、テスト、Javadocのビルドなどが実行されます。
更に--tasksオプションでタスクを指定しても反映されないのでビルド以外のタスクを除外できません。

apkビルドだけで特定の端末にインストールしたい

現状はオプションなどで変更できないので以下のどちらかしかなさそうです。

  • インストールしたくないAndroid端末の電源を切るかUSBケーブルを抜く
  • node_modules/@react-native-community/cli-platform-android/build/commands/runAndroid/index.jsbuildApk内の"build""assembleDebug"に書き換える

以下のPull Requestが取り込まれるとビルドコマンドが統一されてassembleDebugタスクに置き換わるので将来的には解消しそうです。

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

AndroidのWebViewでハマったポイントまとめ

当記事について

WebViewは厄介な存在だと思います。
無数にあるWebページがちゃんと表示されるかが怪しいと感じる今日この頃。
あとはWebViewに限った話ではないですが、OSバージョンや端末によって起きたり起きなかったりするバグ。
当記事では、WebViewの実装で個人的にハマったポイントを随時更新していきます。

Android 6.0にて、動画コンテンツのあるURLを表示しようとするとクラッシュする

事象

Android 6.0にて、動画コンテンツのあるURLを表示しようとすると、以下のようにクラッシュします。

07-21 22:35:26.674 4770-4770/com.example.samplewebviewapp E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.samplewebviewapp, PID: 4770
    java.lang.NullPointerException: Attempt to invoke virtual method 'int android.graphics.Bitmap.getWidth()' on a null object reference
        at com.android.webview.chromium.WebViewContentsClientAdapter.getDefaultVideoPoster(WebViewContentsClientAdapter.java:1208)
        at org.chromium.android_webview.DefaultVideoPosterRequestHandler$1.run(DefaultVideoPosterRequestHandler.java:39)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:148)
        at android.app.ActivityThread.main(ActivityThread.java:5417)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
07-21 22:35:26.779 4770-4811/com.example.samplewebviewapp E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5

原因

HTML内部にvideoタグがあり、その中にposter属性という属性が存在します。
どうやらユーザー環境で利用できる動画が無い場合に表示させる画像を指定するようです。
<video>-HTML5タグリファレンス

この属性が指定されていない場合、WebChromeClient#getDefaultVideoPosterメソッドが呼ばれるのですが、その戻り値がnullであるために起きていました。

解決策

AndroidManifest.xmlWRITE_EXTERNAL_STORAGEを宣言する方法では解決できませんでした。
WebChromeClient#getDefaultVideoPosterメソッドをオーバーライドし、getDefaultVideoPosterメソッドがnullを返すようであれば、代わりの画像を返すように修正することでクラッシュしなくなりました。
ちなみに、BitmapFactory.decodeResourceメソッドの第2引数に指定するリソースIDですが、pngやjpegなどの画像ファイルを指定する必要があります。XMLで定義したリソースではnullとなってしまうので注意。

webView.webChromeClient = object : WebChromeClient() {
    override fun getDefaultVideoPoster(): Bitmap? {
        return if (super.getDefaultVideoPoster() == null) {
            BitmapFactory.decodeResource(applicationContext.resources, android.R.drawable.ic_media_play)
        } else {
            super.getDefaultVideoPoster()
        }
    }
}

結果としては以下の通り表示されますが、指定したリソースが画面に表示されてしまい、ブラウザで本来表示されている内容とは異なってきてしまいます。
ですので、1×1ピクセルの黒画像を用意するなどして、目立たないように表示すると良いかもしれません。

1.png

参考URL

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

【Android/Kotlin】sqliteを使う基本

やりたいこと

 今回はデータベースを作ったり、クエリを送るメソッドを紹介します。

openOrCreateDatabase

 引数にDB名、動作モード(0かPrivate)、カーソルをインスタンス化させるファクトリクラスを渡すことで、それらに則ったDBが作られる。以下の例では、sqlite-test-1というデータベースを作っています。データーベースはdata/data/パッケージ名/databaseというディレクトリに作られ、ターミナルから操作することも可能。

val database = baseContext.openOrCreateDatabase("sqlite-test-1.db", Context.MODE_PRIVATE, null) //databaseはsqlite-test-1.dbというDBが定義された

クエリをDBに送る

execSQL メソッドを使えば、簡単にクエリを送ることが出来る。しかし、全てのクエリをexecSQL で送れるという訳ではなく、SELECT / INSERT / UPDATE / DELETE対しては使うことが出来ない。テーブルの作成や削除、セーブに適したメソッド。
リファレンスに細かなことが書いてあります。:https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase#execSQL(java.lang.String,%2520java.lang.Object%5B%5D)

var sql = "create table if not exists contacts(_id integer primary key not null, name text, phone integer, email text)"
database.execSQL(sql)
database.execSQL("drop table if exists contacts")

INSERT文

 以下のように、ContentValues()内でカラムに対応するレコードを詰めてINSERT文書く。insertというメソッドは、引数にテーブル名、nullColumnHackというテーブルにnullを挿入するか否かを指定するパラメータ、ContentValue()をとって、テーブルに新たなレコードを挿入する。非常に簡単に行えることが分かる。

val values = ContentValues().apply {
            put("name", "Fred")
            put("phone", 12345)
            put("email", "fred@nurk.com")
        }

        //applyの有無での比較↑↓
//        val values = ContentValues()
//        values.put("name", "Fred")
//        values.put("phone", 12345)
//        values.put("email", "fred@nurk.com")

val generatedId = database.insert("contacts", null, values)

カーソル

 テーブルを読み込むときに使う。一行ずつカーソルがテーブルを読み取ることで効率的にデータを収集することが出来る。例えば、一気にDBのデータを呼び出したりしてしまうと、デバイスのメモリが死ぬ。しかし、カーソルを使うことでそれを防げる。以下ではSELECT文と一緒に使っている。

val query = database.rawQuery("select * from contacts", null)
query.use {
     while (it.moveToNext()) { //カーソルが次のレコードに移動する。何もないならfalseを返す。
     //全てのレコードを循環する
                with(it) {
                    val id = getLong(0)
                    val name = getString(1)
                    val phone = getInt(2)
                    val email = getString(3)
                    val result = "ID: $id, Name = $name, phone = $phone, email = $email"
                }
            }
        }

ここでは何を行っているかというと、rawQueryメソッドを使ってセレクト文の結果を取得しています。rawQueryメソッドはセレクト文等の結果が返されるクエリに対して用いるメソッドです。そして、そのクエリの結果をwhile文を通して一行ずつ取得しています。これが上で説明したカーソルです。

これらのメソッドを使う事で、おおよそのクエリをアンドロイド上で使うことが出来ます。

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

Exposed Dropdown Menu を使ってみる

はじめに

本稿は Material Design の Exposed dropdown menu の初期設定から実装までのメモ書きです。

※ Android アプリに Exposed dropdown menu を組み込むための最低限の情報が一通り含まれています。

◆ Exposed dropdown menu とは?

Material Design で Exposed dropdown menu と定義されているもので、選択されているアイテムがメニュー上に表示されるメニューです。

Filled exposed dropdown menu と Outlined exposed dropdown menu の 2 種類が存在します。

▼ Filled exposed dropdown menu

▼ Outlined exposed dropdown menu

  1. 非活性状態
  2. 活性状態
  3. 非活性かつ値がセットされている状態

※ 図はこちらから引用したものです

◆ 本メモの目的

本メモでは、Exposed dropdown menu を Android アプリ内で利用するための技術的情報を扱います。

デザインそのものに関する説明は行いません。1

◆ 参考情報

基本設定

以下に、Android Studio 上で作成した直後の Android Project に対して設定する方法を示します。

※ 下記サンプルと異なる設定をしたい場合などは Getting started with Material Components for Android 及び Exposed Dropdown Menus を参考にしてください。

◆ Dependency

  • レポジトリを指定します。2
  • Android Studio で Android 用プロジェクトを作成した場合はデフォルトで設定されています。

allprojects

build.gradle
allprojects {
    repositories {
        google()
        jcenter()
    }
}

android module

  • Android module に依存関係を設定します。
  • バージョンは ここ から確認してください。
  • 本稿執筆時は 1.1.0 を利用しました。
build.gradle
dependencies {
    implementation 'com.google.android.material:material:<version>'
}

◆ Material Styles

theme もしくは styles に Theme.MaterialComponents.* に由来するものを設定する必要があります。

以下に、Android Studio の Android Project で自動作成されたコード に対して theme の親を書き換える例を示します3

※ 書き換えたのは Theme.MaterialComponents.Light の部分のみです

styles.xml
<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.MaterialComponents.Light">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
</resources>

style が正しく設定されていない場合は、以下のようなエラーが発生します。

styleが正しく設定されていない時に発生するエラーの例
Caused by: android.view.InflateException: Binary XML file line #9: Binary XML file line #9: Error inflating class com.google.android.material.textfield.TextInputLayout
Caused by: android.view.InflateException: Binary XML file line #9: Error inflating class com.google.android.material.textfield.TextInputLayout

実装例

◆ Filled/Outlined のサンプル

以下に、Filled exposed dropdown menu と Outlined exposed dropdown menu の両方を表示するレイアウトを示します。

☆ 画面レイアウト

  • 起動直後に AutoCompleteTextView がフォーカスを得ないように、トップレベルで android:descendantFocusability="beforeDescendants" 及び android:focusableInTouchMode="true" を設定しています。
  • 文字の直接入力を避けるため、android:editable="false" を指定しています。4
activity_main.xml
<?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"
    android:descendantFocusability="beforeDescendants"
    android:focusableInTouchMode="true"
    android:padding="16dp"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/filled_exposed_dropdown_desc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Filled exposed dropdown menu"
        app:layout_constraintBottom_toTopOf="@id/filled_exposed_dropdown_container"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_chainStyle="packed" />

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/filled_exposed_dropdown_container"
        style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="State"
        app:layout_constraintBottom_toTopOf="@id/outlined_exposed_dropdown_desc"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/filled_exposed_dropdown_desc">

        <AutoCompleteTextView
            android:id="@+id/filled_exposed_dropdown"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:editable="false" />
    </com.google.android.material.textfield.TextInputLayout>

    <TextView
        android:id="@+id/outlined_exposed_dropdown_desc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="100dp"
        android:text="Outlined exposed dropdown menu"
        app:layout_constraintBottom_toTopOf="@id/outlined_exposed_dropdown_container"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/filled_exposed_dropdown_container" />

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/outlined_exposed_dropdown_container"
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="State"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/outlined_exposed_dropdown_desc">

        <AutoCompleteTextView
            android:id="@+id/outlined_exposed_dropdown"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:editable="false" />
    </com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

◆ メニューアイテムを扱うサンプル

下図のように振る舞うサンプルを示します。
  

☆ 画面レイアウト

activity_main.xml
<?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"
    android:descendantFocusability="beforeDescendants"
    android:focusableInTouchMode="true"
    android:padding="16dp"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/outlined_exposed_dropdown_desc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Outlined exposed dropdown menu"
        app:layout_constraintBottom_toTopOf="@id/outlined_exposed_dropdown_container"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_chainStyle="packed" />

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/outlined_exposed_dropdown_container"
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="State"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/outlined_exposed_dropdown_desc">

        <AutoCompleteTextView
            android:id="@+id/outlined_exposed_dropdown"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:editable="false" />
    </com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

☆ アイテム用レイアウト

dropdown_menu_popup_item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="16dp"
    android:ellipsize="end"
    android:maxLines="1"
    android:textAppearance="?attr/textAppearanceSubtitle1"/>

☆ メニューアイテムの設定

MainActivity.kt
package com.objectfanatics.myapplication

import android.os.Bundle
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // get AutoCompleteTextView
        val autoCompleteTextView =
            findViewById<AutoCompleteTextView>(R.id.outlined_exposed_dropdown)

        // create and set menu items
        val menuItems = listOf("MN", "NY", "NC", "ND")
        val adapter = ArrayAdapter(this, R.layout.dropdown_menu_popup_item, menuItems)
        autoCompleteTextView.setAdapter(adapter)

        // set default menu item
        autoCompleteTextView.setText("NY", false)
    }
}

おわりに

◆ やったこと

Material Design の Exposed dropdown menu の利用法を、最低限一通り確認してみました。

◆ 備考

Material Design を提供する側の視点で見ると、デザインの定義や比較的抽象度の低い UI 部品の提供など、必要な情報を提供できているように思えます。そして、これらの情報やリソースを有機的に組み合わせることにより、開発側は高い自由度が得られます。

しかし、これはあくまでも汎用の infrastructure であり、これをアプリ開発で直接的に利用するというのは危険な気がします。理由は、特定アプリ開発に利用するには自由度が高すぎるからです。それは、各エンジニアがそれぞれ微妙に異なるUIを作成してしまう可能性が高いことを意味します。

実際問題、アイテムの選択を AutoCompleteTextView の text に文字列を直接入れるとか、どんだけ bug prone なんだよという感じです。

ということで、Exposed dropdown menu でもその他のデザイン要素にしても、アプリ開発に適用する時には、以下のようなアプローチをとるのが良いと思われます。

  • アプリのデザインガイドラインを策定する。
  • デザインガイドラインに準じたカスタムコンポーネントを用意する。
  • アプリ開発はカスタムコンポーネントをベースに行う。

以上、おしまい。

※ たぶん後日、別エントリでカスタムコンポーネントの例を上げると思います。


  1. デザインそのものについては本家を読みましょう。^^ 

  2. google() は https://maven.google.com/web/index.html#com.google.android.material:material, jcenter() は https://bintray.com/bintray/jcenter で内容の確認ができます。 

  3. この例では Theme.MaterialComponents.Light.DarkActionBar を利用しましたが、こちら に書かれた他の選択肢も検討してみてください。 

  4. 執筆当時は android:inputType="none" では入力を防げませんでした。 

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

大学生が1週間でFlutterアプリを学んでリリースした過程(4日目)

こんにちはシオンです。

きてしまいましたか、これを書く時間が。。
今日、目が覚めて布団で寝返りを打った時からずっとこれを書く時間が来るのが憂鬱でした。
なぜデザインを終わらせなかったんだ、昨日の自分。
ツケはこのチャレンジが終わった時にしっかりと払ってもらうからな。(焼肉いきます。)

■目次

■今日こそデザインを決める
■いよいよプログラミング
■プログラミングを学ぶ
■まとめ

■今日こそデザインを決める

今日こそデザインを決めるというか、さっさと決めてコードを書き始めないと終わりません。
だってデザインが終わったらコードを書くための勉強をしてそれからコードを書かなきゃいけないんだから。

昨日の進捗はこれです。
1.png

全然進んでいませんね。
うんうん悩んでいてもあまり進まないことがわかったので、一旦3画面作ってみたいと思います。

ということで作ってみました。(すみません、記事書きながらやってるの忘れていました。。)
こちらです!
2.png

どうでしょうか?
なんとなく形にはなってますかね。画面増えてんじゃん!っていうツッコミはやめてください。

まあ赤点は回避できてるのではないかなと思います。
というか、何より時間がないのでこのまま行くしかないです。もしコード書きながら変更点出てきたら都度変えていきましょう。頑張れ明日の自分。

では、いよいよ目的だったFlutterに入っていきます!!
やっときました!(長かった。本当に。)
早速やっていきましょう!

■いよいよプログラミング

3.png

「Welcome to Android Studio」

ありがとうございます。やっっっときました。この画面に。どうぞ盛大に出迎えてください。

これが初期画面です。一度環境構築した時にここまではきていました。
4.png

では早速コードを書いていきましょう。
と言いたいところですが、コードを書くための知識が全くないため、何がなんだかわかりません。一から学んでいきましょう。

5.png

ということで参考書をキンドルで買いました。

これで一通り勉強していきたいと思います。

ただ、時間があまりにもなさすぎるので機能にしぼって学びます。
今回のアプリを作るために必要なコードとしては
・配置の仕方
・画面遷移
・テキスト入力
・画像表示
この辺を中心に学んでいきたいと思います。

■プログラミングを学ぶ

今更ながら、アプリ制作の工程に「プログラミングを学ぶ」という工程が組み込まれていることに驚きを隠せないですが。
マックでハンバーガー注文したら「ハンバーガーですね!わかりました、では作り方を学んできます!」と言われるみたいなことですかね。やばいですね。

とりあえず学んでいきます。
今回はFlutterの概要を学んで終わりにしたいと思います。
学びながらすごく簡単に言語化していくので、もしも間違っていたらすみません。その時は教えていただけると嬉しいです。

コードの書き方をみていきます。開発の背景とかはすっ飛ばします。

なるほど、どうやらFlutterでプログラミングをしていく上で大事になってくるのはwidget(ウィジェット)というものらしい。
widget自体はandroidスマホを使っている人は結構身近なものらしいですね。ホーム画面上に常に表示されるアプリのショートカット機能のことも、「ウィジェット」というらしいですね。

Flutterでのプログラミングでは、widgetとは「UI情報が入った箱」だと考えると良さそうです。この箱を自分の好みのデザインに積んでいくようなイメージ。
でこの箱には種類が2つあって、「中身の情報が変わらない箱」と「中身の情報が変化する箱」
前者をStatelessWidget、後者をStatefulWidgetという。この箱を画面の中で配置していくのが大体の流れになっているようですね。

だからこのwidgetの置き方・配置の仕方、2種類のWidgetの使い分けの仕方を学べばとりあえず画面を表示することはできそうです。
では、この参考書に従っておなじみのHelloWorldを書いて今日は終わりにしたいと思います。

このプログラミングの細かいところは飛ばしていきます。
ちなみに、やったこととしてはKaishoukunという一番大きな箱を用意。その中にStatelessWidgetをおく。そしてその真ん中にTextのUI情報を持った箱を用意。そのTextにHelloWorldと入力しました。

期限まで3日「HelloWorld」してから3日でアプリをリリースしたら割とすごいんじゃないか?と思ってワクワクしましたが、今のところ想像するに完成していない未来しか見えていないので、逆に絶望しています。
明日明後日の土日でなんとかしていきます。

6.png

明日もよろしくお願いいたします。

■まとめ

やっとプログラミングのスタートに立ちました。プログラミングの過程を公開するというテーマなのに、ここまで全くコードがなくてごめんなさい笑
ここからは学びながら、できるだけ早くコードを書いていきます。
リリースまで考えるとこの土日の2日間でコードを書き切る必要がありますが、果たしてどうなるのか。。

明日の自分がどこまで頑張るのか楽しみですね。頑張れ!

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

Androidで始める機械学習(TensorFlow Lite, ML Kit)

みなさんAndroidで機械学習やってますか?
Android 11 Meetups(GDG)に参加し、私はつい最近入門しました。

Android 11 Meetupsで学んだ内容と、自分で調べた内容を元に、Androidでの機械学習を使った機能を実装するにはどうすればいいか、まとめてみました。

クライアント側で機械学習

image.png

まずそもそもですが、図のようにサーバ側で推論や学習を行うか、クライアントで推論や学習1を行うかの違いで、それぞれ特徴があり、要件を考慮して決める必要があります。他にもいろいろあると思いますが、以下のようなことが考えられるでしょう。

  • クライアント
    • リアルタイムに処理したい
    • データをサーバにあげたくない
    • ネットワークに繋げられない
  • サーバ
    • マシンパワーが必要
    • 常に最新のモデルを使う必要がある
    • 学習済みモデルを守りたい

図はスマホ端末っぽいものしか記載されていませんが、もちろん車やIoT機器でも同様でしょう。
「エッジAI」だったり、「エッジ機械学習」、「オンデバイス」といったワードで耳にしたりします。

エッジで機械学習させるためのフレームワーク

エッジで機械学習する上で、どんな選択肢があるのかですが、主にTensorFlow Liteと、MLKitがあります。(ちまたにはPytorch mobileや、AppleだとCoreMLなどがあるらしい)

TensorFlow Lite

TensorFlow Liteは、モバイル端末上で推論するためのフレームワークです。
TensorFlowで作ったモデルをTensorFlow Liteのモデルに変換できます。
モデルの作り方は、後述する他の方法でも作成することができます。
Android, iOS, IoTデバイスで利用することができます。

利用するモデルによっては、推論時にGPUやTPUなどを利用できます。(MLKitだとできない)
既に学習済みのモデルもTensorFlow Hubで公開されています。

MLKit

ML Kitは、よくある機械学習のユースケースと学習済みモデルをラップして、使いやすくしたものです。
下はTensorFlowLiteが使われています。
こちらは、Android, iOSで利用可能です。

以前はML Kit for Firebaseとして、Firebaseのサービスの中にありましたが、6月にFirebaseから分離し、on-deivice向けに変更(ベータ版)されました。

https://developers.google.com/ml-kit/guides

ML Kit's on-device APIs are now available as a standalone SDK. Learn more about this change in our migration guide. Cloud APIs, AutoML Vision Edge, and custom model deployment are still available via Firebase Machine Learning.

FirebaseとMLKitは、以下のような住み分けになったようです。

  • Firebase ML
    • CloudAPI(text recognition, image labeling, landmark recognition)
    • Auto ML Vision Edge
    • manage custom model deployment
  • ML Kit
    • on-deivce API

現在、ML Kitで提供されているAPIは以下になります。

  • Barcode scanning
  • Face detection
  • Image labeling
  • Object detection and tracking
  • Text recognition 2
  • Language ID
  • On-device translation
  • Smart Reply

また、MLKitでは、利用するモデルはラップされていますが、image labeling、object detectionだけ、カスタムモデルを利用できるようです。

作ってみた

実際どうやねんということで、実機で試してみることにしました。

TensorFlow Lite

以下のAndroid11 MeetUpsの動画の通りに作成しました。
Build an Android app to recognize flowers(YouTube)

TensorFlow Lite Model Makerを利用して、花を分類するモデルを作成し、AndroidStudio4.1(プレビュー)のプラグインを利用して、AndroidアプリにTensorFlow Liteのモデルを組み込みました。

TensorFlow Lite Model Makerは、転移学習を用いて、TensorFlow Liteのモデルを作成できるライブラリです。
思ったよりも少ない画像3で、短時間でモデルを作成することができます。精度も結構良く、作りたいモデルによっては十分使えそうな印象でした。

image.png

ML Kit

ML KitはFace Detectionを試しました。
ガイドを見ればこれぐらいは簡単に実装できます。

CameraXとともに実装しましたが、取得した画像をMLKitに渡すだけです。顔の検出、トラッキングだけであれば驚くほど簡単に実装できます。むしろCameraXの実装(プレビューや検出した顔のOverlay)の方が時間がかかりました。

output.gif

複数の顔も同時に検出できたり、顔のランドマーク(目や口、鼻など)の検出もできるので、そちらも試して見ると面白そうです。

TensorFlow Lite or MLKit?

どちらを使うかですが、まずはMLKitを試して、要件を見たさないならTensorFlow Liteを検討するのが良さそうです。
TensorFlow Liteの方がより柔軟ですが、想定するユースケースを十分満たすのであれば、ML Kitの方が面倒を見ることが少ないです。

TensorFlow Liteのモデルを作るには?

3つ選択肢があります。後者はより蔵人向けかと。

  • AutoML Vision Edge
    • GUI操作(ノンプログラミング)で、ラベル付されたイメージのアップロードするだけで、モデルが作れる。
  • TensorFlow Lite Model Maker
    • 転移学習を用いて、on-device向けにモデルを作成できるライブラリ。
  • TensorFlowでモデルを作り、TensorFlow Liteに変換

まとめ

利用する用途によっては、手の込んだモデルを作ったり、自作するより、これで十分なんじゃないかという印象でした。
より難易度が高いことをするにしても、まずはプロトタイプとして利用するのにも有用だと思います。

参考


  1. クライアント側で学習することがあるのかですが、実際はマシンパワーなどが必要になったりするようで、ユースケースは限定的になるようです。 

  2. 現在英語のみ。日本語を対象にする場合は、CloudAPIを利用する必要があります。 

  3. このサンプルで用意されている画像は、1つのラベルで500枚を超えています。主観で思ったよりも少ないと思っただけです。 

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

【Android/Kotlin】検索UIの追加

やりたいこと

 MainActivityからアプリバーの検索ボタンを押すと、検索するためのアクティビティを開始し、検索用のUIが起動します。データのやり取りについては記事が長くなるので別で書こうと思います。
gif.gif

menuを作成

 検索機能を作るための一つ目の作業として、メニューの機能から、MainActivityに表示する検索ボタンを追加する作業を行います。resフォルダにあるmenuフォルダに、メニューリソースファイルを追加しましょう。そして、menuにMenuItemを追加します。MenuItemは様々なAttributeをいじることが出来ますが、今回はiconshowAsActionを設定します。iconは@android:drawable/ic_menu_searchを、showAsActionifRoomを選択しましょう。
 ここで、showAsActionについて軽く表にまとめてみました。細かいところですが、きちんと役割が違います。

説明
ifRoom スペースがある限り、アプリバーにアイテムをボタンとして格納。
withText alwaysやifRoomなどと一緒に使う。スマホを横向きにした時、アプリバーにアイコンとタイトルを表示させる。
never アプリバーではなく、オーバーフローメニュー内に表示
always アプリバーに常にアイテムを配置

xmlファイルは以下のようになりました。

<menu 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"
    tools:context="com.e.myapplication.MainActivity">
    <item
        android:id="@+id/action_search"
        android:icon="@android:drawable/ic_menu_search"
        android:title="action_search"
        app:showAsAction="ifRoom" />
</menu>

現時点でのアプリバーの様子。検索ボタンが右端にあるのが分かります。
キャプチャ.PNG

 次に、今作った検索ボタンが押されて、検索用のActivityに遷移した先で表示するアプリバーの設定を行っていきます。これも先ほどまでと同じような方法を取ります。まず、menuフォルダから新たなメニューリソースファイルを作りましょう。前回とは異なり、Search Itemをmenuにドラッグアンドドロップしてあげてください。その後は先ほどと同じように、showAsActionifRoomにするだけです。xmlは以下のようになります。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/app_bar_search"
        android:icon="@drawable/ic_search_black_24dp"
        android:title="activity_search"
        app:actionViewClass="android.widget.SearchView"
        app:showAsAction="ifRoom" />
</menu>

アプリバーは先ほどと同様の見た目をしているはずです。

検索用のActivityを作る。

 新たに検索用のアクティビティを作ります。これは、MainActivityでアプリバーの検索ボタンが押された時に遷移する先のアクティビティとなります。初めに、onCreateOptionsMenuメソッドをオーバーライドして二つ目に作ったメニューリソースファイルをインフレートしましょう。また、この次に設定する検索可能構成と検索可能アクティビティを設定した後に、SearchViewでそれらを使えるようにするための設定を行います。setOnQueryTextListenerについては別の記事で説明しようと思いますが、データのやり取りに使うリスナーです。以下のようにコードを書きます。

class SearchActivity : AppCompatActivity() {

    private var searchView: SearchView? = null
              ・
              ・
              ・
override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_search, menu)

        val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
        searchView = menu.findItem(R.id.app_bar_search).actionView as SearchView
        val searchableInfo = searchManager.getSearchableInfo(componentName)
        searchView?.setSearchableInfo(searchableInfo)

        searchView?.isIconified

        searchView?.setOnQueryTextListener(object: SearchView.OnQueryTextListener {
            override fun onQueryTextSubmit(query: String?): Boolean {
            finish()
            return true
        }

        override fun onQueryTextChange(newText: String?): Boolean {
            return false
        }
         })
        return true
}

検索可能性を構成する

 検索クエリをアクティビティに渡したり、検索候補を表示させたりするにはxml形式で検索構成を指定する必要があります。具体的には、resフォルダ直下にxml形式のフォルダを作り、その中でsearchableという名前のファイルを作ります。labelは必須のパラメータで、アプリやアクティビティの名前です。hintは検索する時に「キーワードを入力」などと薄文字で表示してくれる、よくあるやつです。具体的には以下のように記述します。この他にも、検索候補や音声検索をする時にこのファイルは役立ちます。

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:hint="単語を入力してください"
    android:label="SearchActivity"/>

マニフェストファイルの設定

 先ほどxml形式で指定した検索構成を適用するActivityに設定します。具体的には、以下のように該当するActivityにメタデータとインテントフィルターを挿入するだけです。下記の物をコピペするだけで大丈夫なはずです。

 <activity android:name=".SearchActivity">
            <intent-filter>
                <action android:name="android.intent.action.SEARCH"/>
            </intent-filter>
            <meta-data android:name="android.app.searchable"
                android:resource="@xml/searchable"/>
        </activity>

MainActivity

 最後にMainActivityにアプリバーの設定を追加するだけです。以下の二つのメソッドを使います。

override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_main, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.action_search -> {
                startActivity(Intent(this, SearchActivity::class.java))
                true
            }
            else -> super.onOptionsItemSelected(item)
        }
    }

これで、取り敢えず記事の一番上のgifのように動作すると思います。次に、検索に使った文字列をMainActivityに送るための処理を説明しようと思いますが、それは別の記事にまとめようと思います。

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