使うと手放せなくなるKotlin Extension集 (Kotlin 1.3版)

便利で汎用性高めのExtension集です。
今後も便利なExtensionができ次第、本記事を更新していきます。
この他にも便利なものがあればコメントでお願いします！！

[Swift版の記事](https://qiita.com/tattn/items/dc7dfe2fceec00bb4ff7)を見て、Kotlin版も欲しいなと思ったのがきっかけです。

## クラス名の取得

- ProGuardを使用していると、クラス名は難読化されるので注意しましょう。

```kotlin:Any.kt
/**
 * クラス名を取得
 *
 * @return String
 */
fun Any.className(): String = javaClass.simpleName

/**
 * クラス名を取得
 *
 * @param any Any
 * @return String
 */
fun Any.className(any: Any?): String {
    if (any == null) {
        return "Null"
    }
    return any.javaClass.simpleName
}
```

## Viewの表示切り替え

```kotlin:ViewExt.kt
/**
 * VisibleとGoneを切り替える
 *
 * @param isVisible Visibleであるかどうか
 */
fun View.visibleOrGone(isVisible: Boolean) {
    visibility = if (isVisible) View.VISIBLE else View.GONE
}

/**
 * VisibleとInvisibleを切り替える
 *
 * @param isVisible Visibleであるかどうか
 */
fun View.visibleOrInvisible(isVisible: Boolean) {
    visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
}

/**
 * GoneとVisibleを切り替える
 *
 * @param isGone Goneであるかどうか
 */
fun View.goneOrVisible(isGone: Boolean) {
    visibility = if (isGone) View.GONE else View.VISIBLE
}

/**
 * InvisibleとVisibleを切り替える
 *
 * @param isInvisible Invisibleであるかどうか
 */
fun View.invisibleOrVisible(isInvisible: Boolean) {
    visibility = if (isInvisible) View.INVISIBLE else View.VISIBLE
}

/**
 * VISIBLEに切り替える
 */
fun View.toVisible() {
    visibility = View.VISIBLE
}

/**
 * INVISIBLEに切り替える
 */
fun View.toInvisible() {
    visibility = View.INVISIBLE
}

/**
 * GONEに切り替える
 */
fun View.toGone() {
    visibility = View.GONE
}
```

## Viewの連打対策

```kotlin:ViewExt.kt
/**
 * 連打対策のためViewを一定時間クリック無効にするクリックリスナー
 *
 * @param onClickListener Unit
 */
fun View.setOnClickPauseListener(onClickListener: (View) -> Unit) {
    if (hasOnClickListeners()) {
        return
    }
    setOnClickListener {
        pauseClickTimer()
        onClickListener.invoke(it)
    }
}

/**
 * 連打対策のためViewを一定時間クリック無効にする
 *
 * @param delayMillis クリックの無効化時間（ミリ秒）
 */
fun View.pauseClickTimer(delayMillis: Long = 500) {
    isClickable = false
    Handler().postDelayed({ isClickable = true }, delayMillis)
}
```

## ViewPager

```kotlin:ViewPagerExt.kt
fun ViewPager.setOnPageChangeListener(onPageChangeListener: (Int) -> Unit) {
    addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
            // do nothing
        }

        override fun onPageSelected(position: Int) {
            onPageChangeListener(position)
        }

        override fun onPageScrollStateChanged(state: Int) {
            // do nothing
        }
    })
}
```

## RecyclerView with Groupie

- Groupieライブラリについては[こちら](https://github.com/lisawray/groupie)

```kotlin:RecyclerViewExt.kt
fun RecyclerView.setGroupieAdapter() {
    adapter = GroupAdapter<ViewHolder>()
}

fun RecyclerView.setLinearLayoutManager() {
    layoutManager = LinearLayoutManager(context)
}

fun RecyclerView.setLinearLayoutManagerWithDivider() {
    val linearLayoutManager = LinearLayoutManager(context)
    layoutManager = linearLayoutManager
    val dividerItemDecoration = DividerItemDecoration(context, linearLayoutManager.orientation)
    addItemDecoration(dividerItemDecoration)
}

fun RecyclerView.setGridLayoutManager(span: Int) {
    layoutManager = GridLayoutManager(context, span)
}

inline fun <reified T : Group> RecyclerView.setGroupieOnItemClickListener(crossinline onItemClickListener: (T, View) -> Unit) {
    val adapter = adapter
    if (adapter is GroupAdapter) {
        adapter.setOnItemClickListener { item, view ->
            if (item is T) onItemClickListener(item, view)
        }
    }
}

inline fun <reified T : Group> RecyclerView.setGroupieOnItemLongClickListener(crossinline onItemLongClickListener: (T, View) -> Boolean) {
    val adapter = adapter
    if (adapter is GroupAdapter) {
        adapter.setOnItemLongClickListener { item, view ->
            return@setOnItemLongClickListener if (item is T) onItemLongClickListener(item, view) else false
        }
    }
}
```

## EditText

```kotlin:EditTextExt.kt
fun EditText.setOnChangedTextListener(onChangedTextListener: (String) -> Unit) {
    addTextChangedListener(object : TextWatcher {
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            // do nothing
        }

        override fun afterTextChanged(s: Editable?) {
            // do nothing
        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            onChangedTextListener.invoke(s.toString())
        }
    })
}
```

## TextView

```kotlin:TextViewExt.kt
/**
 * 通貨を設定
 *
 * @param price Int
 * @param locale ロケール
 */
fun TextView.setTextCurrency(price: Int?, locale: Locale = Locale.JAPAN) {
    if (price != null) {
        try {
            text = NumberFormat.getCurrencyInstance(locale).format(price)
        } catch (e: Exception) {
            Log.e(className(), "通貨を表示できません", e)
        }
    }
}
```

## ImageView with Glide

```kotlin:ImageViewExt.kt
/**
 * 画像URLから画像を設定する
 *
 * @param imageUrl 画像URL
 */
fun ImageView.setImageUrl(imageUrl: String?) {
    if (imageUrl.isNullOrEmpty()) {
        return
    }
    Glide.with(context).load(imageUrl).into(this)
}

/**
 * 画像Fileから画像を設定する
 *
 * @param file 画像File
 */
fun ImageView.setImageFile(file: File?) {
    if (file == null) {
        return
    }
    Glide.with(context).load(file).into(this)
}

/**
 * 画像URIから画像を設定する
 *
 * @param uri 画像URI
 */
fun ImageView.setImageUri(uri: Uri?) {
    if (uri == null) {
        return
    }
    Glide.with(context).load(uri).into(this)
}
```

## Android Architecture Component

- Android Architecture Componentについては[こちら](https://developer.android.com/topic/libraries/architecture/)

### LiveData

```kotlin:LiveDataExt.kt
// region 監視(observer)

fun <T> LiveData<T>.observe(owner: LifecycleOwner, observer: (T) -> Unit) = observe(owner, Observer {
    if (it != null) observer.invoke(it)
})

fun <T> LiveData<T>.observeOrNull(owner: LifecycleOwner, observer: (T?) -> Unit) = observe(owner, Observer {
    observer.invoke(it)
})

// endregion

// region 変換(map)

fun <X, Y> LiveData<X>.map(func: (X) -> Y) = Transformations.map(this, func)

fun <X, Y> LiveData<X>.switchMap(func: (X) -> LiveData<Y>) = Transformations.switchMap(this, func)

// endregion

// region 初期(default)

fun <T, U : MutableLiveData<T>> U.default(value: T) = apply { setValue(value) }

// endregion

// region 取得(is/get)

inline val <reified T> LiveData<T>?.requireValue: T
    get() = this?.value ?: throw IllegalArgumentException("LiveData value is null.")

fun <T> LiveData<T>?.isNotNull(): Boolean = this?.value != null

fun LiveData<String>?.isNotEmpty(): Boolean = this?.value?.isNotEmpty() ?: false

fun LiveData<String>?.isEmpty(): Boolean = this?.value?.isEmpty() ?: false

fun LiveData<Boolean>?.isTrue(): Boolean = this?.value ?: false

fun LiveData<Boolean>?.isFalse(): Boolean = this?.value?.not() ?: false

// endregion
```

追記：
- バージョンアップでMutableLiveDataの初期値が設定できるようになったみたいです
`implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0-alpha01'`

### LiveData with RxKotlin

```kotlin:LiveDataExt.kt

// region bindTo

/**
 * RxをLiveDataにバインドする
 *
 * @param data データ
 * @return Disposable
 */
fun <T : Any> Flowable<T>.bindTo(data: MutableLiveData<T>): Disposable {
    return subscribeBy(onNext = { data.value = it }, onError = defaultError())
}

/**
 * RxをLiveDataにバインドする
 *
 * @param data データ
 * @return Disposable
 */
fun <T : Any> Observable<T>.bindTo(data: MutableLiveData<T>): Disposable {
    return subscribeBy(onNext = { data.value = it }, onError = defaultError())
}

/**
 * RxをLiveDataにバインドする
 *
 * @param data データ
 * @return Disposable
 */
fun <T : Any> Single<T>.bindTo(data: MutableLiveData<T>): Disposable {
    return subscribeBy(onSuccess = { data.value = it }, onError = defaultError())
}

/**
 * RxをLiveDataにバインドする
 *
 * @param data データ
 * @return Disposable
 */
fun <T : Any> Maybe<T>.bindTo(data: MutableLiveData<T>): Disposable {
    return subscribeBy(onSuccess = { data.value = it }, onError = defaultError())
}

// endregion

// region withProgress

/**
 * プログレス状態のフラグ切り替え
 *
 * @param progress MutableLiveData
 * @return Flowable
 */
@CheckResult
fun <T> Flowable<T>.withProgress(progress: MutableLiveData<Boolean>): Flowable<T> {
    return compose { item ->
        item
                .doOnSubscribe { progress.postValue(true) }
                .doFinally { progress.postValue(false) }
    }
}

/**
 * プログレス状態のフラグ切り替え
 *
 * @param progress MutableLiveData
 * @return Observable
 */
@CheckResult
fun <T> Observable<T>.withProgress(progress: MutableLiveData<Boolean>): Observable<T> {
    return compose { item ->
        item
                .doOnSubscribe { progress.postValue(true) }
                .doFinally { progress.postValue(false) }
    }
}

/**
 * プログレス状態のフラグ切り替え
 *
 * @param progress MutableLiveData
 * @return Observable
 */
@CheckResult
fun <T> Single<T>.withProgress(progress: MutableLiveData<Boolean>): Observable<T> {
    return toObservable().withProgress(progress)
}

/**
 * プログレス状態のフラグ切り替え
 *
 * @param progress MutableLiveData
 * @return Observable
 */
@CheckResult
fun <T> Maybe<T>.withProgress(progress: MutableLiveData<Boolean>): Observable<T> {
    return toObservable().withProgress(progress)
}

/**
 * プログレス状態のフラグ切り替え
 *
 * @param progress MutableLiveData
 * @return Observable
 */
@CheckResult
fun <T> Completable.withProgress(progress: MutableLiveData<Boolean>): Observable<T> {
    return toObservable<T>().withProgress(progress)
}

// endregion
```

## Date

```kotlin:DateExt.kt
/**
 * 日付フォーマット
 *
 * @param pattern パターン
 */
enum class DateFormat(val pattern: String) {
    YYYY_MM("yyyy/MM"),
    YYYY_MM_DD("yyyy/MM/dd"),
    YYYY_MM_DD_HH_MM_SS("yyyy/MM/dd HH:mm:ss")
}

/**
 * 日付をフォーマットする
 *
 * @param format DateFormat
 * @param locale ロケール
 */
fun Date.toString(format: DateFormat, locale: Locale = Locale.getDefault()): String {
    try {
        val sdf = SimpleDateFormat(format.pattern, locale)
        return sdf.format(this)
    } catch (e: Exception) {
        Log.e(className(), "日付変換エラー : $e")
    }
    return ""
}

/**
 * 文字列から日付に変換
 *
 * @param format 日付フォーマット
 * @return 日付
 */
fun String.toDate(format: DateFormat, locale: Locale = Locale.getDefault()): Date? {
    try {
        val sdf = SimpleDateFormat(format.pattern, locale)
        return sdf.parse(this)
    } catch (e: Exception) {
        Log.e(className(), "日付変換エラー : $e")
    }
    return null
}
```