Koi - A lightweight Kotlin library for Android
Koi include many useful extensions and functions, they can help reducing the boilerplate code in Android applications. Specifically, Koi include a powerful extension function named asyncSafe.
Gradle
Latest Version: Compiled with Kotlin 1.1.4.
compile 'com.mcxiaoke.koi:core:0.5.5' // useful extensions (only ~100k)
compile 'com.mcxiaoke.koi:async:0.5.5' // async functions (only ~70k)
Context Extensions
Activity Functions
// available for Activity
fun activityExtensions() {
val act = getActivity() // Activity
act.restart() // restart Activity
val app = act.getApp() // Application
val app2 = act.application // Application
// Activity.find()
// Fragment.find()
// View.find()
val textView = act.find<TextView>(android.R.id.text1)
}
Fragment Functions
// available for Fragment
fun fragmentExtensions() {
val act = activity // Activity
val app = getApp() // Application
val textView = find<TextView>(android.R.id.text1) // view.findViewById
val imageView = find<TextView>(android.R.id.icon1) // view.findViewById
}
Easy to use Toast
// available for Context
fun toastExtensions() {
// available in Activity/Fragment/Service/Context
toast(R.string.app_name)
toast("this is a toast")
longToast(R.string.app_name)
longToast("this is a long toast")
}
Easy to Inflate Layout
// available for Context
fun inflateLayout() {
val view1 = inflate(R.layout.activity_main)
val viewGroup = view1 as ViewGroup
val view2 = inflate(android.R.layout.activity_list_item, viewGroup, false)
}
Useful Functions
// available for Context
fun miscExtensions() {
val hasCamera = hasCamera()
mediaScan(Uri.parse("file:///sdcard/Pictures/koi/cat.png"))
addToMediaStore(File("/sdcard/Pictures/koi/cat.png"))
val batteryStatusIntent = getBatteryStatus()
val colorValue = getResourceValue(android.R.color.darker_gray)
}
Easy to create Intent
// available for Context
fun intentExtensions() {
val extras = Bundle { putString("key", "value") }
val intent1 = newIntent<MainActivity>()
val intent2 = newIntent<MainActivity>(Intent.FLAG_ACTIVITY_NEW_TASK, extras)
}
Easy to Start Activity
// available for Activity
fun startActivityExtensions() {
startActivity<MainActivity>()
// equal to
startActivity(Intent(this, MainActivity::class.java))
startActivity<MainActivity>(Intent.FLAG_ACTIVITY_SINGLE_TOP, Bundle())
startActivity<MainActivity>(Bundle())
startActivityForResult<MainActivity>(100)
startActivityForResult<MainActivity>(Bundle(), 100)
startActivityForResult<MainActivity>(200, Intent.FLAG_ACTIVITY_CLEAR_TOP)
}
Easy to Start Service
// available for Context
fun startServiceExtensions() {
startService<BackgroundService>()
startService<BackgroundService>(Bundle())
}
Network State
// available for Context
fun networkExtensions() {
val name = networkTypeName()
val operator = networkOperator()
val type = networkType()
val wifi = isWifi()
val mobile = isMobile()
val connected = isConnected()
}
Notification Builder
// available for Context
fun notificationExtensions() {
// easy way using Notification.Builder
val notification = newNotification() {
this.setColor(0x0099cc)
.setAutoCancel(true)
.setContentTitle("Notification Title")
.setContentText("Notification Message Text")
.setDefaults(0)
.setGroup("koi")
.setVibrate(longArrayOf(1, 0, 0, 1))
.setSubText("this is a sub title")
.setSmallIcon(android.R.drawable.ic_dialog_info)
.setLargeIcon(null)
}
}
Package Functions
// available for Context
fun packageExtensions() {
val isYoutubeInstalled = isAppInstalled("com.google.android.youtube")
val isMainProcess = isMainProcess()
val disabled = isComponentDisabled(MainActivity::class.java)
enableComponent(MainActivity::class.java)
val sig = getPackageSignature()
val sigString = getSignature()
println(dumpSignature())
}
System Service
// available for Context
// easy way to get system service, no cast
fun systemServices() {
val wm = getWindowService()
val tm = getTelephonyManager()
val nm = getNotificationManager()
val cm = getConnectivityManager()
val am = getAccountManager()
val acm = getActivityManager()
val alm = getAlarmManager()
val imm = getInputMethodManager()
val inflater = getLayoutService()
val lm = getLocationManager()
val wifi = getWifiManager()
}
Easy to Log
// available for Context
fun logExtensions() {
KoiConfig.logEnabled = true //default is false
// true == Log.VERBOSE
// false == Log.ASSERT
// optional
KoiConfig.logLevel = Log.VERBOSE // default is Log.ASSERT
//
logv("log functions available in Context") //Log.v
logd("log functions available in Context") //Log.d
loge("log functions available in Context") //Log.e
// support lazy evaluated message
logv { "lazy eval message lambda" } //Log.v
logw { "lazy eval message lambda" } //Log.w
}
View Extensions
View Listeners 1
fun viewListeners1() {
val view = View(this)
// View.OnClickListener
view.onClick { print("view clicked") }
// View.OnLongClickListener
view.onLongClick { print("view long clicked");false }
// View.OnKeyListener
view.onKeyEvent { view, keyCode, event ->
print("keyEvent: action:${event.action} code:$keyCode")
false
}
// View.OnTouchListener
view.onTouchEvent { view, event ->
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> print("touch down")
MotionEvent.ACTION_UP -> print("touch up")
MotionEvent.ACTION_MOVE -> print("touch move")
}
false
}
// View.OnFocusChangeListener
view.onFocusChange { view, hasFocus ->
print("focus changed = $hasFocus")
}
}
View Listeners 2
fun viewListeners2() {
// TextWatcher
val editText = EditText(this)
editText.onTextChange { text, start, before, count ->
print("text changed: $text")
}
// OnCheckedChangeListener
val checkBox = CheckBox(this)
checkBox.onCheckedChanged { button, isChecked ->
print("CheckBox value changed:$isChecked")
}
// OnSeekBarChangeListener
val seekBar = SeekBar(this)
seekBar.onProgressChanged { seekBar, progress, fromUser ->
print("seekBar progress: $progress")
}
}
ListView Listeners
fun listViewListeners() {
val listView = ListView(this)
// OnItemClickListener
listView.onItemClick { parent, view, position, id ->
print("onItemClick: position=$position")
}
// OnScrollListener
listView.onScrollChanged { view, scrollState ->
print("scroll state changed")
}
}
View Utils
// available for View
fun viewSample() {
val w = dm.widthPixels
val h = dm.heightPixels
val v1 = 32.5f
val dp1 = v1.pxToDp()
val v2 = 24f
val px1 = v2.dpToPx()
val dp2 = pxToDp(56)
val px2 = dpToPx(32)
val dp3 = 72.pxToDp()
val px3 = 48.dpToPx()
hideSoftKeyboard()
val editText = EditText(context)
editText.showSoftKeyboard()
editText.toggleSoftKeyboard()
}
Adapter Extensions
Easy to Create Adapter
// easy way to create array adapter
fun adapterFunctions() {
listView.adapter = quickAdapterOf(
android.R.layout.simple_list_item_2,
(1..100).map { "List Item No.$it" })
{ binder, data ->
binder.setText(android.R.id.text1, data)
binder.setText(android.R.id.text2, "Index: ${binder.position}")
}
val adapter2 = quickAdapterOf<String>(android.R.layout.simple_list_item_1) {
binder, data ->
binder.setText(android.R.id.text1, data)
}
adapter2.addAll(listOf("Cat", "Dog", "Rabbit"))
val adapter3 = quickAdapterOf<Int>(android.R.layout.simple_list_item_1,
arrayOf(1, 2, 3, 4, 5, 6)) {
binder, data ->
binder.setText(android.R.id.text1, "Item Number: $data")
}
val adapter4 = quickAdapterOf<Int>(android.R.layout.simple_list_item_1,
setOf(22, 33, 4, 5, 6, 8, 8, 8)) {
binder, data ->
binder.setText(android.R.id.text1, "Item Number: $data")
}
}
Bundle Extensions
Bundle Builder
// available in any where
fun bundleExtension() {
// easy way to create bundle
val bundle = Bundle {
putString("key", "value")
putInt("int", 12345)
putBoolean("boolean", false)
putIntArray("intArray", intArrayOf(1, 2, 3, 4, 5))
putStringArrayList("strings", arrayListOf("Hello", "World", "Cat"))
}
// equal to using with
val bundle2 = Bundle()
with(bundle2) {
putString("key", "value")
putInt("int", 12345)
putBoolean("boolean", false)
putIntArray("intArray", intArrayOf(1, 2, 3, 4, 5))
putStringArrayList("strings", arrayListOf("Hello", "World", "Cat"))
}
}
Parcelable Extensions
Easy to create Parcelable
// easy way to create Android Parcelable class
data class Person(val name: String, val age: Int) : Parcelable {
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(name)
dest.writeInt(age)
}
override fun describeContents(): Int = 0
protected constructor(p: Parcel) : this(name = p.readString(), age = p.readInt()) {}
companion object {
// using createParcel
@JvmField val CREATOR = createParcel { Person(it) }
}
}
Collection Extensions
Collection to String
fun collectionToString() {
val pets = listOf<String>("Cat", "Dog", "Rabbit", "Fish")
// list to string, delimiter is space
val string1 = pets.asString(delim = " ") // "Cat Dog Rabbit Fish"
// default delimiter is comma
val string2 = pets.asString() // "Cat,Dog,Rabbit,Fish"
val numbers = arrayOf(2016, 2, 2, 20, 57, 40)
// array to string, default delimiter is comma
val string3 = numbers.asString() // "2016,2,2,20,57,40"
// array to string, delimiter is -
val string4 = numbers.asString(delim = "-") // 2016-2-2-20-57-40
// using Kotlin stdlib
val s1 = pets.joinToString()
val s2 = numbers.joinToString(separator = "-", prefix = "<", postfix = ">")
}
Map to String
fun mapToString() {
val map = mapOf<String, Int>(
"John" to 30,
"Smith" to 50,
"Alice" to 22
)
// default delimiter is ,
val string1 = map.asString() // "John=30,Smith=50,Alice=22"
// using delimiter /
val string2 = map.asString(delim = "/") // "John=30/Smith=50/Alice=22"
// using stdlib
map.asSequence().joinToString { "${it.key}=${it.value}" }
}
List Append
fun appendAndPrepend() {
val numbers = (1..6).toArrayList()
println(numbers.joinToString()) // "1, 2, 3, 4, 5, 6, 7"
numbers.head() // .dropLast(1)
numbers.tail() //.drop(1)
val numbers2 = 100.appendTo(numbers) //
val numbers3 = 2016.prependTo(numbers)
}
Database Extensions
Easy to get Cursor Value
// available for Cursor
fun cursorValueExtensions() {
val cursor = this.writableDatabase.query("table", null, null, null, null, null, null)
cursor.moveToFirst()
do {
val intVal = cursor.intValue("column-a")
val stringVal = cursor.stringValue("column-b")
val longVal = cursor.longValue("column-c")
val booleanValue = cursor.booleanValue("column-d")
val doubleValue = cursor.doubleValue("column-e")
val floatValue = cursor.floatValue("column-f")
// no need to do like this, so verbose
cursor.getInt(cursor.getColumnIndexOrThrow("column-a"))
cursor.getString(cursor.getColumnIndexOrThrow("column-b"))
} while (cursor.moveToNext())
}
Easy to convert Cursor to Model
// available for Cursor
// transform cursor to model object
fun cursorToModels() {
val where = " age>? "
val whereArgs = arrayOf("20")
val cursor = this.readableDatabase.query("users", null, where, whereArgs, null, null, null)
val users1 = cursor.map {
UserInfo(
stringValue("name"),
intValue("age"),
stringValue("bio"),
booleanValue("pet_flag"))
}
// or using mapAndClose
val users2 = cursor.mapAndClose {
UserInfo(
stringValue("name"),
intValue("age"),
stringValue("bio"),
booleanValue("pet_flag"))
}
// or using Cursor?mapTo(collection, transform())
}
Easy to use Transaction
// available for SQLiteDatabase and SQLiteOpenHelper
// auto apply transaction to db operations
fun inTransaction() {
val db = this.writableDatabase
val values = ContentValues()
// or db.transaction
transaction {
db.execSQL("insert into users (?,?,?) (1,2,3)")
db.insert("users", null, values)
}
// equal to
db.beginTransaction()
try {
db.execSQL("insert into users (?,?,?) (1,2,3)")
db.insert("users", null, values)
db.setTransactionSuccessful()
} finally {
db.endTransaction()
}
}
IO Extensions
Easy to close Stream
// available for Closeable
fun closeableSample() {
val input = FileInputStream(File("readme.txt"))
try {
val string = input.readString("UTF-8")
} catch(e: IOException) {
e.printStackTrace()
} finally {
input.closeQuietly()
}
}
Stream doSafe Function
// simple way, equal to closeableSample
// InputStream.doSafe{}
fun doSafeSample() {
val input = FileInputStream(File("readme.txt"))
input.doSafe {
val string = readString("UTF-8")
}
}
readString/readList
// available for InputStream/Reader
fun readStringAndList1() {
val input = FileInputStream(File("readme.txt"))
try {
val reader = input.reader(Encoding.CHARSET_UTF_8)
val string1 = input.readString(Encoding.UTF_8)
val string2 = input.readString(Encoding.CHARSET_UTF_8)
val list1 = input.readList()
val list2 = input.readList(Encoding.CHARSET_UTF_8)
} catch(e: IOException) {
} finally {
input.closeQuietly()
}
}
readString/readList using doSafe
// available for InputStream/Reader
//equal to readStringAndList1
fun readStringAndList2() {
val input = FileInputStream(File("readme.txt"))
input.doSafe {
val reader = reader(Encoding.CHARSET_UTF_8)
val string1 = readString(Encoding.UTF_8)
val string2 = readString(Encoding.CHARSET_UTF_8)
val list1 = readList()
val list2 = readList(Encoding.CHARSET_UTF_8)
}
}
writeString/writeList using doSafe
fun writeStringAndList() {
val output = FileOutputStream("output.txt")
output.doSafe {
output.writeString("hello, world")
output.writeString("Alic's Adventures in Wonderland", charset = Encoding.CHARSET_UTF_8)
val list1 = listOf<Int>(1, 2, 3, 4, 5)
val list2 = (1..8).map { "Item No.$it" }
output.writeList(list1, charset = Encoding.CHARSET_UTF_8)
output.writeList(list2)
}
}
File Read and Write
fun fileReadWrite() {
val directory = File("/Users/koi/workspace")
val file = File("some.txt")
val text1 = file.readText()
val text2 = file.readString(Encoding.CHARSET_UTF_8)
val list1 = file.readList()
val list2 = file.readLines(Encoding.CHARSET_UTF_8)
file.writeText("hello, world")
file.writeList(list1)
file.writeList(list2, Encoding.CHARSET_UTF_8)
val v1 = file.relativeToOrNull(directory)
val v2 = file.toRelativeString(directory)
// clean files in directory
directory.clean()
val file1 = File("a.txt")
val file2 = File("b.txt")
file1.copyTo(file2, overwrite = false)
}
Handler Extensions
Easy to use Handler
// available for Handler
// short name for functions
fun handlerFunctions() {
val handler = Handler()
handler.atNow { print("perform action now") }
// equal to
handler.post { print("perform action now") }
handler.atFront { print("perform action at first") }
// equal to
handler.postAtFrontOfQueue { print("perform action at first") }
handler.atTime(timestamp() + 5000, { print("perform action after 5s") })
// equal to
handler.postAtTime({ print("perform action after 5s") }, 5000)
handler.delayed(3000, { print("perform action after 5s") })
// equal to
handler.postDelayed({ print("perform action after 5s") }, 3000)
}
Other Extensions
Date Functions
// available in any where
fun dateSample() {
val nowString = dateNow()
val date = dateParse("2016-02-02 20:30:45")
val dateStr1 = date.asString()
val dateStr2 = date.asString(SimpleDateFormat("yyyyMMdd.HHmmss"))
val dateStr3 = date.asString("yyyy-MM-dd-HH-mm-ss")
// easy way to get timestamp
val timestamp1 = timestamp()
// equal to
val timestamp2 = System.currentTimeMillis()
val dateStr4 = timestamp1.asDateString()
}
Number Functions
fun numberExtensions() {
val number = 179325344324902187L
println(number.readableByteCount())
val bytes = byteArrayOf(1, 7, 0, 8, 9, 4, 125)
println(bytes.hexString())
}
String Functions
// available for String
fun stringExtensions() {
val string = "hello, little cat!"
val quotedString = string.quote()
val isBlank = string.isBlank()
val hexBytes = string.toHexBytes()
val s1 = string.trimAllWhitespace()
val c = string.containsWhitespace()
val url = "https://github.com/mcxiaoke/kotlin-koi?year=2016&encoding=utf8&a=b#changelog"
val urlNoQuery = url.withoutQuery()
val isNameSafe = url.isNameSafe()
val fileName = url.toSafeFileName()
val queries = url.toQueries()
val path = "/Users/koi/workspace/String.kt"
val baseName = path.fileNameWithoutExtension()
val extension = path.fileExtension()
val name = path.fileName()
}
Crypto Functions
// available in any where
fun cryptoFunctions() {
val md5 = HASH.md5("hello, world")
val sha1 = HASH.sha1("hello, world")
val sha256 = HASH.sha256("hello, world")
}
Check API Level
// available in any where
fun apiLevelFunctions() {
// Build.VERSION.SDK_INT
val v = currentVersion()
val ics = icsOrNewer()
val kk = kitkatOrNewer()
val bkk = beforeKitkat()
val lol = lollipopOrNewer()
val mar = marshmallowOrNewer()
}
Device Functions
// available in any where
fun deviceSample() {
val a = isLargeHeap
val b = noSdcard()
val c = noFreeSpace(needSize = 10 * 1024 * 1024L)
val d = freeSpace()
}
Preconditions
// available in any where
// null and empty check
fun preconditions() {
throwIfEmpty(listOf(), "collection is null or empty")
throwIfNull(null, "object is null")
throwIfTrue(currentVersion() == 10, "result is true")
throwIfFalse(currentVersion() < 4, "result is false")
}
Thread Functions
Create Thread Pool
// available in any where
fun executorFunctions() {
// global main handler
val uiHandler1 = CoreExecutor.mainHandler
// or using this function
val uiHandler2 = koiHandler()
// global executor service
val executor = CoreExecutor.executor
// or using this function
val executor2 = koiExecutor()
// create thread pool functions
val pool1 = newCachedThreadPool("cached")
val pool2 = newFixedThreadPool("fixed", 4)
val pool3 = newSingleThreadExecutor("single")
}
Main Thread Functions
// available in any where
fun mainThreadFunctions() {
//check current thread
// call from any where
val isMain = isMainThread()
// execute in main thread
mainThread {
print("${(1..8).asSequence().joinToString()}")
}
// delay execute in main thread
mainThreadDelay(3000) {
print("execute after 3000 ms")
}
}
Context Check
// isContextAlive function impl
fun <T> isContextAlive(context: T?): Boolean {
return when (context) {
null -> false
is Activity -> !context.isFinishing
is Fragment -> context.isAdded
is android.support.v4.app.Fragment -> context.isAdded
is Detachable -> !context.isDetached()
else -> true
}
}
Safe Functions
// available in any where
fun safeFunctions() {
val context = this
// check Activity/Fragment lifecycle
val alive = isContextAlive(context)
fun func1() {
print("func1")
}
// convert to safe function with context check
// internal using isContextAlive
val safeFun1 = safeFunction(::func1)
// call function with context check
// internal using isContextAlive
safeExecute(::func1)
// direct use
safeExecute { print("func1") }
}
Async Functions
class AsyncFunctionsSample {
private val intVal = 1000
private var strVal: String? = null
}
With Context Check 1
// async functions with context check
// internal using isContextAlive
// context alive:
// !Activity.isFinishing
// Fragment.isAdded
// !Detachable.isDetached
//
// available in any where
// using in Activity/Fragment better
fun asyncSafeFunction1() {
// safe means context alive check
// async
asyncSafe {
print("action executed only if context alive ")
// if you want get caller context
// maybe null
val ctx = getCtx()
// you can also using outside variables
// not recommended
// if context is Activity or Fragment
// may cause memory leak
print("outside value, $intVal $strVal")
// you can using mainThreadSafe here
// like a callback
mainThreadSafe {
// also with context alive check
// if context dead, not executed
print("code here executed in main thread")
}
// if you don't want context check, using mainThread{}
mainThread {
// no context check
print("code here executed in main thread")
}
}
// if your result or error is nullable
// using asyncSafe2, just as asyncSafe
// but type of result and error is T?, Throwable?
}
With Context Check 2
fun asyncSafeFunction2() {
// async with callback
asyncSafe(
{
print("action executed in async thread")
listOf<Int>(1, 2, 3, 4, 5)
},
{ result, error ->
// in main thread
print("callback executed in main thread")
})
}
fun asyncSafeFunction3() {
// async with success/failure callback
asyncSafe(
{
print("action executed in async thread")
"this string is result of the action"
// throw RuntimeException("action error")
},
{ result ->
// if action success with no exception
print("success callback in main thread result:$result")
},
{ error ->
// if action failed with exception
print("failure callback in main thread, error:$error")
})
}
Without Context Check
// if you don't want context check
// using asyncUnsafe series functions
// just replace asyncSafe with asyncUnsafe
fun asyncUnsafeFunctions() {
// async
asyncUnsafe {
print("action executed with no context check ")
// may cause memory leak
print("outside value, $intVal $strVal")
mainThread {
// no context check
print("code here executed in main thread")
}
}
}
Custom Executor
val executor = Executors.newFixedThreadPool(4)
asyncSafe(executor) {
print("action executed in async thread")
mainThreadSafe {
print("code here executed in main thread")
}
}
With Delay
// async functions with delay
// with context check
// if context died, not executed
// others just like asyncSafe
fun asyncDelayFunctions() {
// usage see asyncSafe
asyncDelay(5000) {
print("action executed after 5000ms only if context alive ")
// you can using mainThreadSafe here
// like a callback
mainThreadSafe {
// also with context alive check
// if context dead, not executed
print("code here executed in main thread")
}
// if you don't want context check, using mainThread{}
mainThread {
// no context check
print("code here executed in main thread")
}
}
}