Help us understand the problem. What is going on with this article?

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

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

Swift版の記事を見て、Kotlin版も欲しいなと思ったのがきっかけです。

クラス名の取得

  • ProGuardを使用していると、クラス名は難読化されるので注意しましょう。
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の表示切り替え

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の連打対策

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

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ライブラリについてはこちら
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

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

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

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についてはこちら

LiveData

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

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

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
}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away