Autosizing TextViews

TextView のフォントサイズの自動調整機能Android 8.0 (API level 26) から導入され、Support Library 26.0 以降でも採用されています。

XML

<TextView
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:maxLines="2"
    android:text="じゅげむじゅげむごこうのすりきれかいじゃりすいぎょのすいぎょうまつうんらいまつふうらいまつくうねるところにすむところ・・・"
    app:autoSizeMinTextSize="1px"
    app:autoSizeTextType="uniform" />

注意

  • support library が慮ってくれるので android.support.v7.widget.AppCompatTextView とか明示しないように。
  • app:autoSizeMinTextSize を設定しないと自動調整が行われないっぽいです。

Auto sizing が効いてない場合の表示:

f:id:beyondseeker:20180212205052p:plain

Auto sizing が効いている場合の表示:

f:id:beyondseeker:20180212205100p:plain

Data BindingでDelegates.observableを使う際に非nullの初期値を強制されたくない件

Data Binding Library で notifyPropertyChanged を利用する際、普通にやると下記のように非 null の初期値が強制されてしまう。

class MyModel : BaseObservable() {
    @get:Bindable
    var myText: String by Delegates.observable("") { _, _, _ -> notifyPropertyChanged(BR.myText) }
}

しかし、lazy のように get 時には非 null だが初期値は null にしたいというケースがある。

でも、下記のようにできるとラクチンっすね。

class MyModel : BaseObservable() {
    @get:Bindable
    var myText: String by dataBindingObservable(BR.myText)
}

ということで、以下のようなコードで対応する。

// 上記から dataBindingObservable(BR.myText) って呼ばれるやつ。
fun <T> BaseObservable.dataBindingObservable(brId: Int): ReadWriteProperty<Any?, T> = lazyObservable { _, _, _ -> notifyPropertyChanged(brId) }

/**
 * @see kotlin.properties.Delegates.observable
 */
inline fun <T> lazyObservable(crossinline onChange: (property: KProperty<*>, oldValue: T?, newValue: T) -> Unit):
        ReadWriteProperty<Any?, T> = object : LazyObservableProperty<T>() {
    override fun afterChange(property: KProperty<*>, oldValue: T?, newValue: T) = onChange(property, oldValue, newValue)
}

/**
 * @see kotlin.properties.ObservableProperty
 */
abstract class LazyObservableProperty<T> : ReadWriteProperty<Any?, T> {
    private var value: T? = null

    protected open fun beforeChange(property: KProperty<*>, oldValue: T?, newValue: T): Boolean = true

    protected open fun afterChange(property: KProperty<*>, oldValue: T?, newValue: T) {}

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        val oldValue = this.value
        if (!beforeChange(property, oldValue, value)) {
            return
        }
        this.value = value
        afterChange(property, oldValue, value)
    }
}

おまけ。

// notifyChange() を使いたい場合
fun <T> BaseObservable.dataBindingObservable(): ReadWriteProperty<Any?, T> = lazyObservable { _, _, _ -> notifyChange() }

// 初期値と brId を指定したい場合
fun <T> BaseObservable.dataBindingObservable(initialValue: T, brId: Int): ReadWriteProperty<Any?, T> = Delegates.observable(initialValue) { _, _, _ -> notifyPropertyChanged(brId) }

// 初期値を指定して notifyChange() を使いたい場合
fun <T> BaseObservable.dataBindingObservable(initialValue: T): ReadWriteProperty<Any?, T> = Delegates.observable(initialValue) { _, _, _ -> notifyChange() }

kotlinで値がprimitive wrapperのためにlateinitできない場合

'lateinit' modifier is not allowed on properties of primitive types とか怒られるので by Delegates.notNull() で凌ぎましょう。

例:

import kotlin.properties.Delegates

var i: Int by Delegates.notNull()

fun main(args: Array<String>) {
    i = 10
    println("i = $i")
}

結果:

i = 10

Data Binding Library と Google Analytics for Firebase の依存関係の件

Data Binding Library と Google Analytics for Firebase が古いライブラリを参照してるのでメモ。

手元にある support library の version として "27.0.2" を採用しているプロジェクトで、普通にビルドすると lint で下記のように怒られる。

Incompatible Gradle Versions

../../build.gradle: All com.android.support libraries must use the exact same version specification (mixing versions can lead to runtime crashes). Found versions 27.0.2, 25.2.0. Examples include com.android.support:animated-vector-drawable:27.0.2 and com.android.support:support-media-compat:25.2.0

gradlew :modules:app:dependencies とかやると下記のような依存関係がわかる。

com.android.databinding:library:1.3.1
  - com.android.support:support-v4:21.0.3

com.google.firebase:firebase-analytics:11.8.0
  - com.google.android.gms:play-services-basement:11.8.0
    - com.android.support:support-v4:25.2.0

ぐぬぬ、、、

本質的な解決じゃないけど下記のように設定してlintエラーを回避するくらいしかできんよな、、、(´・ω・`)

implementation "com.android.support:support-v4:27.0.2"

RxJava2のnullの扱いとOptional

RxJava 2.0 で Observable による null の emission が非サポートになったので、その点に関してのメモ。

Javaでnullをemitしてみる

Observable<String> source = Observable.create(emitter -> {
    try {
        emitter.onNext("Alpha");
        emitter.onNext("Beta"); // io.reactivex.exceptions.OnErrorNotImplementedException: The mapper function returned a null value.
        emitter.onNext("Gamma");
        emitter.onNext("Delta");
        emitter.onNext("Epsilon");
        emitter.onComplete();
    } catch (Throwable e) {
        emitter.onError(e);
    }
});
source.map(s -> s.length() >= 5 ? s.length() : null).subscribe(s -> System.out.println("RECEIVED: " + s));

emitter.onNext("Beta"); のところで例外がスローされる。

JavaでOptionalを使ってみる

        Observable<String> source = Observable.create(emitter -> {
            try {
                emitter.onNext("Alpha");
                emitter.onNext("Beta");
                emitter.onNext("Gamma");
                emitter.onNext("Delta");
                emitter.onNext("Epsilon");
                emitter.onComplete();
            } catch (Throwable e) {
                emitter.onError(e);
            }
        });
        source.map(s -> Optional.ofNullable(s.length() >= 5 ? s.length() : null)).subscribe(optional -> System.out.println("RECEIVED: " + (optional.orElse(null))));

Optional でラップすることにより null を emit していないので例外はスローされず、以下のように出力される。

RECEIVED: 5
RECEIVED: null
RECEIVED: 5
RECEIVED: 5
RECEIVED: 7

kotlinで普通にSequencesを使った場合

        val source = listOf("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
        source.map { s -> if (s.length >= 5) s.length else null }.forEach { s -> println("RECEIVED: " + s) }

普通に null が扱えるので、例外はスローされず以下のように出力される。

RECEIVED: 5
RECEIVED: null
RECEIVED: 5
RECEIVED: 5
RECEIVED: 7

ThinkPad Compact Bluetooth Keyboard with TrackPointの接続が切れるようになった時のメモ

Windows 10 で ThinkPad Compact Bluetooth Keyboard with TrackPoint(ThinkPad Bluetooth ワイヤレストラックポイントキーボード)の接続が切れるようになった時のメモ。

結論

ThinkPad 側のアップデートで IntelBluetooth ドライバをアップデートしたら治ったっぽい。(2017-08-18現在では全く問題がない)

過去の状況

2017-07-30現在では、どうにも動きが不安定で因果関係がよくわからない。数秒で切れたり(一番多い)、ディスプレイの切り替えがあると止まったり(これは再現性が高い)、ディスプレイの切り替えをやっても止まらないが翌日には止まっていたり(ほとんどない)、そんな感じ。

メモ

  1. 試してみたけど無関係だった設定その1
    • バイスマネージャー
    • Bluetooth
    • インテル(R) ワイヤレス Bluetooth(R)
    • 電源の管理
    • 『電源の節約のために、コンピューターでこのデバイスの電源をオフにできるようにする(A)』のチェックを外してOKを選択。
  2. 試してみたけど無関係だった設定その2
    • バイスマネージャー
    • ヒューマン インターフェイスバイス
    • Lenovo BT Interface Device(HID)
    • 電源の管理
    • 『電源の節約のために、コンピューターでこのデバイスの電源をオフにできるようにする(A)』のチェックを外してOKを選択。

java や kotlin でもポインタを扱いたい件

多角的に検証してないけどとりあえず思い付きをメモしてみた。

状態を持つ Session オブジェクトを UI から操作する場合を考えてみる。

Session が単一の interface を持つ場合は、特定状態では実行不可能なメソッドを叩くと実行時例外がスローされるように作ることが多い。

しかし、そもそも実行不可能なメソッドを叩けなくするというアプローチもある。状態遷移を起こすメソッドの戻り値を状態遷移イベントにして、その中に次の状態用のインタフェースを入れておくとか。

この場合、状態用のインタフェースの参照を UI が保持してしまうと、Session 側で破棄されても UI 側で使えてしまう。もしそれがポインタであれば、Session 側で ポインタの値を null にするなり新しい値にしておけば、古い参照を使用することが不可能になる。

実際問題として正しいコードを書く分にはポインタがあろうがなかろうが関係ないのだが、assertion のコストが全然違う。UI側が古い参照を叩いてしまうというバグについては、ポインタが使える場合はヌルポで落ちるだけなので何もしなくてもよいということもある。しかしポインタが使えない場合は古いものを叩いた時に実行時例外をスローするような仕組みが必要となり、それを入れないとエラーと気づかないまま間違った動作をしてしまう可能性がある。

現状だと開発者に対するルールで縛るしかなさそうな気がする、、、。