Slide 1

Slide 1 text

ライブラリでしかお目にかかれ ない珍しい実装 GMOメイクアプリ・Sansan モバイル勉強会 in 福岡 mikan(一瀬喜弘)

Slide 2

Slide 2 text

自己紹介 object Mikan { val name = "一瀬喜弘" val company = "karabiner.tech" val work = Engineer.Android val hobby = listOf( "漫画", "アニメ", "ゲーム", "折り紙", "OSS開発・コントリビュート", ) }

Slide 3

Slide 3 text

1. Avoid using data class

Slide 4

Slide 4 text

Avoid using data class What ライブラリが公開しているAPIについて、 data class を使用することはほとんどない

Slide 5

Slide 5 text

@Immutable class ButtonColors constructor( val containerColor: Color, val contentColor: Color, val disabledContainerColor: Color, val disabledContentColor: Color, ) { fun copy( containerColor: Color = this.containerColor, contentColor: Color = this.contentColor, disabledContainerColor: Color = this.disabledContainerColor, disabledContentColor: Color = this.disabledContentColor ) = ButtonColors( containerColor.takeOrElse { this.containerColor }, contentColor.takeOrElse { this.contentColor }, disabledContainerColor.takeOrElse { this.disabledContainerColor }, disabledContentColor.takeOrElse { this.disabledContentColor }, ) // ... override fun hashCode(): Int { var result = containerColor.hashCode() result = 31 * result + contentColor.hashCode() result = 31 * result + disabledContainerColor.hashCode() result = 31 * result + disabledContentColor.hashCode() return result

Slide 6

Slide 6 text

Avoid using data class Why data class が生成する、 copy や componentN メソッドにABI互換性がないから プロパティを追加したら、コンストラクタとcopyメソッドの互換性が壊れる プロパティの順番を変えたら、conponentNの互換性が壊れる https://kotlinlang.org/docs/api-guidelines-backward-compatibility.html#avoid-using-data-classes-in-your-api How 自前で hashCode や copy を実装している

Slide 7

Slide 7 text

2. Fake constructor

Slide 8

Slide 8 text

Fake constructor How private val _counter = MutableStateFlow(0)

Slide 9

Slide 9 text

Fake constructor How https://github.com/Kotlin/kotlinx.coroutines/blob/6c6df2b850382887462eeaf51f21f58bd982491d/kotlinx- coroutines-core/common/src/flow/StateFlow.kt#L156-L181 private val _counter = MutableStateFlow(0) public interface MutableStateFlow : StateFlow, MutableSharedFlow { // ... }

Slide 10

Slide 10 text

Fake constructor What public fun MutableStateFlow(value: T): MutableStateFlow = StateFlowImpl(value ?: NULL)

Slide 11

Slide 11 text

Fake constructor What 関数名をインターフェースと同じにして、あたかもコンストラクタかのように見せかけている Why 実装クラスを隠す目的で利用されている ライブラリではDIが使えないので、このような工夫がなされているのだと思われる public fun MutableStateFlow(value: T): MutableStateFlow = StateFlowImpl(value ?: NULL)

Slide 12

Slide 12 text

3. Readonly implementation

Slide 13

Slide 13 text

Readonly implementation What MutableStateFlow を StateFlow に up cast するときに利用されている public fun MutableStateFlow.asStateFlow(): StateFlow = ReadonlyStateFlow(this, null)

Slide 14

Slide 14 text

Readonly implementation What public fun MutableStateFlow.asStateFlow(): StateFlow = ReadonlyStateFlow(this, null) @OptIn(ExperimentalForInheritanceCoroutinesApi::class) private class ReadonlyStateFlow( flow: StateFlow, @Suppress("unused") private val job: Job? // keeps a strong reference to the job (if present) ) : StateFlow by flow, CancellableFlow, FusibleFlow { override fun fuse(context: CoroutineContext, capacity: Int, onBufferOverflow: BufferOverflow) = fuseStateFlow(context, capacity, onBufferOverflow) }

Slide 15

Slide 15 text

Readonly implementation Why MutableStateFlowにdown castできてしまう class MainViewModel: ViewModel() { private val _uiState = MutableStateFlow(MainUiState()) val uiState: StateFlow = _uiState } (viewModel.uiState as MutableStateFlow).update { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // down cast to MutableStateFlow }

Slide 16

Slide 16 text

Readonly implementation Why ClassCastException が発生して、クラッシュする より安全な実装になる class MainViewModel: ViewModel() { private val _uiState = MutableStateFlow(MainUiState()) val uiState = _uiState.asStateFlow() } (viewModel.uiState as MutableStateFlow).update { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // throw ClassCastException }

Slide 17

Slide 17 text

ご清聴ありがとうございました