SharedPreferences で putStringSet() が1つしか保存できない。

何ですか、この動きは。

「Preference から Set を取得後、追加して再保存する」

問題なく以下のようなテストも通過します。


@Test fun getStringSet() {

  val key = "key"
  val preferences = context.getSharedPreferences(key, Context.MODE_PRIVATE)
  val beforeData = preferences.getStringSet(key, mutableSetOf())
  val beforeSize = beforeData.size

  beforeData.add(System.currentTimeMillis().toString())
  preferences.edit()
      .putStringSet(key, beforeData)
      .commit()

  val afterData = preferences.getStringSet(key, mutableSetOf())
  val afterSize = afterData.size

  println("$beforeData -> $afterData")
  println("$beforeSize -> $afterSize")
  assertEquals(beforeSize + 1, afterSize)

}


I/System.out: [1520865219872, 1520865358333] -> [1520865219872, 1520865358333]
I/System.out: 1 -> 2

しかし、何回実行してもデータが1つのままで増えていきません。


I/System.out: [1520865219872, 1520865504185] -> [1520865219872, 1520865504185]
I/System.out: 1 -> 2

I/System.out: [1520865219872, 1520865553254] -> [1520865219872, 1520865553254]
I/System.out: 1 -> 2

実際にファイルを確認してみると、


mako:/data/data/com.example.cryptocurrency/shared_prefs # cat key.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <set name="key">
        <string>1520865219872</string>
    </set>
</map>

1つしか保存されてないですね!

クソですね!

なぜなのか?

android - Misbehavior when trying to store a string set using SharedPreferences - Stack Overflow

Android compares the modified HashSet that you are trying to save using SharedPreferences.Editor.putStringSet with the current one stored on the SharedPreference, and both are the same object!!!

A possible solution is to make a copy of the Set returned by the SharedPreferences object

どうやら、同じオブジェクトの比較となるので保存してくれないようです。

読み出し後、すぐコピーすればいいのですね!


val beforeData = preferences.getStringSet(key, mutableSetOf()).toMutableSet()

なんとなく、よくある話のような気がします。


Soundflower と Audio MIDI設定

なんとなく接続できてるがなんだかきもいです。

OBS 音声設定の Soundflower。

Releases · mattingalls/Soundflower · GitHub

自在に利用したいので接続のつながりを少し整理しておきます。

どうしてSoundFlowerとかいるのか。

以下の問題があるそうです。

macOS版 OBS Studio既知の問題一覧 (公式サイトより引用)
・デスクトップオーディオをキャプチャする場合は、仮想音声デバイスを利用する必要があります。

OBS Studio for macOS インストール / アップデート 方法解説:OBS Studioに関する情報投稿ブロマガ - ブロマガ

ここでいう「仮想音声デバイス」が Soundflower らしいです。

OBSで設定-音声をみてみます。

選択肢には Soundflower しかありませんね。

「既定」は何なのか謎ですがとりあえず音声は受け取れてないようでした。

「デスクトップ音声」の設定

OBSがデスクトップ音声をSoundflowerからしか受け取れないことは分かりました。

あとはこの先に「デスクトップ音声」を接続すればいいのですね!

Mac設定から「サウンド」「出力」で 「Soundflower(2ch)」を選択します。

これで、いけるっちゃあいけるのですが、Macからは音がでなくなりました。

クソですね!

Audio MIDIの設定

「OBS」と「内蔵出力(スピーカーやヘッドホン)」の2つに「デスクトップ音声」をわたしたいですよね!

しかし、Mac設定から「サウンド」「出力」では「サウンドを出力する装置」は一つしか選択できません。

Mac上で「アプリケーション」「ユーティリティ」に「AudioMIDI設定」というのがあります。

これは、Macの設定「サウンド」の詳細設定ができるものです。

赤い四角のスピーカーマークが「デスクトップ音声」です。サウンド設定から「Soundflower(2ch)」を選択しましたよね。

ここで「複数出力装置」なるものを作ると分岐できるようになります。

「内蔵出力(スピーカー)」を有効にして、「標準出力装置」を右クリックで「デスクトップ音声」を接続します。

これは以下のようにつながっています。

これで、Mac上の音声を聞きながらOBS経由でゲーム配信できますね!

まとめ

「OBS」は「Soundflower」からしか「デスクトップ音声」を取得できない。

「デスクトップ音声」はひとつのデバイスしか接続できないのでその先で分岐する。

「複数出力装置」や「Soundflower」は、複数の入出力を行えるデバイスである。

だが、さまざまな理由からソフトウェアによって利用できない入出力デバイスがある。

ゲーム配信者はすごいなあ。

👉 【Catalina】SoundFlowerからBlackHoleに移行してOBS接続おさらい 

Soundflower - ニコニコ生主のためのwiki
機器セットを作成して複数のオーディオインターフェイスを組み合わせる - Apple サポート
「Audio MIDI 設定.app」を紐解く | scribble warehouse


【Kotlin】sortedWith + compareBy で並び替え

「C# でこんなの書けるけど Kotlin ではどう書くの?」 という話。


var list = new List<Person>();
list.Add(new Person(25, "Tom"));
list.Add(new Person(25, "Dave"));
list.Add(new Person(20, "Kate"));
list.Add(new Person(20, "Alice"));

// will produce: Alice, Kate, Dave, Tom
var sortedList = list
    .OrderBy(person => person.Age)
    .ThenBy(person => person.Name)
    .ToList();

Javaのコードから考えるとKotlinでももちろんシンプルな記述となります。


val sortedList = list
    .sortedWith(compareBy({ it.age }, { it.name }))


val sortedList = list
    .sortedWith(compareBy(Person::age, Person::name))


list.sortedWith(compareBy<Person> { it.age }.thenBy { it.name }.thenBy { it.address })


list.sortedWith(compareBy({ it.age }, { it.name }, { it.address }))

java - Sort collection by multiple fields in Kotlin - Stack Overflow

大文字小文字を無視する場合。


places
    .sortedWith(
        compareBy(String.CASE_INSENSITIVE_ORDER, { it.name })
     )


places
    .sortedWith(
        compareBy(String.CASE_INSENSITIVE_ORDER) { it.name }
     )


places
    .sortedWith(
        compareBy(String.CASE_INSENSITIVE_ORDER, Place::name)
     )

kotlin - How to sort objects list in case insensitive order? - Stack Overflow

降順昇順の対応やモデルに入れたり。


val sortedList = list
    .sortedWith(compareBy({ it.a }, { it.b }, { it.c }))


list.sortedWith(
    compareBy<Foo> { it.a }
        .thenByDescending { it.b }
        .thenBy { it.c }
)


class Foo(val a: String, val b: Int, val c: Date) : Comparable<Foo> {

  override fun compareTo(other: Foo)
      = compareValuesBy(this, other, { it.a }, { it.b }, { it.c })
}
val sortedList = list.sorted()


class Foo(val a: String, val b: Int, val c: Date) : Comparable<Foo> {

  override fun compareTo(other: Foo) = comparator.compare(this, other)

  companion object {
    val comparator = compareBy(Foo::a, Foo::b, Foo::c)
  }
}
val sortedList = list.sorted()

comparable - How to sort based on/compare multiple values in Kotlin? - Stack Overflow

シンプルで便利になりつつも複数の記述で書くことができてしまう最近の言語のパターン。