Skip to content

Instantly share code, notes, and snippets.

@khaledahmedelsayed
Last active July 17, 2024 14:35
Show Gist options
  • Save khaledahmedelsayed/38c6d1caf4e0feae0073fd079da0ca63 to your computer and use it in GitHub Desktop.
Save khaledahmedelsayed/38c6d1caf4e0feae0073fd079da0ca63 to your computer and use it in GitHub Desktop.
Kotlin useful extensions
/**
* Kotlin extensions you can use in any project that solves common problems in Android
*/
fun Checkable.setOnCheckableSafeClick(view: View, listener: () -> Unit) {
view.setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_UP)
listener()
true
}
}
// Use the 2 below functions together to differentiate between user input and text changed programmatically, Note that, this trick can't be done with debounce because of its timeout
fun EditText.setTextProgrammatically(text: String?) {
tag = text
setText(text)
tag = null
}
fun EditText.doAfterTextChangedByUser(lambda: (text : String?) -> Unit): TextWatcher {
val textWatcher = object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
if (tag == null) // Changed by user
lambda(text.toString())
else {
// Changed programmatically
}
}
}
addTextChangedListener(textWatcher)
return textWatcher
}
fun NestedScrollView.scrollToBottom() {
postDelayed({ fullScroll(View.FOCUS_DOWN) }, 100)
}
fun View.setOnClickListenerSafely(timeBetweenClicksInMillis : Int = 300, block: () -> Unit) {
var lastClickTime = 0L
setOnClickListener {
if (SystemClock.elapsedRealtime() - lastClickTime < timeBetweenClicksInMillis) {
return@setOnClickListener
}
lastClickTime = SystemClock.elapsedRealtime()
block()
}
}
fun FragmentStateAdapter.getItem(position: Int): Fragment? {
return this::class.superclasses.find { it == FragmentStateAdapter::class }
?.java?.getDeclaredField("mFragments")
?.let { field ->
field.isAccessible = true
val mFragments = field.get(this) as LongSparseArray<Fragment>
return@let mFragments[getItemId(position)]
}
}
fun EditText.doAfterTextChangedWithDebounce(coroutineContext: CoroutineContext = Dispatchers.Main, debounceInMillis : Long = 300, lambda: (text : String) -> Unit): TextWatcher {
var debounceJob: Job? = null
val textWatcher = object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
debounceJob?.cancel()
debounceJob = CoroutineScope(coroutineContext).launch {
delay(debounceInMillis)
s?.let {
lambda(it.toString())
}
}
}
}
addTextChangedListener(textWatcher)
return textWatcher
}
inline fun <reified T> T.deepCopy(): T {
val jsonString = Gson().toJson(this)
return Gson().fromJson(jsonString, object : TypeToken<T>() {}.type)
}
fun Context.getFileNameFromUri(uri: Uri): String {
val projection = arrayOf(MediaStore.Images.Media.DISPLAY_NAME)
val cursor = contentResolver.query(uri, projection, null, null, null)
cursor?.moveToFirst()
val name = cursor?.getString(0)
cursor?.close()
return name ?: System.currentTimeMillis().toString()
}
fun Context.makeFileFromUri(uri: Uri): File {
val newFile = File(cacheDir, getFileNameFromUri(uri))
val inputStream = contentResolver.openInputStream(uri)
val outputStream = FileOutputStream(newFile)
inputStream?.copyTo(outputStream)
inputStream?.close()
return newFile
}
fun Context.getUriFromFile(file: File): Uri {
return FileProvider.getUriForFile(
this,
"$packageName.provider",
file
)
}
fun <DialogBinding : ViewBinding> Context.showAlertDialogFromBinding(
inflatedViewBinding: DialogBinding,
onBuild: (AlertDialog, DialogBinding) -> Unit
) {
AlertDialog.Builder(this).run {
setView(inflatedViewBinding.root)
show()
}.also { dialog ->
// To display rounded corners
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
onBuild(dialog, inflatedViewBinding)
}
}
fun <BottomSheetBinding : ViewBinding> Context.showBottomSheetFromBinding(
inflatedViewBinding: BottomSheetBinding,
onBuild: (BottomSheetDialog, BottomSheetBinding) -> Unit
) {
BottomSheetDialog(this, R.style.BottomSheetStyle).run {
setContentView(inflatedViewBinding.root)
show()
window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
behavior.state = BottomSheetBehavior.STATE_EXPANDED
onBuild(this, inflatedViewBinding)
}
}
fun Bitmap.convertToByteArray(): ByteArray {
val stream = ByteArrayOutputStream()
compress(Bitmap.CompressFormat.PNG, 90, stream)
return stream.toByteArray()
}
suspend fun getByteArrayFromUrl(urlString: String?): ByteArray {
return withContext(Dispatchers.IO) {
val url = URL(urlString)
val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
connection.requestMethod = "GET"
val inputStream: InputStream = connection.inputStream
val buffer = ByteArray(1024)
val byteArrayOutputStream = ByteArrayOutputStream()
var bytesRead: Int
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
byteArrayOutputStream.write(buffer, 0, bytesRead)
}
return@withContext byteArrayOutputStream.toByteArray()
}
}
fun Context.byteArrayToFile(byteArray: ByteArray?): File {
val file = File(cacheDir, System.currentTimeMillis().toString())
val fileOutputStream = FileOutputStream(file)
fileOutputStream.write(byteArray)
fileOutputStream.close()
return file
}
fun Context.getFileSizeInMB(uri: Uri): Double {
var inputStream: InputStream? = null
try {
inputStream = contentResolver.openInputStream(uri)
val fileSizeInBytes = inputStream?.available()?.toLong() ?: 0
return fileSizeInBytes.toDouble() / (1024 * 1024)
} finally {
inputStream?.close()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment