Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Here I collect some of my most useful Kotlin extensions
inline fun SharedPreferences.edit(changes: SharedPreferences.Editor.() -> SharedPreferences.Editor) {
edit().changes().apply()
}
fun ImageView.tintSrc(@ColorRes colorRes: Int) {
val drawable = DrawableCompat.wrap(drawable)
DrawableCompat.setTint(drawable, ContextCompat.getColor(context, colorRes))
setImageDrawable(drawable)
if (drawable is TintAwareDrawable) invalidate() // Because in this case setImageDrawable will not call invalidate()
}
fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View {
return LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)
}
/**
* Compat version of setExactAndAllowWhileIdle()
*/
fun AlarmManager.setExactAndAllowWhileIdleCompat(alarmType: Int, timeMillis: Long, pendingIntent: PendingIntent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// This version added Doze
setExactAndAllowWhileIdle(alarmType, timeMillis, pendingIntent)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// This version changed set() to be inexact
setExact(alarmType, timeMillis, pendingIntent)
} else {
set(alarmType, timeMillis, pendingIntent)
}
}
/**
* Helps to set clickable part in text.
*
* Don't forget to set android:textColorLink="@color/link" (click selector) and
* android:textColorHighlight="@color/window_background" (background color while clicks)
* in the TextView where you will use this.
*/
fun SpannableString.withClickableSpan(clickablePart: String, onClickListener: () -> Unit): SpannableString {
val clickableSpan = object : ClickableSpan() {
override fun onClick(widget: View?) = onClickListener.invoke()
}
val clickablePartStart = indexOf(clickablePart)
setSpan(clickableSpan,
clickablePartStart,
clickablePartStart + clickablePart.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return this
}
/**
* Helps to get Map, List, Set or other generic type from Json using Gson.
*/
inline fun <reified T: Any> Gson.fromJsonToGeneric(json: String): T {
val type = object : TypeToken<T>() {}.type
return fromJson(json, type)
}
// The way to pass server errors from custom error response to the Rx onError.
// BaseReply has field error: ApiError and ApiError extends Exception.
fun <T : BaseReply> Single<T>.apiErrorsToOnError(): Single<T> {
// .map the source and
return this.map { reply ->
// throw if reply with error or
reply.error?.let { throw it }
// return if not
reply
}
}
// Adds polling to the request. BaseReply is general response type that has error field
fun <T : BaseReply> Single<T>.poll(delay: Long, errorConsumer: Consumer<Throwable>): Observable<T> = this
.repeatWhen { completed -> completed.delay(delay, TimeUnit.SECONDS) }
.doOnError { errorConsumer.accept(it) }
.retryWhen { errors -> errors.delay(delay, TimeUnit.SECONDS) }
.toObservable()
fun View.visible(visible: Boolean, useGone: Boolean = true) {
this.visibility = if (visible) View.VISIBLE else if (useGone) View.GONE else View.INVISIBLE
}
// Helps to set status bar color with api version check
fun Activity.setStatusBarColor(@ColorRes colorRes: Int): Unit {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.statusBarColor = ContextCompat.getColor(this, colorRes)
}
}
// Adds flags to make window fullscreen
fun Activity.setFullscreenLayoutFlags() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
}
}
// Adds window insets to the view while entire activity is fullscreen.
fun View.applyWindowInsets(applyTopInset: Boolean = true, applyOtherInsets: Boolean = true): Unit {
if (applyTopInset || applyOtherInsets) {
ViewCompat.setOnApplyWindowInsetsListener(
this,
{ view, insets ->
// Set padding for needed insets
view.setPadding(
if (applyOtherInsets) insets.systemWindowInsetLeft else view.paddingLeft,
if (applyTopInset) insets.systemWindowInsetTop else view.paddingTop,
if (applyOtherInsets) insets.systemWindowInsetRight else view.paddingRight,
if (applyOtherInsets) insets.systemWindowInsetBottom else view.paddingBottom
)
// Return without consumed insets
insets.replaceSystemWindowInsets(
if (applyOtherInsets) 0 else insets.systemWindowInsetLeft,
if (applyTopInset) 0 else insets.systemWindowInsetTop,
if (applyOtherInsets) 0 else insets.systemWindowInsetRight,
if (applyOtherInsets) 0 else insets.systemWindowInsetBottom
)
})
} else {
// Listener is not needed
ViewCompat.setOnApplyWindowInsetsListener(this, null)
}
}
fun Activity.getScreenHeight(): Int {
val size = Point()
windowManager.defaultDisplay.getSize(size)
return size.y
}
fun String.onlyDigits(): String = replace(Regex("\\D*"), "")
fun View.showKeyboard(show: Boolean) {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
if (show) {
if (requestFocus()) imm.showSoftInput(this, 0)
} else {
imm.hideSoftInputFromWindow(windowToken, 0)
}
}
/**
* Loads image with Glide into the [ImageView].
*
* @param url url to load
* @param previousUrl url that already loaded in this target. Needed to prevent white flickering.
* @param round if set, the image will be round.
* @param cornersRadius the corner radius to set. Only used if [round] is `false`(by default).
* @param crop if set to `true` then [CenterCrop] will be used. Default is `false` so [FitCenter] is used.
*/
@SuppressLint("CheckResult")
fun ImageView.load(
url: String,
previousUrl: String? = null,
round: Boolean = false,
cornersRadius: Int = 0,
crop: Boolean = false
) {
val requestOptions = when {
round -> RequestOptions.circleCropTransform()
cornersRadius > 0 -> {
RequestOptions().transforms(
if (crop) CenterCrop() else FitCenter(),
RoundedCorners(cornersRadius)
)
}
else -> null
}
Glide
.with(context)
.load(url)
.let {
// Apply request options
if (requestOptions != null) {
it.apply(requestOptions)
} else {
it
}
}
.let {
// Workaround for the white flickering.
// See https://github.com/bumptech/glide/issues/527
// Thumbnail changes must be the same to catch the memory cache.
if (previousUrl != null) {
it.thumbnail(
Glide
.with(context)
.load(previousUrl)
.let {
// Apply request options
if (requestOptions != null) {
it.apply(requestOptions)
} else {
it
}
}
)
} else {
it
}
}
.into(this)
}
inline fun <reified C : Collection<T>, reified T : Any> Moshi.collectionAdapter(): JsonAdapter<C> {
val parametrizedType = Types.newParameterizedType(C::class.java, T::class.java)
return this.adapter<C>(parametrizedType)
}
@Tgo1014
Copy link

Tgo1014 commented Jun 12, 2019

Thank you for the withClickableSpan extension.

@Gperez88
Copy link

Gperez88 commented Jun 26, 2019

Thank you

I have a improvement for the withClickableSpan extension, basically if you want search by multiple values.

fun SpannableString.withClickableSpan(pattern: Pattern, onClickListener: (String) -> Unit): SpannableString {

val matcher: Matcher = pattern.matcher(this)

while (matcher.find()) {

    val tag: String = matcher.group(0)
    val clickableSpan = object : ClickableSpan() {
        override fun onClick(widget: View?) = onClickListener.invoke(tag)
    }

    setSpan(clickableSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}

return this

}

@shiveshmehta09
Copy link

shiveshmehta09 commented Jul 11, 2019

Even you can use this..

fun TextView.makeLinks(vararg links: Pair<String, View.OnClickListener>) {
    val spannableString = SpannableString(this.text)
    for (link in links) {
        val clickableSpan = object : ClickableSpan() {
            override fun onClick(view: View) {
                Selection.setSelection((view as TextView).text as Spannable, 0)
                view.invalidate()
                link.second.onClick(view)
            }
        }
        val startIndexOfLink = this.text.toString().indexOf(link.first)
        spannableString.setSpan(
            clickableSpan, startIndexOfLink, startIndexOfLink + link.first.length,
            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
        )
    }
    this.movementMethod =
        LinkMovementMethod.getInstance() // without LinkMovementMethod, link can not click
    this.setText(spannableString, TextView.BufferType.SPANNABLE)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment