20190827のAndroidに関する記事は7件です。

UnityでAndroidのKeystoreを自動でセーブ・ロードする

前提

  • unity 2018.4.7f1
  • Android

できること

  • 一度、KeystoreとAliasのパスワードを設定してビルドすると、以降、エディター起動時に自動的に設定されるようになります。

やりかた

  • 以下のコードを、適当なAssets/~/Editor/~へ入れてください。
SaveKeystore.cs
using 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ファイルの絶対パスに依存します。プロジェクトフォルダを移動するとオートロードできなくなります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityでAndroidのKeystoreパスワードを自動セーブ・ロードする

前提

  • unity 2018.4.7f1
  • Android

できること

  • 一度、KeystoreとAliasのパスワードを設定してビルドすると、以降、エディター起動時に自動的に設定されるようになります。

やりかた

  • 以下のコードを、適当なAssets/~/Editor/~へ入れてください。
SaveKeystore.cs
using 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ファイルの絶対パスに依存します。プロジェクトフォルダを移動するとオートロードできなくなります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【lightbox2】disableScrolling を true にしても、スマホでスクロールできてしまう問題【jQuery】

前回、こんなこと があり、2.10.0 で試しています。

lightbox2 のオプションには disableScrolling があり、true にすると Litebox が開いてる間はスクロールできなくなります。

オプションの参考:簡単に画像のポップアップ!「Lightbox」の実装方法

しかし、true にしても、スマホではスクロールできるバグ?があります。

htmlbody タグに overflow: hidden を指定するとスクロールできなくなります。

ただし、これはPCのみでスマホではスクロールできてしまいます。

そのため、JavaScriptで制御する必要があります。

preventDefault(); を使うスクロールを無効化できましたが、新しいブラウザでは効かないようです。

参考:スクロール禁止が overflow:hidden や 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});
}

引用元: マウスによるスクロールやスマホのスワイプを制御するjs(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');
    }
  };
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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()))

ConcreteUseCaseexecute()メソッドをモックします。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)
    }

ここでのポイントは、スーパクラスのUseCaseprotectedなメソッドと同じ名前のexecute()拡張関数を定義しているところです。このようにリフレクションのコードを書くと、テストコードからは、protectedなメソッドを直接モックできているように記述できます。

これでコンパイルエラーの部分は、問題なく実行出来るようになっているはずです。

おわりに

Template Methodパターンはよく使うパターンだと思うので、protectedなメソッドのモックは結構需要あるんじゃないかと思ってます。UseCaseでなくても、protectedなメソッドと同名のリフレクションを利用した拡張関数を書くことで、快適にテストを書けるようになるはずです。

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

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
  • Tools → AVD Manager
    a02.png
  • 該当DeviceのEdit(鉛筆アイコン)を選択
    a01.png
  • 「Show Advanced Setting」を選択
    a03.png
  • Emulated Performance項目のBoot Optionを切り替える。
    • Cold Bootになっている場合にはQuick Bootに
    • Quick Bootになっている場合にはCold Bootに
      a04.png
  • Emulatorを起動
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.kt
SampleViewModel : ViewModel() {
    val touchBlock = ObservableBoolean(true)

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

Android Studioに同梱されているKotlin Pluginをバージョンアップする方法

概要

先日kotlinの新バージョン 1.3.50がリリースされました
それに伴ってKotlin Pluginも1.3.50に対応したのですが、Android Studioにはもともと同梱されており、
Pluginからはバージョンアップできなくて少し手こずったので方法を残しておきます

方法

preferencesの下記の場所に新しいバージョンをinstallできる
Preferences | Languages & Frameworks | Kotlin
image.png

もしくは、Android Studioを起動した時に右下にインストールを促すサジェストが出るのでそれにしたがってインストールする

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