Last active
July 17, 2024 14:35
-
-
Save khaledahmedelsayed/38c6d1caf4e0feae0073fd079da0ca63 to your computer and use it in GitHub Desktop.
Kotlin useful extensions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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