- 投稿日:2019-08-27T23:25:52+09:00
UnityでAndroidのKeystoreを自動でセーブ・ロードする
前提
- unity 2018.4.7f1
- Android
できること
- 一度、KeystoreとAliasのパスワードを設定してビルドすると、以降、エディター起動時に自動的に設定されるようになります。
やりかた
- 以下のコードを、適当な
Assets/~/Editor/~へ入れてください。SaveKeystore.csusing UnityEditor; using UnityEditor.Callbacks; public class SaveKeystore { private static string keystorePrefsName => PlayerSettings.Android.keystoreName; private static string keyaliasPrefsName => $"{PlayerSettings.Android.keystoreName}/{PlayerSettings.Android.keyaliasName}"; [InitializeOnLoadMethod] private static void OnLoad () { if (string.IsNullOrEmpty (PlayerSettings.Android.keystorePass) && !string.IsNullOrEmpty (PlayerSettings.Android.keystoreName)) { PlayerSettings.Android.keystorePass = EditorPrefs.GetString (keystorePrefsName); } if (string.IsNullOrEmpty (PlayerSettings.Android.keyaliasPass)) { PlayerSettings.Android.keyaliasPass = EditorPrefs.GetString (keyaliasPrefsName); } } [PostProcessBuild] private static void OnBuilded (BuildTarget target, string path) { if (target == BuildTarget.Android) { if (!string.IsNullOrEmpty (PlayerSettings.Android.keystoreName) && !string.IsNullOrEmpty (PlayerSettings.Android.keystorePass)) { EditorPrefs.SetString (keystorePrefsName, PlayerSettings.Android.keystorePass); } if (!string.IsNullOrEmpty (PlayerSettings.Android.keyaliasName) && !string.IsNullOrEmpty (PlayerSettings.Android.keyaliasPass)) { EditorPrefs.SetString (keyaliasPrefsName, PlayerSettings.Android.keyaliasPass); } } } }留意事項
- KeystoreおよびAliasのパスワードが、平文でエディターの設定内(EditorPrefs)に保存されます。
- Keystoreファイルの絶対パスに依存します。プロジェクトフォルダを移動するとオートロードできなくなります。
- 投稿日:2019-08-27T23:25:52+09:00
UnityでAndroidのKeystoreパスワードを自動セーブ・ロードする
前提
- unity 2018.4.7f1
- Android
できること
- 一度、KeystoreとAliasのパスワードを設定してビルドすると、以降、エディター起動時に自動的に設定されるようになります。
やりかた
- 以下のコードを、適当な
Assets/~/Editor/~へ入れてください。SaveKeystore.csusing UnityEditor; using UnityEditor.Callbacks; public class SaveKeystore { private static string keystorePrefsName => PlayerSettings.Android.keystoreName; private static string keyaliasPrefsName => $"{PlayerSettings.Android.keystoreName}/{PlayerSettings.Android.keyaliasName}"; [InitializeOnLoadMethod] private static void OnLoad () { if (string.IsNullOrEmpty (PlayerSettings.Android.keystorePass) && !string.IsNullOrEmpty (PlayerSettings.Android.keystoreName)) { PlayerSettings.Android.keystorePass = EditorPrefs.GetString (keystorePrefsName); } if (string.IsNullOrEmpty (PlayerSettings.Android.keyaliasPass)) { PlayerSettings.Android.keyaliasPass = EditorPrefs.GetString (keyaliasPrefsName); } } [PostProcessBuild] private static void OnBuilded (BuildTarget target, string path) { if (target == BuildTarget.Android) { if (!string.IsNullOrEmpty (PlayerSettings.Android.keystoreName) && !string.IsNullOrEmpty (PlayerSettings.Android.keystorePass)) { EditorPrefs.SetString (keystorePrefsName, PlayerSettings.Android.keystorePass); } if (!string.IsNullOrEmpty (PlayerSettings.Android.keyaliasName) && !string.IsNullOrEmpty (PlayerSettings.Android.keyaliasPass)) { EditorPrefs.SetString (keyaliasPrefsName, PlayerSettings.Android.keyaliasPass); } } } }留意事項
- KeystoreおよびAliasのパスワードが、平文でエディターの設定内(EditorPrefs)に保存されます。
- Keystoreファイルの絶対パスに依存します。プロジェクトフォルダを移動するとオートロードできなくなります。
- 投稿日:2019-08-27T20:27:30+09:00
【lightbox2】disableScrolling を true にしても、スマホでスクロールできてしまう問題【jQuery】
前回、こんなこと があり、2.10.0 で試しています。
lightbox2 のオプションには
disableScrollingがあり、trueにすると Litebox が開いてる間はスクロールできなくなります。オプションの参考:簡単に画像のポップアップ!「Lightbox」の実装方法
しかし、
trueにしても、スマホではスクロールできるバグ?があります。
htmlやbodyタグにoverflow: hiddenを指定するとスクロールできなくなります。ただし、これはPCのみでスマホではスクロールできてしまいます。
そのため、JavaScriptで制御する必要があります。
preventDefault();を使うスクロールを無効化できましたが、新しいブラウザでは効かないようです。新しいブラウザで
preventDefault()を使いたいときは、passive:falseにする必要があります。具体的に言うと
addEventListenerの第3引数に{passive: false}を指定します。スクロール禁止・再開を関数化したのが下記です。
function scroll_control(event) { event.preventDefault(); } function no_scroll(){ document.addEventListener("mousewheel", scroll_control, {passive: false}); document.addEventListener("touchmove", scroll_control, {passive: false}); } function return_scroll(){ document.removeEventListener("mousewheel", scroll_control, {passive: false}); document.removeEventListener('touchmove', scroll_control, {passive: false}); }
no_scroll()を呼び出せばスクロールが禁止され、return_scroll()を呼び出せばスクロール禁止してたのを再開できます。これらを、
litebox.js(v2.10.0) に追記します。追加箇所の抜粋です。
litebox.js(最初に関数を記述)/*! * Lightbox v2.10.0 * by Lokesh Dhakar * * More info: * http://lokeshdhakar.com/projects/lightbox2/ * * Copyright 2007, 2018 Lokesh Dhakar * Released under the MIT license * https://github.com/lokesh/lightbox2/blob/master/LICENSE * * @preserve */ // scroll control function scroll_control(event) { event.preventDefault(); } function no_scroll(){ document.addEventListener("mousewheel", scroll_control, {passive: false}); document.addEventListener("touchmove", scroll_control, {passive: false}); } function return_scroll(){ document.removeEventListener("mousewheel", scroll_control, {passive: false}); document.removeEventListener('touchmove', scroll_control, {passive: false}); }litebox.js(ライトボックスがスタートするときにno_scroll()を実行)// Show overlay and lightbox. If the image is part of a set, add siblings to album array. Lightbox.prototype.start = function($link) { no_scroll(); // この1行を追記しました。 var self = this; var $window = $(window); $window.on('resize', $.proxy(this.sizeOverlay, this)); $('select, object, embed').css({ visibility: 'hidden' }); this.sizeOverlay(); this.album = []; var imageNumber = 0; function addToAlbum($link) { self.album.push({ alt: $link.attr('data-alt'), link: $link.attr('href'), title: $link.attr('data-title') || $link.attr('title') }); }litebox.js(ライトボックスが終了するときにreturn_scroll()を実行)// Closing time. :-( Lightbox.prototype.end = function() { return_scroll(); // この1行を追記しました。 this.disableKeyboardNav(); $(window).off('resize', this.sizeOverlay); this.$lightbox.fadeOut(this.options.fadeDuration); this.$overlay.fadeOut(this.options.fadeDuration); $('select, object, embed').css({ visibility: 'visible' }); if (this.options.disableScrolling) { $('html').removeClass('lb-disable-scrolling'); } };
- 投稿日:2019-08-27T18:51:37+09:00
[Mockito] ReflectionでKotlinのprotectedなメソッドをモックする
はじめに
この記事ではMockitoを使って、
protectedなメソッドをモックする方法について解説します。今回はよく使われていると思われる、Template Methodパターンでの
protectedなメソッドのモックをサンプルとして、解説を進めていきたいと思います。ソースコード : shiita0903/ProtectedMock
ライブラリ
- JUnit 4.12
- AssertJ 3.11.1
- Mockito 3.0.0
- Mockito-Kotlin 2.1.0
テストの前に
Kotlinではデフォルトが
final classなので、そのままではMockitoを使ってモック出来ません。MockitoのWikiで書かれているように、MockMakerを用意する必要があります。テスト対象のプログラム
ViewModel
MVVMアーキテクチャのViewModelがテスト対象で、ViewModelで利用しているUseCaseをモックしてテストをします。
class ViewModel( private val id: Int, private val concreteUseCase: ConcreteUseCase ) { fun loadData(): String { val resultData = concreteUseCase(id) return "${resultData.data1}:${resultData.data2}" } }UseCase
UseCaseではTemplate Methodパターンで処理の共通化をしていて、
execute()メソッドを具象クラスで実装します。Google IOのソースとかが参考になると思います。
execute()メソッドはprotectedなメソッドなので、そのままではモックすることが出来ません。data class ResultData(val data1: Int, val data2: String) class ConcreteUseCase( private val repository1: Repository1, private val repository2: Repository2 ) : UseCase<Int, ResultData>() { // protectedなのでモック出来ない override fun execute(parameters: Int): ResultData { val data1 = repository1.loadData(parameters) val data2 = repository2.loadData(parameters) return ResultData(data1, data2) } } abstract class UseCase<in P, R> { protected abstract fun execute(parameters: P): R operator fun invoke(parameters: P): R { // ローディング等の共通化処理 return execute(parameters) } }Repository
Repositoryではデータのロード等をするとします。この部分の処理はViewModelのテストと関係ないので、適当に値を返す実装です。
class Repository1 { fun loadData(id: Int): Int = id } class Repository2 { fun loadData(id: Int): String = "$id" }まとめると、ViewModel -> UseCase (-> Repository)のような依存関係がある中で、ViewModelのテストのために、UseCaseの
protectedなメソッドをモックしたいといった感じです。テストコード
まず
ConcreteUseCaseクラスのSpyを作成します。invoke()メソッドの内部でexecute()メソッドが呼ばれるように、ここではMockではなくSpyを利用します。Repositoryのインスタンスが必要なので適当にモックしておきます。val useCase = spy(ConcreteUseCase(mock(), mock()))
ConcreteUseCaseのexecute()メソッドをモックします。execute()メソッドはprotectedなので以下のコードはコンパイルエラーになってしまいます。この問題は後ほど解決するとして、テストコードを読み進めていきます。doReturn(ResultData(100, "hoge")) .whenever(useCase) .execute(any())
ConcreteUseCaseクラスのSpyを使ってViewModelクラスのインスタンスを作成します。loadData()メソッドの戻り値のアサーションと、ConcreteUseCaseクラスのinvoke()メソッドが正しく呼ばれているかをテストします。val viewModel = ViewModel(id, useCase) val actual = viewModel.loadData() assertThat(actual).isEqualTo("100:hoge") verify(useCase, times(1)).invoke(id)テストコードの全体はこのようになっています。
class ViewModelTest { @Test fun loadData() { val id = 10 // invokeからexecuteが呼び出されるようにするため、mockではなくspyを使う val useCase = spy(ConcreteUseCase(mock(), mock())) doReturn(ResultData(100, "hoge")) .whenever(useCase) .execute(any()) // ここでコンパイルエラー val viewModel = ViewModel(id, useCase) val actual = viewModel.loadData() assertThat(actual).isEqualTo("100:hoge") verify(useCase, times(1)).invoke(id) } }protectedなメソッドのモック
テストコードでコンパイルエラーとなってしまった、
protectedなメソッドのモックをリフレクションで実現します。declaredMemberFunctionsからモック対象のexecute()メソッドを取得し、isAccessible = trueでアクセス可能にしてからcall()メソッドで呼び出します。// protectedなメソッドの代わりに使う拡張関数 fun <P, R> UseCase<P, R>.execute(parameters: P) = this::class.declaredMemberFunctions.single { it.name == "execute" }.let { function -> function.isAccessible = true function.call(this, parameters) }ここでのポイントは、スーパクラスの
UseCaseにprotectedなメソッドと同じ名前のexecute()拡張関数を定義しているところです。このようにリフレクションのコードを書くと、テストコードからは、protectedなメソッドを直接モックできているように記述できます。これでコンパイルエラーの部分は、問題なく実行出来るようになっているはずです。
おわりに
Template Methodパターンはよく使うパターンだと思うので、
protectedなメソッドのモックは結構需要あるんじゃないかと思ってます。UseCaseでなくても、protectedなメソッドと同名のリフレクションを利用した拡張関数を書くことで、快適にテストを書けるようになるはずです。
- 投稿日:2019-08-27T17:56:18+09:00
Android StudioでEmulator使用時、Emulator: emulator: ERROR: detected a hanging thread 'QEMU2 CPU0 thread'. No response for 15011 msエラー発生
Android StudioでEmulator起動時もしくは使用途中で以下のログを出してフリーズしてしまう場合の対応
17:31 Emulator: emulator: ERROR: detected a hanging thread 'QEMU2 CPU0 thread'. No response for 15011 ms 17:31 Emulator: emulator: ERROR: detected a hanging thread 'QEMU2 CPU1 thread'. No response for 15011 ms 17:31 Emulator: emulator: ERROR: detected a hanging thread 'QEMU2 CPU0 thread'. No response for 15011 ms 17:31 Emulator: emulator: ERROR: detected a hanging thread 'QEMU2 CPU0 thread'. No response for 15011 ms
- 投稿日:2019-08-27T14:54:48+09:00
Databindingで下に重なっているViewにタッチイベントを送らないようにする
タイトル以上でも以下でもないので、早速本題に
BindingAdapterの定義
ViewBinding.kt@BindingAdapter("app:touch_block") fun setTouchBlock(view: View, isBlock: Boolean) { view.setOnTouchListener { _, _ -> isBlock } }onTouch の戻り値をレイアウトから指定できるようにする
レイアウトでの指定方法
layout.xml<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <android.support.constraint.ConstraintLayout android:id="@+id/upper_layout" android:layout_width="match_parent" android:layout_height="match_parent" app:touch_block="@{true}" // ← ココ ...これで id/upper_layout の下に重なっているViewにタッチイベントが送られなくなりました。
細かい条件は指定できないので割り切りでタッチイベントを送りたくないという場合には使えそう。
app:touch_block="@{true}"をapp:touch_block="@{viewModel.touchBlock}"とすればアプリの状態に応じてタッチイベントを 送る/送らない くらいの制御はできそうです。layout.xml<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="viewModel" type="com.hoge.SampleViewModel" /> </data> ... <android.support.constraint.ConstraintLayout android:id="@+id/upper_layout" android:layout_width="match_parent" android:layout_height="match_parent" app:touch_block="@{viewModel.touchBlock}" // ← ココ ...SampleViewModel.ktSampleViewModel : ViewModel() { val touchBlock = ObservableBoolean(true) ... }
- 投稿日:2019-08-27T12:36:03+09:00




