- 投稿日:2019-03-10T21:33:22+09:00
ML-Kit For Firebase でTensorFlow Lite使用サンプルを動かす
概要
ML-Kit For Firebase のTensorFlow Lite使用のサンプルを動かしたかったので
導入手順のメモを残しておきますML-Kit For Firebaseは以下のサンプルも試せますが
画像分類、文字認識、顔検出、バーコードスキャン、ランドマーク認識今回は上記以外にも、TensorFlow Lite形式をMobile端末に置き、使えるとのことで
このサンプルを動かします実行環境は以下のとおり
Windows10 Pro
Android Studio Version3.2.2
AndroidアプリVersion9.0サンプルアプリ動作手順
1.開発環境準備
Android Studio未導入の場合は以下URLからダウンロードしインストール
https://developer.android.com/studio/?hl=ja2.サンプルアプリを取得
ML-Kitサンプルアプリを用意
以下URLから「Clone or Download」をクリックし「quickstart-android.zip」をダウンロード
https://github.com/firebase/quickstart-android上記を解凍すると「quickstart-android-master」フォルダができるので、任意の場所に配置
Android Studioから「File>Open」で上記で配置した「quickstart-android-master」フォルダ配下の「mlkit」フォルダをプロジェクトとして選択
3.Firebaseプロジェクト登録
ML-Kitを使用するには「google-services.json」ファイルをプロジェクトに設定する必要があるため
以下のURLからFirebaseプロジェクトの登録を実施し、Jsonファイルを生成
https://console.firebase.google.com/
※Googleアカウント登録の必要あり以下でプロジェクトを生成
・プロジェクトの追加
以下情報を設定
プロジェクト名:任意の名称
アナリティクスの地域:日本
「次へ」ボタンを押下・AndroidアプリにFirebaseを追加
Androidパッケージ名に「com.google.firebase.samples.apps.mlkit」を設定し「アプリを登録」ボタンを押下
「google-services.json」ボタンを押下しJSONファイルをダウンロード上記で生成したJSON「google-services.json」をプロジェクトの「app」フォルダ配下に配置
4.サンプルアプリを実行
「Android Studio」から「Run」>「Run’app’」を押下しアプリを実行
※実機でも、バーチャルマシンでも実行可能(最小API levelが16との記載ありのため Android 4.4 KitKat以上であれば動作するはず?)アプリが立ち上がったら、コンボボックスから認識対象画像を選択し「FIND OBJECTS」ボタンを押下すると
画像のオブジェクトの種類と認識確度が高いもの上位3つが表示参考文献
https://codelabs.developers.google.com/codelabs/mlkit-android-custom-model/#0
- 投稿日:2019-03-10T19:30:13+09:00
Android KotlinとRxBinding2
はじめに
Jake Whartonが作成したRxBinding2というライブラリをご存知でしょうか。
Viewでのイベントに対して様々な条件などを付与しsubscribeすることでViewでのアクションを制御することができます。
RxBinding2をkotlinで使う場合、kotlin用に拡張されているライブラリを利用する必要があります。RxBinding2をkotlinで使う際の情報が少なかったので苦労しました...
基本的な利用方法を記載しているので、参考になればと思います。
- Kotlin 1.3.21
- Rxbinding-kotlin 2.2.0
RxBinding2の導入
kotlin拡張用のライブラリを導入する必要があるので、
-kotlin
を忘れないようにします。app/build.gradle// ... dependencies { // ... implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' implementation 'io.reactivex.rxjava2:rxjava:2.2.5' def rx_binding2_version = '2.2.0' implementation "com.jakewharton.rxbinding2:rxbinding-kotlin:$rx_binding2_version" implementation "com.jakewharton.rxbinding2:rxbinding-recyclerview-v7-kotlin:$rx_binding2_version" implementation "com.jakewharton.rxbinding2:rxbinding-appcompat-v7-kotlin:$rx_binding2_version" implementation "com.jakewharton.rxbinding2:rxbinding-support-v4-kotlin:$rx_binding2_version" implementation "com.jakewharton.rxbinding2:rxbinding-design-kotlin:$rx_binding2_version" implementation "com.jakewharton.rxbinding2:rxbinding-leanback-v17-kotlin:$rx_binding2_version" }利用方法
それぞれのライブラリの利用方法です。
一例を踏まえながら書き方を記載します。rxbinding-kotlin
ViewにあるButtonに対してclickした際の挙動はこのように書くことができます。
val binding = DataBindingUtil.setContentView<ActivityHogeBinding>(this, R.layout.activity_hoge) binding.demoButton .clicks() .filter { clickCount < 1 } .subscribe { clickCount++ Log.d("debug", "click!!") }rxbinding-recyclerview-v7-kotlin
ViewにあるRecyclerViewに対するアクションを制御できます。
RecyclerViewでスクロールが一番下までいったらロードする実装がこちらです。val binding = DataBindingUtil.setContentView<ActivityHogeBinding>(this, R.layout.activity_hoge) val gridLayoutManager = GridLayoutManager(this, 2) // 実装例1 binding.hogeRecyclerView .scrollEvents() .filter { gridLayoutManager.itemCount - 1 <= gridLayoutManager.findLastVisibleItemPosition() } .subscribe { loadMore(item) } // 実装例2 RxRecyclerView .scrollEvents(binding.companyRecyclerView) .filter { gridLayoutManager.itemCount - 1 <= gridLayoutManager.findLastVisibleItemPosition() } .subscribe { loadMore(item) }fun loadMore(item: Item) { if (this.itemList.last().id != item.id) { Log.d("debug", "loadMore") } }rxbinding-appcompat-v7-kotlin
AppCompat-v7 Libraryで導入できるViewに対するアクションを制御できます。
ToolBarのitemのclickだとこのようになります。// 実装例1 toolbar.itemClicks().subscribe{ Log.d("debug", "itemClicks") } // 実装例2 RxToolbar.itemClicks(toolbar).subscribe{ Log.d("debug", "itemClicks") }rxbinding-support-v4-kotlin
Support-v4 Libraryで導入できるViewに対するアクションを制御できます。
SwipeRefreshLayoutのrefreshだとこのようになります。// 実装例1 binding.hogeFragmentSwipeRefreshLayout .refreshes() .subscribe { Log.d("debug", "refreshes") } // 実装例2 RxSwipeRefreshLayout .refreshes(binding.hogeFragmentSwipeRefreshLayout) .subscribe { Log.d("debug", "refreshes") }rxbinding-design-kotlin
Design Support Libraryで導入できるViewに対するアクションを制御できます。
NavigationViewのitem選択時の挙動だとこのようになります。// 実装例1 binding.demoNavigationView .itemSelections() .subscribe { Log.d("debug", "itemSelections") } // 実装例2 RxNavigationView .itemSelections(binding.demoNavigationView) .subscribe { Log.d("debug", "itemSelections") }rxbinding-leanback-v17-kotlin
Leanback-v17 LibraryはAndroidTV向けのライブラリです。
今回は利用していないので、rxbinding-leanback-v17-kotlinについてはパスします。最後に
RxBinding2の基本的な使い方を書きました。
kotlinの拡張関数との組み合わせや、他のRx系のライブラリと組み合わせることで、
よりシンプルに書くこともできそうだと感じました。
リアクティブプログラミングはまだ全然慣れていないので、良い実装例があれば勉強したいので教えていただけると嬉しいです。
- 投稿日:2019-03-10T16:23:35+09:00
How to run specific instrumented test method coded by Espresso from command line
What's this?
This is the sample command to run specific Android Instrument test case coded by Espresso against Android app.
How?
Assume this the test code stored under
app/src/androidTest/java/com/hoget/testCases/
.sampletest.ktpackage com.hoget.instrumenttest.testcases.accountTest ...... @RunWith(AndroidJUnit4::class) class HogetAccountTest { @Test fun LoginLogoutTest() { // test code in here } }To run
LoginLogoutTest()
method$ ./gradlew connectedAndroidTest -P android.testInstrumentationRunnerArguments.class=com.hoget.instrumenttest.testcases.accountTest.HogetAccountTest#LoginLogoutTest;So, the template for the command is like this:
$ ./gradlew connectedAndroidTest -P android.testInstrumentationRunnerArguments.class=<App's Test Package Name>.<Test Class Name>#<TestMethodName>;
- 投稿日:2019-03-10T16:16:19+09:00
Android 非同期でのUIスレッド処理について
- 投稿日:2019-03-10T16:03:46+09:00
AndroidでJUnitのコードカバレッジを取得する。
設定
AndroidStudioでカバレッジ取得について、Jacocoがサポートされてるのでそれをつかう。
やることは設定を一つgradleに加えるだけ、
buildTypes { debug { testCoverageEnabled true } }これで準備はOK!
コードカバレッジを取得する
いろいろみてみたら取得するのにgitコマンドをつかうらしい、めんどくさいので別の方法で取得する。
AndroidStudio右上の「Gradle」をクリックするをこんな感じに表示される。
上記の設定をしていると「createDebugCoverageReport」が追加されてるはず、
それをダブルクリックでカバレッジが取得できる。(※instrumentedTestの結果)
この際、エミュレータを起動しておきましょう。
そうすると、「/app/build/outputs/reports/」にカバレッジができてるはず!
カバレッジの見方についてはめんどくさいので割愛。おわり
- 投稿日:2019-03-10T15:27:53+09:00
appiumとCodeceptJS(node.js)を使ってAndroid & iOSのE2Eテストの実行環境構築
なぜこの文章を書いたか
前回のQiitaで「Vagrant + Selenium + node.js(CodeceptJS)でIE, Chrome, FirefoxのマルチブラウザE2Eテスト」について書いたのですが「どうせならスマホのE2Eテストも出来るようになりたい」と考え追加ポスト。
環境
OS : macOS Mojave (10.14.3)
VirtualBox : 6.0.4
Vagrant : v2.2.3
node.js : 10.15.1 (ndenvでインストール済み)
Android Studio, Xcode などはインストール済みインストール
appium, appium-doctor のインストール
$ npm install -g appium $ npm install -g appium-doctor $ ndenv rehashappium-doctor の実行
まず実行して自分の環境に何が足りないか、設定が間違っていないかなどを確認
$ appium-doctor --ios --android足りないものをインストール
色々と足りないのでインストール & 設定
carthage のインストール
$ brew install carthageopencv4nodejs のインストール
$ brew install opencv@3 $ brew link opencv@3 --force $ vi ~/.bash_profile.bash_profileに以下を追加
export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/Cellar/openssl/1.0.2q/lib/pkgconfig/" export PATH="/usr/local/opt/opencv@3/bin:$PATH" export LDFLAGS="$LDFLAGS -L/usr/local/opt/opencv@3/lib" export CPPFLAGS="$CPPFLAGS -I/usr/local/opt/opencv@3/include"$ exec $SHELL -l $ OPENCV4NODEJS_DISABLE_AUTOBUILD=1 npm install -g opencv4nodejsffmpeg のインストール
$ brew install ffmpegfbsimctl のインストール
$ brew tap facebook/fb $ brew install fbsimctl --HEADapplesimutils のインストール
$ brew tap wix/brew $ brew install applesimutils --HEADidevicelocation のインストール
$ brew install usbmuxd libplist libimobiledevice libzip openssl make automake autoconf libtool pkg-config $ brew list openssl (省略) /usr/local/Cellar/openssl/1.0.2q/lib/pkgconfig/ (3 files) (省略) $ vi ~/.bash_profile.bash_profileに以下を追加
export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/openssl/lib/pkgconfig/"$ exec $SHELL -l $ git clone https://github.com/JonGabilondoAngulo/idevicelocation.git $ cd idevicelocation $ ./autogen.sh $ make $ sudo make installios-deploy のインストール
$ npm install -g ios-deployios-webkit-debug-proxy のインストール
$ brew install ios-webkit-debug-proxyJAVA_HOMEの設定
$ vi ~/.bash_profile
.bash_profileに以下を追加
export JAVA_HOME=`/usr/libexec/java_home -v 10` export PATH="$PATH:$JAVA_HOME/bin/"$ exec $SHELL -lbundletool.jar のインストール
$ mkdir ~/bin/ $ cd ~/bin/ $ wget https://github.com/google/bundletool/releases/download/0.8.0/bundletool-all-0.8.0.jar $ ln -s bundletool-all-0.8.0.jar bundletool.jar $ chmod a+x bundletool-all-0.8.0.jar $ vi ~/.bash_profile.bash_profileに以下を追加
export PATH="$PATH:$HOME/bin/"$ exec $SHELL -l再度 appium-doctor の実行
$ appium-doctor --ios --android (前略) info AppiumDoctor info AppiumDoctor Everything looks good, bye! info AppiumDoctor
Everything looks good, bye!
と出ていれば問題無しChromeDriver のインストール
AndroidのChromeを動作させるためにChromeDriverをインストールする
Android 9.0のChromeのバージョンに合わせて、ここではChromeDriverのバージョンをv2.44としています詳細:
https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/web/chromedriver.md
http://chromedriver.chromium.org/downloads$ wget https://chromedriver.storage.googleapis.com/2.44/chromedriver_mac64.zip $ unzip chromedriver_mac64.zip $ mv chromedriver ~/bin/chromedriver_v2_44npmを使ってテスト環境の構築
普通にディレクトリを作ってテスト環境を構築する
npm init 他
$ mkdir sptest $ cd sptest $ npm init -y $ npm install codeceptjs --save-devcodecept.jsの初期化
$ npx codeceptjs init Welcome to CodeceptJS initialization tool It will prepare and configure a test environment for you Installing to /Users/xxxx/sptest ? Where are your tests located? ./*_test.js ? What helpers do you want to use? Appium ? Where should logs, screenshots, and reports to be stored? ./output ? Would you like to extend I object with custom steps? Yes ? Do you want to choose localization for tests? ja-JP ? Where would you like to place custom steps? ./steps_file.js Configure helpers... ? [Appium] Application package. Path to file or url http://localhost ? [Appium] Mobile Platform iOS ? [Appium] Device to run tests on emulator Steps file created at /Users/xxxx/sptest/steps_file.js Config created at /Users/xxxx/sptest/codecept.conf.js Directory for temporary output files created at `_output` Almost done! Create your first test by executing `codeceptjs gt` (generate test) command -- Please install dependent packages locally: npm install --save-dev webdriverio@^5.2.2 $ npm install --save-dev webdriverio@^5.2.2実際のテスト
appiumの起動
$ appium --chromedriver-executable ~/bin/chromedriver_v2_44テストの記述
前回と同じ、github.comに行って、"GitHub"という文字列があるかどうかをチェックするだけのコードを書きます。
github_test.js
Feature('Github'); Scenario('test something', (I) => { I.amOnPage('https://github.com'); I.see('GitHub'); });iOS用の設定ファイルを作成
自動で作成されてますが、それを以下のようにいじります。
codecept.ios.conf.js
exports.config = { tests: './*_test.js', output: './output', helpers: { Appium: { platform: "IOS", desiredCapabilities: { "platformName": "iOS", "platformVersion": "12.1", "deviceName": "iPhone 7", "automationName": "XCUITest", "browserName": "Safari" } }, }, include: { I: './steps_file.js' }, bootstrap: null, mocha: {}, name: 'test', translation: 'ja-JP' }Android用の設定ファイルを作成
同様にAndroid用も
exports.config = { tests: './*_test.js', output: './output', helpers: { Appium: { platform: "Android", desiredCapabilities: { automationName: "Appium", deviceName: "Nexus 5x API 28 for appium", platformVersion: "9", browserName: "Chrome" } }, }, include: { I: './steps_file.js' }, bootstrap: null, mocha: {}, name: 'test', translation: 'ja-JP' }テスト実行
iOSにしろAndroidにしろ、問題があればappiumを起動しているターミナルに何かしらエラーが出ているので解読して下さい
iOS
以下のコマンドを実行すると、初回時に必要な設定を自動でしてくれた上でSafariが起動してテストが実行されます
$ npx codeceptjs run --steps --config=./codecept.ios.conf.jsAndroid
以下のコマンド実行の前にAndroid StudioからAndroidエミュレータを立ち上げておきます
Name : Nexus 5x API 28 for appium
OS : Pi (Android 9.0)$ npx codeceptjs run --steps --config=./codecept.android.conf.jsnpm runで実行できるように package.json を修正
package.json
{ "name": "sptest", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "npm run test:ios; npm run test:android", "test:ios": "codeceptjs run --steps --config=./codecept.ios.conf.js", "test:android": "codeceptjs run --steps --config=./codecept.android.conf.js" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "codeceptjs": "^2.0.7", "webdriverio": "^5.7.1" } }iOS
$ npm run test:iosAndroid
$ npm run test:androidiOSとAndroidの逐次実行
$ npm run test終わりに
これで前回のSelleniumと合わせて、ブラウザテストが一通り出来るようになったので、これを使ってガリガリE2Eテスト書いていきます
- 投稿日:2019-03-10T15:01:08+09:00
React NativeでURL をパースする
経緯
React Naviveを使ったアプリで、Deep LinkやPush通知のペイロードに格納されたURLを解析したいのですが、有効な方法が見つからないのでiOS/Androidデバイスのライブラリを使いそれらをパースするコードを実装しました。
覚えているうちに記事にします。
もっと簡単な方法があれば誰か教えて下さい・・・(非同期は何気に扱いにくいし)方針
iOS, AndroidともにURLを解析するAPIがあるので単純にそれらを呼び出します。
ネイティブコードを呼び出すので、この機能の戻り値はPromiseにしています(callbackよりは使いやすいと思うので)。
APIはクエリーパラメータについては、&で結合された文字列だけが戻ってくるので、JSでテキスト処理しています。
例外処理やエラー処理は適当です(参考実装ということで)。完全なコード
こちらにあります。
https://github.com/flipfrog/react-native-url-parseAPI
こんな感じで使用します。
import URLParse from './URLParse'; : async componentDidMount(): void { const url = await URLParse.parse(this.state.urlSpec); this.setState({ protocol: url.protocol, // httpsとかのプロトコルスキーマ host: url.host, // ホスト名 port: url.port, // ポート番号 path: url.path, // パス query: url.query, // クエリーパラメータ ref: url.ref, // インデックス queryMap: url.queryMap, // クエリーパラメータのマップオブジェクト }); }URLParse.jsimport {NativeModules} from 'react-native'; export default class URLParse { static async parse(urlSpec: string) { const url = await NativeModules.RNURLParseModule.parse(urlSpec); if (url && url.query) { const expressions = url.query.split('&'); const queryMap = {}; for (let expression of expressions) { const values = expression.split('='); queryMap[values[0]] = (values[1] ? values[1] : null); } url['queryMap'] = queryMap; } return url; } }iOS
iOSは、.hと.mファイル(Objective-Cを使いました)を作成するだけです。
RNURLParseModule.h#if __has_include(<React/RCTBridgeModule.h>) #import <React/RCTBridgeModule.h> #else #import "RCTBridgeModule.h" #endif @interface RNURLParseModule : NSObject <RCTBridgeModule> @endRNURLParseModule.m#import "RNURLParseModule.h" #if __has_include("RCTUtils.h") #import "RCTUtils.h" #else #import <React/RCTUtils.h> #endif #import <Foundation/Foundation.h> @implementation RNURLParseModule { } RCT_EXPORT_MODULE(); RCT_EXPORT_METHOD(parse:(NSString *)urlSpec parseWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { NSURL *url = [NSURL URLWithString:urlSpec]; if (url == NULL) { NSDictionary *errorDic = @{ NSLocalizedDescriptionKey:@"Parse error", NSLocalizedRecoverySuggestionErrorKey:@"Confirm parameter urlSpec." }; NSError *error = [[NSError alloc] initWithDomain:@"org.reactjs.native.example.URLParseSample.parse" code:-1 userInfo:errorDic]; reject(@"Parse error", @"Parse error", error); } else { NSDictionary *info = @{ @"protocol": [url scheme], @"host": [url host], @"port": [url port], @"path": [url path], @"query": [url query], @"ref": [url fragment] }; resolve(info); } } @endAndroid
Androidは、モジュールとパッケージ定義を作成して、MainApplication.javaでインスタンス化したパッケージを返すようにします。
RNURLParseModule,javapackage com.urlparsesample.extension; import java.net.URL; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.WritableMap; import java.util.HashMap; import java.util.Map; // package private class class RNURLParseModule extends ReactContextBaseJavaModule { private static final String E_URL_PARSE_ERROR = "URL Parse error"; RNURLParseModule(ReactApplicationContext context) { super(context); } @Override public String getName() { return "RNURLParseModule"; } @Override public Map<String, Object> getConstants() { final Map<String, Object> constants = new HashMap<>(); constants.put("IsAndroid", true); return constants; } @ReactMethod public void parse(final String urlSpec, Promise promise) { WritableMap info = Arguments.createMap(); try { URL url = new URL(urlSpec); info.putString("protocol", url.getProtocol()); info.putString("host", url.getHost()); info.putString("path", url.getPath()); info.putInt("port", url.getPort()); info.putString("query", url.getQuery()); info.putString("ref", url.getRef()); promise.resolve(info); } catch (Exception e) { promise.reject(E_URL_PARSE_ERROR, e); } } }RNURLParsePackage.javapackage com.urlparsesample.extension; import java.util.ArrayList; import java.util.Collections; import java.util.List; import com.facebook.react.ReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; public class RNURLParsePackage implements ReactPackage { @Override public List<NativeModule> createNativeModules (ReactApplicationContext context) { List<NativeModule> modules = new ArrayList<>(); modules.add(new RNURLParseModule(context)); return modules; } // Deprecated RN 0.47 // @Override public List<Class<? extends JavaScriptModule>> createJSModules() { return Collections.emptyList(); } @Override public List<ViewManager> createViewManagers(ReactApplicationContext context) { return Collections.emptyList(); } }MainApplication.java(抜粋)@Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new RNURLParsePackage() // ここに追加します ); }実行結果
先に記載したGitHubのコードを実行すると下記のように結果を表示します。
- 投稿日:2019-03-10T15:00:12+09:00
TextInputLayout - password visibility toggleのデザイン変更
はじめに
Androidのマテリアルデザインに沿った入力フィールドを作成するのに、Android Design Support Libraryの
TextInputLayout
を使うことが多いかと思います。これにはパスワードの表示/非表示を切り替える機能( password visibility toggle )が備わっているのですが、デザインを変更する際に少しハマったのでメモします。実装
変更箇所
パスワードの表示/非表示を切り替えるボタン( password visibility toggle button )のアイコンと色をそれぞれ切り替えます。
失敗例
ic_password_toggle.xml<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_checked="false"> <bitmap android:src="@drawable/ic_lock" android:tint="#9e9e9e" /> </item> <item android:state_checked="true"> <bitmap android:src="@drawable/ic_lock_open" android:tint="@color/colorAccent" /> </item> </selector>activity_main.xml<android.support.design.widget.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content" app:hintAnimationEnabled="true" app:hintEnabled="true" app:passwordToggleDrawable="@drawable/ic_password_toggle" app:passwordToggleEnabled="true"> <android.support.design.widget.TextInputEditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/hint_password" android:inputType="textPassword" /> </android.support.design.widget.TextInputLayout>パッと見これで問題なさそうですが、
passwordToggleTint
を指定していないので、デフォルトで塗りつぶされてしまい、色が切り替わりません。成功例
ic_password_toggle.xml<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/ic_lock" android:state_checked="false" /> <item android:drawable="@drawable/ic_lock_open" android:state_checked="true" /> </selector>tint_password_toggle.xml<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="#9e9e9e" android:state_checked="false" /> <item android:color="@color/colorAccent" android:state_checked="true" /> </selector>activity_main.xml<android.support.design.widget.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content" app:hintAnimationEnabled="true" app:hintEnabled="true" app:passwordToggleDrawable="@drawable/ic_password_toggle" app:passwordToggleEnabled="true" app:passwordToggleTint="@color/tint_password_toggle"> <android.support.design.widget.TextInputEditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/hint_password" android:inputType="textPassword" /> </android.support.design.widget.TextInputLayout>
passwordToggleDrawable
とpasswordToggleTint
に分けてそれぞれ設定するのがミソです。まとめ
公式のリファレンスはちゃんとに読みましょう(戒め)。一応 GitHub にサンプルをアップしておきます。
参考文献
- 投稿日:2019-03-10T13:57:44+09:00
androidでsecret tokenをrepositoryに入れない
これ cleanしたら、gradle.properties 消える? じゃあだめだこれ
gradle.properties
をgitignoreしたうえで、参照する。// build.gradle android { defaultConfig { resValue "string", "google_maps_key", (project.findProperty("GOOGLE_MAPS_API_KEY") ?: "") }// gradle.properties GOOGLE_MAPS_API_KEY=AIzaYOURAPIKEYあとは普通にこういうやつで参照できる。
<meta-data android:name="com.google.android.geo.API_KEY" android:value="@string/google_maps_key" />references
Hiding API keys from your Android repository – Code Better – Medium https://medium.com/code-better/hiding-api-keys-from-your-android-repository-b23f5598b906
https://stackoverflow.com/a/51582501/104080
- 投稿日:2019-03-10T12:21:13+09:00
osmdroid を使ってAndroid に国土地理院の地図画像を表示する
osmdroid を使って OpenStreetMap の地図を Android に表示する
の続きです国土地理院の地図画像
国土地理院は、測量及び地理情報を扱う行政機関です。
以前は、5万分の1地形図のような紙媒体で公開していた。
最近は、電子データ化を推進している。地理院タイルという名称で地図画像をXYZ方式で配信している。
地理院タイル一覧
https://maps.gsi.go.jp/development/ichiran.html利用条件
国土地理院のサーバ上にある地理院タイルを、リアルタイムで読み込み表示するウェブサイトやソフトウェア、アプリケーションを製作する場合、地理院タイルは出典の明示のみで申請不要でご利用いただけます。ということなので、ありがたく利用されて頂く。
osmdroidのXYTileSource
XYZ方式で提供されているタイルを使用するためのクラス
前回の記事で、OSM日本のサーバーを指定しいたところを、
地理院タイルのサーバーを指定する// OSM日本 final ITileSource tileSource = new XYTileSource("FietsRegionaal", 3, 18, 256, ".png", new String[] { "http://tile.openstreetmap.jp/" }); // 地理院タイル final ITileSource tileSource = new XYTileSource("GSI", 3, 18, 256, ".png", new String[] { "https://cyberjapandata.gsi.go.jp/xyz/std/" });スクリーンショット
github にサンプルコードを公開した
https://github.com/ohwada/Android_Samples/tree/master/Osmdroid10
- 投稿日:2019-03-10T12:03:05+09:00
AndroidStudioでinstall_failed_verification_failureというエラーが出た時
原因
どうやら、セキュリティ系のソフト(GooglePlayストアのPlayプロテクトやその他のアンチウイルス系ソフトなど)が原因の場合が多いよう。
対処
スキャンなどをオフにする。僕の場合はPlayプロテクトが原因だったので、その対処方法。
まずGooglePlayストアアプリを開いて、メニューを出し、Playプロテクトという所を開く。
そして「端末をスキャンしてセキュリティ上の脅威を確認」という項目をOFFにする。これで僕の場合は治りました。注意
もちろん安全性を犠牲にすることになるので、開発時以外はOnに戻すのを忘れないように。
それか、いっそ開発用のデバイスが欲しいなと、僕は思いました。
- 投稿日:2019-03-10T08:56:13+09:00
公開したアプリがポリシー違反で非承認となった件
昨日アップロードしたアプリ
視力回復文学:はつ恋
が「非承認」となりました。公開ステータス: 否承認
審査の結果、お客様のアプリは、ポリシーに違反していると判断されたため、否承認となり公開されませんでした。送信したのがアップデートの場合は、引き続き以前のバージョンのアプリが Google Play に掲載されます。問題: メタデータに関するポリシーへの違反
誤解を招くメタデータ、無関係なメタデータ、過度のメタデータ、不適切なメタデータをアプリに設定することは認められておりません。メタデータには、アプリの説明、デベロッパー名、タイトル、アイコン、スクリーンショット、プロモーション画像などが含まれます(ただしこれらに限定されません)。
ストアの掲載情報に表示されるコンテンツは、(ログインしているか、していないかに関わらず)全ユーザーに表示されるため、すべてのユーザーに適したものである必要がございます。不適切なテキストではあるものの、それがアプリの説明に不可欠な場合は、ストアの掲載情報内ではアスタリスクなどを使用して伏せ字にしてください。画像につきましては、一般ユーザーに適したものに差し替えていただくか、ストアの掲載情報から削除していただきますようお願いいたします。お客様のストア掲載情報のコンテンツ(説明、タイトル、アイコン、スクリーンショット、動画、プロモーション画像など)は、以下の問題に該当しております。
メタデータやロゴに不適切なテキストが含まれている(冒涜的、下品、攻撃的な表現など)
対処方法: アプリを送信して再審査を受ける
1.メタデータに関するポリシーを確認し、アプリを修正(アプリの説明、タイトル、アイコン、スクリーンショット、プロモーション画像がすべてのユーザーに適したものになるよう変更)。
2.アプリが他のすべてのデベロッパー プログラム ポリシーに準拠していることを確認(再度ポリシーに違反した場合、追加の措置を取らせていただくことがございます。
3.Play Console にログインして、アプリのアップデートを送信。ポリシーをご確認のうえ、今回の決定が誤りだと思われる場合は、お手数ですが弊社のポリシー サポートチームにお問い合わせください。2 営業日以内にご連絡いたします。
アプリの説明文に問題があるということなので、修正し、再審査の依頼を行いました。
このメールを受け取った後でもGoogle Play Console では「公開中」のままです。このメールを受け取った後でも「非公開」になっていないようです。
何が原因なのか?まずは、掲載していたアプリの説明文です。
3DVR等のように平行法を用いて文学を読むことでストレスの溜まった目をストレッチするためのアプリです。はつ恋(ツルゲーネフ作・神西清訳:青空文庫より)
・画面の見方
まずは3m程度先を見て、すぐにスマホの画面を見るようにします。そして左右に表示された文字が一つに重なって見えるようスマホの距離を調整しましょう。文字が重なったらゆっくりとスマホを離して、はっきりと文字が見えつつできるだけ離した状態にしてください。あとは読むだけです。最初は時間がかかるかもしれませんが慣れるとすぐにできるようになります。・ページの移動
画面の何も表示されていないところをスワイプ(フリック)するとページ送り/戻しができます。
中央に表示されているゲージをドラッグすると大きく移動することができます。ポイント)
画面を平行法で見ることで寄り目でなくなり、スマホなど近距離を長時間見て負荷のかかった目をストレッチする効果が期待できます。ご注意)
視力回復文学となっておりますが視力が回復することを保障するものではありません。
長時間のご使用は避けてください。長時間見続けると目を痛めたり、気分が悪くなったり、頭痛、吐き気などを催す可能性があります。少しでも違和感を感じた時は使用することをおやめください。ご注意)
このアプリを電車内で使用することは避けてください。電車に座った状態でこのアプリを使用しますとあなたはアプリを眺めていても周りから見ると目線が少し先のほうを見ているように見えますので、向かいに座った人から「あの向かいの席に座っている紳士はァッッッ!スマホを眺めているフリをしてずっと私の事を見ているゥッッッ!まるで19世紀のロシア文学に酔いしれているような目つきでェェェェッッ!これはっ!紛れもなくッ!セクシュアルゥハラスゥメントゥゥゥッ!」と誤解されてしまうかもしれません。ご注意ください。解説)
この解説はアプリ開発者個人の意見であり、なんの医学的根拠もない妄想と理解して読んでください。
スマホや読書などでずっと近くを見続けていると疲れ目となりますが、これは長い間近くにピントを合わせているから起こるというより、近くを見ることで寄り目の状態が続くことにより発生すると私は考えています。本を読むために作成した近距離用のメガネで本を読んでも同様に疲れ目になるという私の経験から辿り着いた考察です。
東アジア人は他の地域に比べて近視の割合が多いとされていますが、その理由の一つとして目が細いことが挙げられるのではないでしょうか。寄り目になると角膜の上部と下部がまぶたに触れるため角膜を押さえる力が加わります。それが長時間続くことで角膜表面が歪むので一時的な乱視の状態(画像がぶれて見える)となるのではないかと私は考えております。
この一時的な乱視の状態でもはっきりとブレないように見る方法があります。それが「さらに近づけて見る」ことです。そうです、どんどん、どんどん、スマホや本の距離が近づいていきます。そもそも、人間の目にそんな近くを見る機能はないのですが人間の目は最後の手段、眼軸の延長を行います。眼軸を延長することでより近くを見ることができるようになります。そしてその代償として遠くを見ることができなくなります。そして軸性近視と呼ばれる「絶対に治らない近視」になってしまいます。
現在、伸びてしまった眼軸を短くする方法は見つかっていません。宇宙飛行士は宇宙空間に滞在すると眼軸が伸びるという話がある程度です。視力回復とまでは言いませんが、ご自分の目が今以上に悪くならないよう目に対する興味、知識(この解説は妄想やデタラメばかりなのでご自分で調べてみてください)を高めていただくことのきっかけとなれば、こんなクソアプリの説明を読んだことも時間の無駄にはならないかなと思います。
ということで、視力回復文学をよろしくお願いいたします。どうでしょう?どこが問題になったのでしょうか?
私の予想では、おそらく最後の締めに出てくる「クソアプリ」という単語が引っかかったのではないかと考えています。セクシャルハラスメントの下りも「下品」であるとされたのかもしれません。ご注意から解説までごっそり削って、再認証の依頼をしておきました。3回目の非承認です。
1回目はよみあげ!の説明文に「初音ミクの声で」と書いていたことが「商標」を記入しているということで非承認になりました。その後公開されました。
2回目は頭痛アプリ
Healing sound app for headache(パッケージ ID )は、審査の結果、虚偽の振る舞いに関するポリシーに違反しているため、Google Play での公開が停止され、削除されました。
(※パッケージ IDは引用時に削除しました)
まぁ、これは仕方ないかな。実際、時々(笑)、治ることもあるんだけども。これはそのまま再審査の請求もしなかった。で、今回が通算3回目。
昨日、非承認を受けてふと思ったんだけど。他人から見ると私は随分とふざけた人間なのだろうな。いたるところでそういう部分がふと滲み出てしまって周りの人を不愉快にするのだと思う。人生が上手くいかないのもそういうところなんだろうなと思う。自分では結構真面目なつもりなんだけれど。
monochrome:目に優しいモノトーンのブラウザ
の説明文はとてもふざけているけどなんとか常識の範囲内に収まっているのだろうな。自分ではその境界線がわからないのだ。そうだな。思い起こせば私の人生はそういうことが多くあったな。まぁ、いいけど。
- 投稿日:2019-03-10T00:46:09+09:00
Fragment と Toolbar の歴史の話
この辺りは Android 初学者を混乱させる存在なのだけれど、人に説明していて結構骨が折れた&いい資料があるといいなと思ったので。
事実誤認とかあるかもです。鵜呑みにしないでね。
TL;DR
- AndroidX を入れる
AppCompatActivity
を継承してActivity
を実装するTheme.AppCompat.NoActionBar
やTheme.MaterialComponents.NoActionBar
など、NoActionBar
系のテーマを、アプリケーションのテーマの parent に指定する- Layout XML 上で、
androidx.appcompat.widget.Toolbar
を配置するsetSupportActionBar()
でToolbar
を ActionBar として設定するAndroid にはこのような お作法 があり、新しくプロジェクトを作ると勝手にやってくれます。
この辺について疑問に思った人向けの歴史的経緯の説明です。
Android 2.X 時代
この時代にはまだ
Fragment
はなく、ひとつの画面を作るのにひとつのActivity
を定義していました。タブのように複数の画面をネストするような画面を実現するには、複数の
Activity
を管理するActivityGroup
というものを使う必要がありました。「
TabActivity
なんてものもあったよね」とぼんやり記憶してる人がいるくらいだと思いますが……。タイトルバー
Android が iOS と比べて興味深かった点は、
Activity
をエントリポイントとして、複数のアプリケーションを跨った協調動作ができた点で、このためかActivity
は自身を表すラベルをタイトルバーに表示していました。もっとも、「見た目が悪い」「iOS とデザインを合わせたい」などの理由により、真っ先に以下のようなコードで抹消される運命でした。
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().requestFeature(Window.FEATURE_NO_TILE); // Activity#requestWindowFeature() という糖衣記法もある setContentView(R.layout.main); }現在では
style.xml
でベーステーマにNoActionBar
やNoTitleBar
を指定するか、または自分でアプリテーマに以下の記述をするため、<item name="android:windowNoTitle">true</item>コードから制御することは稀だと思いますが、同じ処理が行われます。
ここでひとつのポイントなのが、タイトルバーというものは Window の機能であった という点です。
昔はたくさんあった物理ボタン
Android 10 では back button すら消えるという噂を耳にしたのですが、昔は Back / Menu / Home / Search の4種類のボタンがありました。
Search ボタンが一級市民というところに、検索事業から始まった Google の趣向を感じ取っていたのですが、いつの間にか消えてました。
いまから Android 開発に触れる場合、「
ToolBar
に表示させるMenuItem
をActivity#onCreateOptionsMenu()
で作成するのはなぜ?」 という素朴な疑問を抱くと思うのですが、オプションメニューは物理ボタンを押すと表示されるものだった というところを踏まえると、納得がいくと思います。Android 3.X 時代
いまは Android のバージョンは単に大きい方が新しいだけでしたが、2.X は Phone 端末向け、3.X は Tablet 端末向けという意味合いがありました。
ActionBar の登場
Android 4.x時代のアプリにないと残念なActionBarとは という記事に ActionBar の解説が画像入りであります。
この記事にもあるように、ActionBar とは 2.X のタイトルバーが進化したものです。具体的にどう強化されたのかというと、
ロゴアイコンが表示される
デフォルトのタイトルバーは
Activity
のラベルしか表示されないため、どのアプリのActivity
なのかはわかりませんでした。ActionBar はロゴアイコンを表示することができるようになりました。この時代、ActionBar の色の変更を手軽に行うことはできず、かわりに ロゴアイコンを表示させることで、アプリの個性を表現することが推奨 されていました。
Android 5.0 からは色を使ってブランディングするという方向にシフトしたため、現在の Toolbar はロゴを表示していません。
メニューアイテムを表示できる
2.X では物理メニューボタンを使って、オプションメニューを表示させていました。換言すれば、オプションメニューが用意されているのかどうかは、メニューボタンを押すまで分からない ということです。
常時メニューが表示されるようになり、メニューに機能を持たせやすくなりました。
その他の機能
- タブ・ドロップダウンメニューを表示する機能(
setNavigationMode()
)- ActionBar を上下に分割する機能(Split Action Bar)
などが存在しました。この辺は闇に葬られたので知らなくても仔細ありません。
長らく苦しめられた ActionBar の互換性の問題
Android 2.X ではタイトルバーを消すことが多かったのですが、ActionBar はタイトルバーが進化したもののため、
requestFeature(Window.FEATURE_NO_TILE)
を行うと ActionBar も消えてしまう という問題がありました。特にこの問題は、Phone 端末でも ActionBar を表示するようになった 4.0 以降に「Android らしいアプリを作りたい」という Androider を大いに悩ませる問題でした。
- 2.X ではタイトルバー / 4.X では ActionBar を表示する
- ActionBar モドキの View を自作する
- ActionBarSherlock という非公式のバックポートライブラリを使う
- 諦める
辺りが主力な選択肢だったような気がします。
冒頭の記事が、「Android 4.x時代のアプリにないと残念なActionBar」 と書いていたのは、諦めを選択して ActionBar を非表示にしていたアプリが少なくなかった からです。
のちに公式で
ActionBarActivty
が登場して互換性の問題は解決するのですが、2〜3 年待たされたように思います。Fragment の登場
Tablet 端末と Phone 端末とでは、ディスプレイサイズの違いから、表示できる情報量に違いがあるため、
Activity
のユーザーインターフェースを複数のモジュールに 断片化 させることで、両対応を行おうという発想で登場したのがFragment
です。公式のデベロッパーガイド の以下の画像が分かりやすいと思います。
実際のところ、この試みはうまくいかなかったように思いますがどうなんでしょう?
Fragment の断片化
バックポートライブラリの登場が遅かった ActionBar とは異なり、
Fragment
は比較的早くに Support Package 版が登場しました。現在、Fragment のクラスは以下の 3 つが存在しています。
android.app.Fragment
(もはや使うべきではない)android.support.v4.app.Fragment
(AndroidX に移行するべき)androidx.fragment.app.Fragment
Android SDK に存在する本来の
Fragment
を Fragment(真)、Support Library や AndroidX のFragment
を Fragment(偽)と個人的に呼んでいるのですが、これらは異なるクラスなので、
- Fragment(真)を管理する
FragmentManager
はActivity#getFragmentManager()
で取得する- Fragment(偽)を管理する
FragmentManager
はActivity#getSupportFragmentManager()
で取得するというような違いがあったりして、この辺も初学者を的確に躓かせるトラップして秀逸だったと思います。
android.support.v4.app.Fragment
とandroidx.fragment.app.Fragment
も別物なので、本来は混在させることができない のですが、Jetifier というツールがコンパイル時に置き換えるという魔法的手段を使って解決してます。Fragment を使うのは避けた方が良いのではないか?
Activity
の上で動作するFragment
のライフサイクルは複雑で、実装ミスを誘発しやすく、ごく稀に起きる謎のクラッシュに悩まされたりということがあり、このため一時期はFragment
に対してネガティブな意見を見かけることがありました。Advocating Against Android Fragments(日本語訳:【翻訳】Android Fragmentへの反対声明)が一番盛り上がった時期だったかなと思います。
この時代の古文書を読むと「
Fragment
は使うべきではない」という強い言葉を見かけたりもしますが、Android Architecture Components の登場によって、ライフサイクルに応じた処理の書き方に選択肢が生まれ、View とロジックの切り離しがやりやすくなった現在では、ほぼほぼ気にしなくていいと思います。近い将来としては、SingleActivity の上に
Fragment
で画面を構築した上で、それを Navigation components で紐付けることで、直接FragmentManager
を扱うコードは減っていくものと思います。Up と Back の概念
ActionBar には Up ボタンがあるのですが、これには Back ボタンは別のセマンティクスがあります。
ただし、この違いは Navigation components が普及するとともに緩やかに消失していくと考えているので、特に詳述しません。
Android 5.0 時代
もう minSdk21 にしても許されますよね……?
Toolbar の登場
Android 5.0 でこの記事の本題である
Toolbar
が登場します。これは見た目としては ActionBar と同一です。テーマに
NoActionBar
を指定した上でToolbar
を ActionBar として使用する……、という Android 初学者の急所を抉ることに特化した形をしています。何を解決するものなのか?
本来の ActionBar とはタイトルバーが進化したもので、Window が管理しているものである という歴史的な経緯を踏まえると、なんとなく分かるのかなと思います。
ちょっと語弊のある図ですが、両者の違いはこういう感じです。
ユーザーがレイアウトで定義したアプリケーションの View は Window の上に表示されます。
ActionBar(真)を使うとき、その ActionBar は アプリケーションが管理している View の外側に、Window の機能として表示されます。現在では考慮する必要性がほとんどないものの、古代の端末ではタイトルバーで表示されるかもしれないし、ActionBar が持つ機能というのも、すべてのバージョンで同じわけではなく、カスタマイズ性も乏しいです。
テーマに
NoActionBar
を指定すると、アプリケーションは ActionBar(真)が表示されていた部分も、アプリケーションが管理する View 領域にすることができます。その上で、ActionBar(真)の代替としてレイアウトファイルにToolbar
を配置します。これは アプリケーションの View の一部なので、Android OS のバージョンに依存することがなく 、実装もただの View なので、見栄えやスクロールなどのインタラクションと連動させるような カスタマイズが非常に容易 となっています。
Toolbar(真)について
Fragment
がフラグメンテーションを起こしていたように、Toolbar
も 3 種類あります。
android.widget.Toolbar
(使うべきではないというか、存在意義がわからない)android.support.v7.widget.Toolbar
(AndroidX に移行するべき)androidx.appcompat.widget.Toolbar
minSdk 21 の時代が訪れたとすれば、
androidx.appcompat.widget.Toolbar
をsetSupportActionBar()
するのではなく、android.widget.Toolbar
をsetActionBar()
するという選択肢も取れるように思います。しかしながら、外部ライブラリである
androidx.appcompat.widget.Toolbar
は最新のすべての機能を使えるのに対して、Android SDK の一部であるandroid.widget.Toolbar
は API レベルによっては使えない機能があるので、つねに AndroidX の Toolbar(偽)を使った方がいいです。Theme.AppCompat.Light.NoActionBar テーマは何をしているの?
こんなの興味ある人がいるのか不明ですが、テーマの定義を見ると、以下のようになっています。
<style name="Theme.AppCompat.Light.NoActionBar"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style>ここで
windowNoTitle
が、requestFeature(Window.FEATURE_NO_TITLE)
を呼び出しているんだろう……と、考えるところですが、android:windowNoTitle
ではなく、windowNoTitle
という点に引っ掛けがあります。AppCompat のテーマを遡ると、 L 以上の場合、もともと NoActionBar テーマを継承しているのです。
v21/values-v21.xml<style name="Platform.V21.AppCompat.Light" parent="android:Theme.Material.Light.NoActionBar" />L 未満の場合はというと、
android:windowNoTitle
を true に設定にしています。values.xml<style name="Platform.AppCompat.Light" parent="android:Theme.Holo.Light"> <item name="android:windowNoTitle">true</item> <item name="android:windowActionBar">false</item> <!-- 略 --> </style>これが意味するところというのは、
AppCompat
のテーマを使った時点で、requestFeature(Window.FEATURE_NO_TITLE)
が必ず呼び出され、プラットフォーム本来の ActionBar が使われることはない ということです。このとき表示される ActionBar は、AppCompat が用意する ActionBar(偽)です。
android:
プレフィクスのついていない、windowNoTitle
やwindowActionBar
は、AppCompatDelegate
が受け取り ActionBar(偽)の表示制御に使われます。
windowNoTitle windowActionBar 結果 true - 表示されない false true 表示する
windowNoTitle
を true に設定した場合、ActionBar が表示されることはないので、windowActionBar
が判定されることはありません。if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) { requestWindowFeature(Window.FEATURE_NO_TITLE); } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) { // Don't allow an action bar if there is no title. requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR); }