Last active
March 17, 2023 08:31
-
-
Save virendersran01/76c27ea5dabee803bb87e18dacf04e1e to your computer and use it in GitHub Desktop.
Kotlin Extension Functions
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
/* | |
finds and colors all urls contained in a string | |
@param linkColor color for the url default is blue | |
@param linkClickAction action to perform when user click that link | |
*/ | |
fun String.linkify(linkColor:Int = Color.BLUE,linkClickAction:((link:String) -> Unit)? = null): SpannableStringBuilder { | |
val builder = SpannableStringBuilder(this) | |
val matcher = Patterns.WEB_URL.matcher(this) | |
while(matcher.find()){ | |
val start = matcher.start() | |
val end = matcher.end() | |
builder.setSpan(ForegroundColorSpan(Color.BLUE),start,end,0) | |
val onClick = object : ClickableSpan(){ | |
override fun onClick(p0: View) { | |
linkClickAction?.invoke(matcher.group()) | |
} | |
} | |
//builder.setSpan(onClick,start,end,0) | |
} | |
return builder | |
} | |
// easily show a toast | |
fun Fragment.showToast(@StringRes msg: Int) { | |
Toast.makeText(requireActivity(), msg, Toast.LENGTH_LONG).show() | |
} | |
showToast(R.string.greeting) | |
fun Context.toast(msg: String, length: Int = Toast.LENGTH_SHORT) { | |
Toast.makeText(this, msg, length).show() | |
} | |
fun Fragment.toast(msg: String, length: Int = Toast.LENGTH_SHORT) { | |
requireContext().toast(msg, length) | |
} | |
///// | |
fun View.visible(animate: Boolean = true) { | |
if (animate) { | |
animate().alpha(1f).setDuration(300).setListener(object : AnimatorListenerAdapter() { | |
override fun onAnimationStart(animation: Animator) { | |
super.onAnimationStart(animation) | |
visibility = View.VISIBLE | |
} | |
}) | |
} else { | |
visibility = View.VISIBLE | |
} | |
} | |
myView.visible() | |
/////////// | |
// extension property to make menu invisible | |
var Menu.visibility: Boolean | |
get() = false | |
set(value) { | |
iterator().forEach { | |
it.isVisible = value | |
} | |
} | |
menu.visibility = boolea | |
//////// | |
Center title in a toolbar | |
fun Toolbar.centerTitle() { | |
doOnLayout { | |
children.forEach { | |
if (it is TextView) { | |
it.x = width / 2f - it.width / 2f | |
return@forEach | |
} | |
} | |
} | |
} | |
just say toolbar.centerTitle() that’s it | |
/** | |
* fades out a view making in completely invisible by default | |
* @param fadeDuration the duration of fade effect in milliseconds default value is 300ms | |
* @param endAlpha the alpha value that view will have after the animation completes default value is 0 | |
*/ | |
fun View.fadeOut(fadeDuration:Long = 300,endAlpha:Float = 0f){ | |
ValueAnimator.ofFloat(1f,endAlpha).apply { | |
duration = fadeDuration | |
addUpdateListener { | |
val animatedValue = it.animatedValue as Float | |
alpha = animatedValue | |
} | |
}.start() | |
} | |
myView.fadeOut() or pass your values if required | |
/** | |
* fades in a view | |
* @param fadeDuration the duration of fade effect in milliseconds default value is 300ms | |
*/ | |
fun View.fadeIn(fadeDuration:Long = 300){ | |
ValueAnimator.ofFloat(0f,1f).apply { | |
duration = fadeDuration | |
addUpdateListener { | |
val animatedValue = it.animatedValue as Float | |
alpha = animatedValue | |
} | |
}.start() | |
} | |
myView.fadeIn() | |
////////////////// | |
fun Fragment.getAsDrawable(id:Int) = ContextCompat.getDrawable(this.requireActivity(),id)!! | |
getAsDrawable(id) and it is not nullable also | |
fun Fragment.getAsColor(id:Int) = ContextCompat.getColor(this.requireActivity(),id)!! | |
getAsColor() | |
///////////////////////////////// | |
Encode and decode your strings to base64 in easy way | |
fun String.decode(): String { | |
return Base64.decode(this, Base64.DEFAULT).toString(Charsets.UTF_8) | |
} | |
fun String.encode(): String { | |
return Base64.encodeToString(this.toByteArray(Charsets.UTF_8), Base64.DEFAULT) | |
} | |
just say myString.encode() , myString.decode() | |
//////////////////////////// | |
/** | |
* Kotlin Extensions for simpler, easier way | |
* of launching of Activities | |
*/ | |
inline fun <reified T : Any> Activity.launchActivity( | |
requestCode: Int = -1, | |
options: Bundle? = null, | |
noinline init: Intent.() -> Unit = {} | |
) { | |
val intent = newIntent<T>(this) | |
intent.init() | |
startActivityForResult(intent, requestCode, options) | |
} | |
inline fun <reified T : Any> Context.launchActivity( | |
options: Bundle? = null, | |
noinline init: Intent.() -> Unit = {} | |
) { | |
val intent = newIntent<T>(this) | |
intent.init() | |
startActivity(intent, options) | |
} | |
inline fun <reified T : Any> newIntent(context: Context): Intent = | |
Intent(context, T::class.java) | |
/** | |
* Kotlin Extensions for simpler, easier way | |
* of launching of Activities | |
*/ | |
/** Set the View visibility to INVISIBLE */ | |
fun View.invisible() { | |
this.visibility = View.INVISIBLE | |
} | |
/** Set the View visibility to GONE*/ | |
fun View.gone() { | |
this.visibility = View.GONE | |
} | |
/** Set the View visibility to GONE*/ | |
fun View.visible() { | |
this.visibility = View.VISIBLE | |
} | |
fun Context.toast(msg: String) { | |
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() | |
} | |
fun View.hideKeyboard() { | |
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager | |
imm.hideSoftInputFromWindow(windowToken, 0) | |
} | |
fun View.showKeyboard() { | |
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager | |
imm.showSoftInput(this, 0) | |
} | |
fun EditText.onAction(action: Int, runAction: () -> Unit) { | |
this.setOnEditorActionListener { _, actionId, _ -> | |
return@setOnEditorActionListener when (actionId) { | |
action -> { | |
runAction.invoke() | |
true | |
} | |
else -> false | |
} | |
} | |
} | |
val Int.dp: Int | |
get() = (this / Resources.getSystem().displayMetrics.density).toInt() //fun Int.toDp(): Int = (this / Resources.getSystem().displayMetrics.density).toInt() | |
val Int.px: Int | |
get() = (this * Resources.getSystem().displayMetrics.density).toInt() //fun Int.toPx(): Int = (this * Resources.getSystem().displayMetrics.density).toInt() | |
class SpaceItemDecoration( | |
private val space: Int, | |
private val includeEdge: Boolean | |
) : RecyclerView.ItemDecoration() { | |
init { | |
require(space >= 0) { "Space between items can not be negative" } | |
} | |
override fun getItemOffsets( | |
outRect: Rect, | |
view: View, | |
parent: RecyclerView, | |
state: RecyclerView.State | |
) { | |
val itemCount = | |
parent.adapter?.itemCount ?: throw IllegalStateException("Adapter must not be null") | |
val itemPosition = parent.getChildAdapterPosition(view) | |
val layoutDirection = parent.layoutDirection | |
val sideSize = if (layoutDirection == LinearLayout.VERTICAL) view.width else view.height | |
val (spans, orientation) = with(parent.layoutManager!!) { | |
when (this) { | |
is GridLayoutManager -> Pair( | |
this.spanCount, | |
this.orientation | |
) // grid is itself a linear! | |
is LinearLayoutManager -> Pair(1, this.orientation) | |
else -> throw IllegalArgumentException("For now, only LinearLayout and GridLayout managers are supported") | |
} | |
} | |
val params = DecorationParams( | |
itemCount = itemCount, | |
itemPosition = itemPosition, | |
spanCount = spans, | |
itemSideSize = sideSize, | |
layoutDirection = layoutDirection, | |
orientation = orientation | |
) | |
setItemMargin(outRect, params, includeEdge) | |
} | |
private fun setItemMargin(outRect: Rect, params: DecorationParams, includeEdge: Boolean): Rect { | |
val (top, right, bottom, left) = LinearLayoutMarginFormula.apply { | |
updateItem(params, space, includeEdge) | |
}.calculate() | |
outRect.top = top | |
outRect.right = right | |
outRect.bottom = bottom | |
outRect.left = left | |
return outRect | |
} | |
internal data class DecorationParams( | |
val itemCount: Int, | |
val itemPosition: Int, | |
val spanCount: Int, | |
val itemSideSize: Int, | |
val layoutDirection: Int, | |
val orientation: Int, | |
) | |
// ----------------------------------------------------------------- | |
internal data class Margin(val top: Int, val right: Int, val bottom: Int, val left: Int) | |
private object LinearLayoutMarginFormula { | |
private fun findCorrectListVariation( | |
layoutDirection: Int, | |
orientation: Int | |
): LinearListVariation { | |
return listVariations[layoutDirection + (2 * orientation)] | |
} | |
private var includeEdge: Boolean = false | |
private var margin = 0 | |
private lateinit var params: DecorationParams | |
// LTR = 0, RTL = 1, Horizontal = 0, Vertical = 1 | |
private val listVariations = arrayOf( | |
HorizontalLTR(), | |
HorizontalRTL(), | |
VerticalLTR(), | |
VerticalRTL(), | |
) | |
fun updateItem(params: DecorationParams, margin: Int, includeEdge: Boolean) { | |
this.params = params | |
this.margin = margin | |
this.includeEdge = includeEdge | |
} | |
fun calculate(): Margin { | |
val borderMargin = if (includeEdge) margin else 0 | |
val numberOfMargins = params.spanCount + (if (includeEdge) 1 else -1) | |
val itemLayoutWidth = | |
params.itemSideSize - ((numberOfMargins * margin) / params.spanCount) | |
val top = topMargin(borderMargin) | |
val left = leftMargin(params.itemSideSize, itemLayoutWidth, borderMargin) | |
val right = rightMargin(params.itemSideSize, itemLayoutWidth, left) | |
val bottom = bottomMargin(borderMargin) | |
val list: LinearListVariation = findCorrectListVariation( | |
layoutDirection = params.layoutDirection, | |
orientation = params.orientation | |
) | |
return list.adaptVerticalLtrMargin(Margin(top, right, bottom, left)) | |
} | |
private fun leftMargin(itemWidth: Int, layoutWidth: Int, borderMargin: Int): Int { | |
fun spanIdx() = params.itemPosition % params.spanCount | |
return spanIdx() * (margin + layoutWidth - itemWidth) + borderMargin | |
} | |
fun topMargin(borderMargin: Int): Int { | |
fun isAtTop() = params.itemPosition < params.spanCount | |
return if (isAtTop()) borderMargin else (margin / 2) | |
} | |
fun rightMargin(itemWidth: Int, layoutWidth: Int, leftMargin: Int): Int { | |
return itemWidth - layoutWidth - leftMargin | |
} | |
fun bottomMargin(borderMargin: Int): Int { | |
fun isAtBottom() = row(params.itemPosition, params.spanCount) == | |
rows(params.itemCount, params.spanCount) | |
return if (isAtBottom()) borderMargin else (margin / 2) | |
} | |
private fun row(position: Int, spanCount: Int): Int { | |
return ceil((position + 1).toFloat() / spanCount.toFloat()).toInt() | |
} | |
private fun rows(itemCount: Int, spanCount: Int): Int { | |
return ceil(itemCount.toFloat() / spanCount.toFloat()).toInt() | |
} | |
interface LinearListVariation { | |
fun adaptVerticalLtrMargin(margin: Margin): Margin | |
} | |
class VerticalLTR : LinearListVariation { | |
override fun adaptVerticalLtrMargin(margin: Margin) = margin | |
} | |
class VerticalRTL : LinearListVariation { | |
override fun adaptVerticalLtrMargin(margin: Margin) = Margin( | |
top = margin.top, | |
right = margin.left, | |
bottom = margin.bottom, | |
left = margin.right | |
) | |
} | |
class HorizontalLTR : LinearListVariation { | |
override fun adaptVerticalLtrMargin(margin: Margin) = Margin( | |
top = margin.left, | |
right = margin.bottom, | |
bottom = margin.right, | |
left = margin.top | |
) | |
} | |
class HorizontalRTL : LinearListVariation { | |
override fun adaptVerticalLtrMargin(margin: Margin) = Margin( | |
top = margin.left, | |
right = margin.top, | |
bottom = margin.right, | |
left = margin.bottom | |
) | |
} | |
} | |
} | |
//BaseActivity with ViewBinding | |
abstract class BaseActivity<VB : ViewBinding>( | |
private val bindingInflater: (inflater: LayoutInflater) -> VB | |
) : AppCompatActivity() { | |
lateinit var binding: VB | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
binding = bindingInflater.invoke(layoutInflater) | |
setContentView(binding.root) | |
} | |
// override fun attachBaseContext(newBase: Context) { | |
// val localeToSwitchTo = when(UserPreference.getAppLanguage()){ | |
// AppLanguage.ENGLISH -> Constants.LANGUAGE_ENGLISH_CODE | |
// AppLanguage.FRENCH -> Constants.LANGUAGE_FRENCH_CODE | |
// } | |
// | |
// val localeUpdatedContext: ContextWrapper = ContextUtils.updateLocale(newBase, Locale(localeToSwitchTo)) | |
// super.attachBaseContext(localeUpdatedContext) | |
// } | |
} | |
//BaseFragment with ViewBinding | |
abstract class BaseFragment<VB : ViewBinding>( | |
private val bindingInflater: (inflater: LayoutInflater) -> VB | |
) : Fragment() { | |
private var _binding: VB? = null | |
val binding: VB | |
get() = _binding as VB | |
override fun onCreateView( | |
inflater: LayoutInflater, | |
container: ViewGroup?, | |
savedInstanceState: Bundle? | |
): View? { | |
_binding = bindingInflater.invoke(inflater) | |
if (_binding == null) | |
throw java.lang.IllegalArgumentException("Binding cannot be null") | |
return binding.root | |
} | |
override fun onDestroy() { | |
super.onDestroy() | |
_binding = null | |
} | |
} | |
fun isEmailValid(email: String): Boolean { | |
val regExpn = ("^(([\\w-]+\\.)+[\\w-]+|([a-zA-Z]|[\\w-]{2,}))@" | |
+ "((([0-1]?[0-9]{1,2}|25[0-5]|2[0-4][0-9])\\.([0-1]?" | |
+ "[0-9]{1,2}|25[0-5]|2[0-4][0-9])\\." | |
+ "([0-1]?[0-9]{1,2}|25[0-5]|2[0-4][0-9])\\.([0-1]?" | |
+ "[0-9]{1,2}|25[0-5]|2[0-4][0-9]))|" | |
+ "([a-zA-Z]+[\\w-]+\\.)+[a-zA-Z]{2,4})$") | |
val pattern = Pattern.compile(regExpn, Pattern.CASE_INSENSITIVE) | |
val matcher = pattern.matcher(email) | |
return matcher.matches() | |
} | |
fun isPhoneNumberValid(phoneNumber: String, countryCode: String): Boolean { | |
val phoneUtil = PhoneNumberUtil.getInstance() | |
try { | |
val numberProto = phoneUtil.parse(phoneNumber, countryCode) | |
return phoneUtil.isValidNumber(numberProto) | |
} catch (e: NumberParseException) { | |
System.err.println("NumberParseException was thrown: $e") | |
} | |
return false | |
} | |
fun generateRequestBody(text: String): RequestBody { | |
return text.toRequestBody("text/plain".toMediaTypeOrNull()) | |
} | |
fun getFileBody(keyName: String, uri: String): MultipartBody.Part? { | |
var body: MultipartBody.Part? = null | |
if (!TextUtils.isEmpty(uri)) { | |
val file = File(uri) | |
val requestFile = | |
file.asRequestBody("image/*".toMediaTypeOrNull()) | |
body = MultipartBody.Part.createFormData(keyName, file.name, requestFile) | |
} | |
return body | |
} | |
fun Context.progressAlertDialog(): AlertDialog { | |
val alertDialog: AlertDialog? | |
val builder = AlertDialog.Builder(this) | |
builder.setCancelable(false) | |
val binding = LayoutProgressBinding.inflate(LayoutInflater.from(this)) | |
builder.setView(binding.root) | |
alertDialog = builder.create() | |
alertDialog.setCanceledOnTouchOutside(false) | |
alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) | |
return alertDialog | |
} | |
val width: Int | |
get() = Resources.getSystem().displayMetrics.widthPixels | |
val height: Int | |
get() = Resources.getSystem().displayMetrics.heightPixels | |
fun EditText.setEmojiFilter() { | |
this.filters = arrayOf(emojiFilter) | |
} | |
private val emojiFilter = InputFilter { source, start, end, _, _, _ -> | |
for (index in start until end) { | |
when (Character.getType(source[index])) { | |
'*'.code, | |
Character.OTHER_SYMBOL.toInt(), | |
Character.SURROGATE.toInt() -> { | |
return@InputFilter "" | |
} | |
Character.LOWERCASE_LETTER.toInt() -> { | |
val index2 = index + 1 | |
if (index2 < end && Character.getType(source[index + 1]) == Character.NON_SPACING_MARK.toInt()) | |
return@InputFilter "" | |
} | |
Character.DECIMAL_DIGIT_NUMBER.toInt() -> { | |
val index2 = index + 1 | |
val index3 = index + 2 | |
if (index2 < end && index3 < end && | |
Character.getType(source[index2]) == Character.NON_SPACING_MARK.toInt() && | |
Character.getType(source[index3]) == Character.ENCLOSING_MARK.toInt() | |
) | |
return@InputFilter "" | |
} | |
Character.OTHER_PUNCTUATION.toInt() -> { | |
val index2 = index + 1 | |
if (index2 < end && Character.getType(source[index2]) == Character.NON_SPACING_MARK.toInt()) { | |
return@InputFilter "" | |
} | |
} | |
Character.MATH_SYMBOL.toInt() -> { | |
val index2 = index + 1 | |
if (index2 < end && Character.getType(source[index2]) == Character.NON_SPACING_MARK.toInt()) | |
return@InputFilter "" | |
} | |
} | |
} | |
return@InputFilter null | |
} | |
fun Context.isNetworkConnected(): Boolean { | |
val connectivityManager = | |
this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager | |
return when { | |
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> { | |
val nw = connectivityManager.activeNetwork ?: return false | |
val actNw = connectivityManager.getNetworkCapabilities(nw) ?: return false | |
when { | |
actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true | |
actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true | |
else -> false | |
} | |
} | |
else -> { | |
// Use depreciated methods only on older devices | |
val nwInfo = connectivityManager.activeNetworkInfo ?: return false | |
nwInfo.isConnected | |
} | |
} | |
} | |
//fun showSearchableSpinnerDialog( | |
// context: Context, | |
// title: String, | |
// itemList: List<NameIdModel>, | |
// onItemSelected: (data: NameIdModel) -> Unit | |
//) { | |
// var alertDialog: AlertDialog? = null | |
// val builder = MaterialAlertDialogBuilder(context) | |
// builder.setCancelable(false) | |
// val binding = LayoutSpinnerSearchableDialogBinding.inflate(LayoutInflater.from(context)) | |
// builder.setView(binding.root) | |
// binding.textHeader.text = title | |
// val adapter = SearchableSpinnerAdapter { | |
// binding.inputSearch.hideKeyboard() | |
// onItemSelected.invoke(it) | |
// alertDialog?.dismiss() | |
// } | |
// binding.recyclerView.adapter = adapter | |
// adapter.submitList(itemList) | |
// | |
// if (itemList.isEmpty()) { | |
// binding.textError.visible() | |
// } else { | |
// binding.textError.gone() | |
// } | |
// | |
// binding.inputSearch.requestFocus() | |
// binding.inputSearch.showKeyboard() | |
// | |
// binding.inputSearch.addTextChangedListener(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 (s.isNullOrEmpty()) { | |
// adapter.submitList(itemList) | |
// binding.textError.gone() | |
// } else { | |
// val filteredList = itemList.filter { it.name.contains(s, true) } | |
// if (filteredList.isEmpty()) { | |
// binding.textError.visible() | |
// } else { | |
// binding.textError.gone() | |
// } | |
// adapter.submitList(filteredList) | |
// } | |
// } | |
// | |
// }) | |
// | |
// binding.imageCancel.setOnClickListener { | |
// alertDialog?.dismiss() | |
// } | |
// | |
// alertDialog = builder.create() | |
// alertDialog.show() | |
// | |
//} | |
enum class AppLanguage { | |
ENGLISH, FRENCH | |
} | |
@RequiresApi(Build.VERSION_CODES.M) | |
fun isInternetConnected(context: Context): Boolean { | |
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager | |
val capabilities = cm.getNetworkCapabilities(cm.activeNetwork) | |
return capabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true | |
} | |
fun setAppLocale(context: Context, language: String) { | |
val locale = Locale(language) | |
Locale.setDefault(locale) | |
val config = context.resources.configuration | |
config.setLocale(locale) | |
context.createConfigurationContext(config) | |
context.resources.updateConfiguration(config, context.resources.displayMetrics) | |
} | |
///// | |
Repeating a string | |
Another very common operator extension function that not many people would know about. | |
operator fun String.times(n: Int): String { | |
val sb = StringBuilder() | |
repeat(n) { | |
sb.append(this) | |
} | |
return sb.toString() | |
} | |
String substring | |
I am sure you are aware of retrieving a character from a string using str[index]. Now, what if you require a substring but hate to type out that long function name? | |
operator fun String.get(range: IntRange) = | |
substring(range.first, range.last + 1) | |
/* | |
* val mainStr = "Interesting" | |
* val substr = mainStr[2..8] // "teresti" | |
*/ | |
Android resources | |
fun Context.drawable(@DrawableRes resId: Int) = | |
ResourcesCompat.getDrawable(resources, resId, null) | |
fun Context.font(@FontRes resId: Int) = | |
ResourcesCompat.getFont(this, resId) | |
fun Context.dimen(@DimenRes resId: Int) = | |
resources.getDimension(resId) | |
fun Context.anim(@AnimRes resId: Int) = | |
AnimationUtils.loadAnimation(this, resId) | |
///////////////// | |
Complex units | |
You need to set the font size, or box width programmatically but in terms of dp and sp? In Java, you’d rather use a function; however, in Kotlin, you can create an Extension value. | |
val Float.dp | |
get() = TypedValue.applyDimension( | |
TypedValue.COMPLEX_UNIT_DIP, | |
this, | |
Resources.getSystem().displayMetrics | |
) | |
val Float.sp | |
get() = TypedValue.applyDimension( | |
TypedValue.COMPLEX_UNIT_SP, | |
this, | |
Resources.getSystem().displayMetrics | |
) | |
val Int.dp get() = toFloat().dp | |
val Int.sp get() = toFloat().sp | |
/* | |
* use as 18.dp | |
* or 22.5f.sp | |
*/ | |
///////////////////////////////////////////////////////////// | |
/** show desired loader in any fragment | |
@param rootView , loader will go in the midpoint of root view | |
**/ | |
@SuppressLint("ResourceType") | |
fun Fragment.showLoader(rootView : ViewGroup) { | |
val loaderAnimation = LottieAnimationView(requireActivity()).apply { | |
id = 12345 | |
setAnimation(R.raw.loader) | |
repeatMode = LottieDrawable.INFINITE | |
loop(true) | |
layoutParams = ViewGroup.LayoutParams(50.toPx(), 50.toPx()) | |
playAnimation() | |
} | |
loaderAnimation.doOnLayout { | |
it.x = rootView.width/2f - it.width/2 | |
it.y = rootView.height/2f - it.height/2 | |
} | |
rootView.addView(loaderAnimation) | |
} | |
just say showLoader(yourlayout) loader will be centered in the layout | |
// pass same viewgroup that was paased in showLoader(ViewGroup) | |
@SuppressLint("ResourceType") | |
fun removeLoader(rootView: ViewGroup){ | |
val animationView = rootView.findViewById<LottieAnimationView>(12345) | |
rootView.removeView(animationView) | |
} | |
then just say removeLoader(yourlayout) to remove this loader from memory | |
///////////////////////////////////////////////////// | |
fun Fragment.enableTouch() { | |
requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) | |
} | |
fun Fragment.disableTouch() { | |
requireActivity().window.setFlags( | |
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, | |
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | |
) | |
} | |
///////////////////////////// | |
fun View.translate(from: Float, to: Float) { | |
with(ValueAnimator.ofFloat(from, to)) { | |
addUpdateListener { | |
val animatedValue = it.animatedValue as Float | |
translationY = animatedValue | |
} | |
start() | |
} | |
} | |
just say yourView.translate(from,to) will translate with animation. | |
fun View.animateScale(from: Int, to: Int, duration:Long = 1000) { | |
val valueAnimator = ValueAnimator.ofInt(from, to) | |
valueAnimator.duration = duration | |
valueAnimator.addUpdateListener { | |
val animatedValue = it.animatedValue as Int | |
scaleX = animatedValue.toFloat() | |
scaleY = animatedValue.toFloat() | |
} | |
valueAnimator.start() | |
} | |
just say yourView.animateScale(100,200) | |
/////////////////////////////////////////////////////////////////// | |
Do not hit Api for every character in a search view | |
you have an app with search feature you want to show results as the user is typing. | |
ex — user typing shoes, you are hitting api in ontextchanged() so api calls will be for s, sh , sho, shoe , shoes 5 apis calls in total not including when user mistypes and then press back spaces your apis call are ≥ number of letters in the search | |
Just hit it once by using below utility it will wait 1 second before hitting the api do not hit it for duplicate queries and do not hit for empty string find more details here | |
https://blog.mindorks.com/instant-search-using-kotlin-flow-operators | |
fun EditText.afterTextChangedFlow(): Flow<String> { | |
val query = MutableStateFlow("") | |
doOnTextChanged { text, start, before, count -> | |
query.value = text.toString() | |
} | |
return query | |
.debounce(1000) | |
.distinctUntilChanged() | |
.flowOn(Dispatchers.Main) | |
.filter { query.value.isNotBlank() } | |
} | |
//////////////////////////////////////// | |
fun Fragment.launchIO(block: suspend CoroutineScope.() -> Unit) { | |
lifecycleScope.launch(Dispatchers.IO) { | |
block.invoke(this) | |
} | |
} | |
fun Fragment.launchDefault(block: suspend CoroutineScope.() -> Unit) { | |
lifecycleScope.launch(Dispatchers.Default) { | |
block.invoke(this) | |
} | |
} | |
fun Fragment.launchMain(block: suspend CoroutineScope.() -> Unit) { | |
lifecycleScope.launch(Dispatchers.Main) { | |
block.invoke(this) | |
} | |
} | |
just say launchIO { // your code} , launchMain{ // your code} | |
///////////////////////////////////////////////////////////////////////// | |
fun Int.toPx(): Int = (this * Resources.getSystem().displayMetrics.density).toInt() | |
just day 5.toPx() that’s it | |
//////////////////////////////////////////////////////////////////////// | |
fun Fragment.hideSoftKeyboard() { | |
val imm = | |
requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager | |
imm.hideSoftInputFromWindow(view?.windowToken, 0) | |
} | |
hideSoftKeyboard() | |
////////////////////////////////////////////// | |
fun View.doOnDoubleClick(onDoubleClick: (View) -> Unit) { | |
val safeClickListener = DoubleClickListener { | |
onDoubleClick(it) | |
} | |
setOnClickListener(safeClickListener) | |
} | |
class DoubleClickListener( | |
private var defaultInterval: Int = 300, | |
private val onDoubleClick: (View) -> Unit | |
) : View.OnClickListener { | |
private var lastClickTime: Long = 0 | |
override fun onClick(v: View) { | |
val clickTime = System.currentTimeMillis() | |
if (clickTime - lastClickTime < defaultInterval) { | |
onDoubleClick(v) | |
lastClickTime = 0 | |
} | |
lastClickTime = clickTime | |
} | |
} | |
just say yourimageView.doOnDoubleClick{ } | |
///////////////////////////////////////////////////////////////// | |
inline fun View.OnDebouncedListener( | |
delayInClick: Long = 500L, | |
crossinline listener: (View) -> Unit | |
){ | |
val enabledAgain = Runnable { isEnabled = true } | |
setOnClickListener { | |
if (isEnabled){ | |
isEnabled = false | |
postDelayed(enabledAgain, delayInClick) | |
listener(it) | |
} | |
} | |
} //uses view.onDebouncedListener{ perform Any Action here } | |
/////////////////////////////////////////////////////////////////////////// | |
fun Intent?.toDebugString(): String { | |
val intent = this ?: return "" | |
return StringBuilder().apply { | |
appendLine("--- Intent ---") | |
appendLine("type: ${intent.type}") | |
appendLine("package: ${intent.`package`}") | |
appendLine("scheme: ${intent.scheme}") | |
appendLine("component: ${intent.component}") | |
appendLine("flags: ${intent.flags}") | |
appendLine("categories: ${intent.categories}") | |
appendLine("selector: ${intent.selector}") | |
appendLine("action: ${intent.action}") | |
appendLine("dataString: ${intent.dataString}") | |
intent.extras?.keySet()?.forEach { key -> | |
appendLine("* extra: $key=${intent.extras!![key]}") | |
} | |
}.toString() | |
} | |
Skip to content | |
About Me | |
Public Speaking, Writing & Videos | |
in Updates | |
Debugging Android Intents | |
With new behaviors for apps using targetSdk=33 (Android 13) regarding Intents, it may be necessary to dive in and figure out how to make things compatible. | |
In doing this myself, I needed to figure out what was in the Intent, so I could handle it appropriately. | |
I started with this StackOverflow post, but ended up adding more info and doing it cleanly in Kotlin. | |
fun Intent?.toDebugString(): String { | |
val intent = this ?: return "" | |
return StringBuilder().apply { | |
appendLine("--- Intent ---") | |
appendLine("type: ${intent.type}") | |
appendLine("package: ${intent.`package`}") | |
appendLine("scheme: ${intent.scheme}") | |
appendLine("component: ${intent.component}") | |
appendLine("flags: ${intent.flags}") | |
appendLine("categories: ${intent.categories}") | |
appendLine("selector: ${intent.selector}") | |
appendLine("action: ${intent.action}") | |
appendLine("dataString: ${intent.dataString}") | |
intent.extras?.keySet()?.forEach { key -> | |
appendLine("* extra: $key=${intent.extras!![key]}") | |
} | |
}.toString() | |
} | |
Use the extension function above with println(myIntent.toDebugString()). | |
//////////////////////////////////////////////// | |
ViewBinding by Delegate in Fragments | |
fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) = | |
FragmentViewBindingDelegate(this, viewBindingFactory) | |
class FragmentViewBindingDelegate<T : ViewBinding>( | |
val fragment: Fragment, | |
val viewBindingFactory: (View) -> T | |
) : ReadOnlyProperty<Fragment, T> { | |
private var binding: T? = null | |
init { | |
val viewLifecycleOwnerLiveDataObserver = | |
Observer<LifecycleOwner?> { | |
val viewLifecycleOwner = it ?: run { | |
/** | |
* this block will run when fragment viewLifecycleOwner is null | |
* If the fragment onDestroyView runs before the view lifecycle is initialized (for example when navigating in onViewCreated), | |
* the binding reference must be cleared here because the viewLifeCycleObserver will not be triggered. | |
*/ | |
binding = null | |
return@Observer | |
} | |
//binding reference set to be null when view life cycle is destroyed | |
viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { | |
override fun onDestroy(owner: LifecycleOwner) { | |
binding = null | |
} | |
}) | |
} | |
fragment.lifecycle.addObserver(object : DefaultLifecycleObserver { | |
override fun onCreate(owner: LifecycleOwner) { | |
fragment.viewLifecycleOwnerLiveData.observeForever( | |
viewLifecycleOwnerLiveDataObserver | |
) | |
} | |
override fun onDestroy(owner: LifecycleOwner) { | |
fragment.viewLifecycleOwnerLiveData.removeObserver( | |
viewLifecycleOwnerLiveDataObserver | |
) | |
} | |
}) | |
} | |
override fun getValue(thisRef: Fragment, property: KProperty<*>): T { | |
val binding = binding | |
if (binding != null) { | |
return binding | |
} | |
val lifecycle = fragment.viewLifecycleOwner.lifecycle | |
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { | |
throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed: ${fragment.javaClass.simpleName}") | |
} | |
return viewBindingFactory(thisRef.requireView()).also { this.binding = it } | |
} | |
} | |
private val binding by viewBinding(ExampleFragmentBinding::bind) | |
/////////////////////////////////////////////////////////////////////////////// | |
// https://github.com/Zhuinden/fragmentviewbindingdelegate-kt | |
import android.view.View | |
import androidx.fragment.app.Fragment | |
import androidx.lifecycle.DefaultLifecycleObserver | |
import androidx.lifecycle.Lifecycle | |
import androidx.lifecycle.LifecycleOwner | |
import androidx.lifecycle.Observer | |
import androidx.viewbinding.ViewBinding | |
import kotlin.properties.ReadOnlyProperty | |
import kotlin.reflect.KProperty | |
class FragmentViewBindingDelegate<T : ViewBinding>( | |
val fragment: Fragment, | |
val viewBindingFactory: (View) -> T | |
) : ReadOnlyProperty<Fragment, T> { | |
private var binding: T? = null | |
init { | |
fragment.lifecycle.addObserver(object : DefaultLifecycleObserver { | |
val viewLifecycleOwnerLiveDataObserver = | |
Observer<LifecycleOwner?> { | |
val viewLifecycleOwner = it ?: return@Observer | |
viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { | |
override fun onDestroy(owner: LifecycleOwner) { | |
binding = null | |
} | |
}) | |
} | |
override fun onCreate(owner: LifecycleOwner) { | |
fragment.viewLifecycleOwnerLiveData.observeForever(viewLifecycleOwnerLiveDataObserver) | |
} | |
override fun onDestroy(owner: LifecycleOwner) { | |
fragment.viewLifecycleOwnerLiveData.removeObserver(viewLifecycleOwnerLiveDataObserver) | |
} | |
}) | |
} | |
override fun getValue(thisRef: Fragment, property: KProperty<*>): T { | |
val binding = binding | |
if (binding != null) { | |
return binding | |
} | |
val lifecycle = fragment.viewLifecycleOwner.lifecycle | |
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { | |
throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.") | |
} | |
return viewBindingFactory(thisRef.requireView()).also { this.binding = it } | |
} | |
} | |
fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) = | |
FragmentViewBindingDelegate(this, viewBindingFactory) | |
private val binding by viewBinding(FirstFragmentBinding::bind) | |
////////////////////////////////////////////////////////////////// | |
For Activity | |
inline fun <T : ViewBinding> AppCompatActivity.viewBinding( | |
crossinline bindingInflater: (LayoutInflater) -> T) = | |
lazy(LazyThreadSafetyMode.NONE) { | |
bindingInflater.invoke(layoutInflater) | |
} | |
private val binding by viewBinding(MainActivityBinding::inflate) | |
///////////////////////////////////////////// | |
Extension Function For On Backpressed Callback | |
implementation ‘androidx.activity:activity-ktx:1.6+’ | |
Add android:enableOnBackInvokedCallBack="true” to your applications manifest <application> tag. | |
fun AppCompatActivity.onBackPressed(isEnabled: Boolean, callback: () -> Unit) { | |
onBackPressedDispatcher.addCallback(this, | |
object : OnBackPressedCallback(isEnabled) { | |
override fun handleOnBackPressed() { | |
callback() | |
} | |
}) | |
} | |
// How To Use it : | |
class ExampleActivity : AppCompatActivity() { | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContentView(R.layout.activity_example) | |
onBackPressed(true) { | |
// do what do you want when get back | |
} | |
} | |
} | |
fun FragmentActivity.onBackPressed(callback: () -> Unit) { | |
onBackPressedDispatcher.addCallback(this, | |
object : OnBackPressedCallback(true) { | |
override fun handleOnBackPressed() { | |
callback() | |
} | |
} | |
) | |
} | |
// How To Use it : | |
class ExampleFragment : Fragment() { | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
requireActivity().onBackPressed { | |
// do what do you want when get back | |
} | |
} | |
} | |
/** Making this extension function cleans out the code a lot by removing the repetitive | |
flowWithLifecycle() or repeatOnLifecycle() | |
**/ | |
fun <T> Flow<T>.safeCollect( | |
owner: LifecycleOwner, | |
block: (T.() -> Unit)? = null | |
) = owner.lifecycleScope.launch { | |
flowWithLifecycle(owner.lifecycle).collectLatest { block?.invoke(it) } | |
} | |
//This Extension function can easily be used like | |
flow.safeCollect(viewLifecycleOwner){ | |
//Doing the work | |
} | |
////////////////////////////////////////////////////////////////////////////////////////// | |
//If your date is not in formate,this is the function for formatting your date | |
fun String?.formatStringDate(inputFormat : String, outputFormat : String) : String { | |
return if (this.isNullOrEmpty()){ | |
"" | |
}else{ | |
val dateFormatter = SimpleDateFormat(inputFormat, Locale.getDefault()) | |
val date = dateFormatter.parse(this) | |
date?.let { SimpleDateFormat(outputFormat, Locale.getDefault()).format(it) }.orEmpty() | |
} | |
} | |
fun String?.getYesterdayToday(date: String, format : String): String { | |
try { | |
val formatter = SimpleDateFormat(format) | |
val date = formatter.parse(date) | |
val timeInMilliseconds = date.time | |
return when { | |
DateUtils.isToday(timeInMilliseconds) -> { | |
"today" | |
} | |
DateUtils.isToday(timeInMilliseconds+ DateUtils.DAY_IN_MILLIS) -> { | |
"yesterday" | |
} | |
else -> { | |
"" | |
} | |
} | |
} catch (ex: Exception) { | |
ex.message?.let { Log.d("date exception", it) } | |
} | |
return "" | |
} | |
class MainActivity : AppCompatActivity() { | |
//here I am using the date which is already formatted | |
var date1 = "2022-11-25T00:00:00" | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContentView(R.layout.activity_main) | |
//If your date is not in formate, you can use this line to formate your date | |
var formattedDate = date1.formatStringDate("YOUR_DATE_TO_FORMAT", DATE_FORMAT) | |
var isTodayYesterday = | |
date1.getYesterdayToday(date1, DATE_FORMAT) | |
//check if date is today or yesterday | |
when (isTodayYesterday) { | |
TODAY -> { | |
Log.e("message", "Today") | |
} | |
YESTERDAY -> { | |
Log.e("message","Yesterday") | |
} | |
} | |
} | |
companion object { | |
private const val DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss" //2021-05-20T11:28:24 | |
private const val TODAY = "today" | |
private const val YESTERDAY = "yesterday" | |
} | |
} | |
//////////////////////////////////////////////////////////////////////////////////// | |
Here are two extension methods that I use for Bundle & Intent: | |
inline fun <reified T : Parcelable> Intent.parcelable(key: String): T? = when { | |
SDK_INT >= 33 -> getParcelableExtra(key, T::class.java) | |
else -> @Suppress("DEPRECATION") getParcelableExtra(key) as? T | |
} | |
inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? = when { | |
SDK_INT >= 33 -> getParcelable(key, T::class.java) | |
else -> @Suppress("DEPRECATION") getParcelable(key) as? T | |
} | |
I also requested this to be added to the support library | |
And if you need the ArrayList support there is: | |
inline fun <reified T : Parcelable> Bundle.parcelableArrayList(key: String): ArrayList<T>? = when { | |
SDK_INT >= 33 -> getParcelableArrayList(key, T::class.java) | |
else -> @Suppress("DEPRECATION") getParcelableArrayList(key) | |
} | |
inline fun <reified T : Parcelable> Intent.parcelableArrayList(key: String): ArrayList<T>? = when { | |
SDK_INT >= 33 -> getParcelableArrayListExtra(key, T::class.java) | |
else -> @Suppress("DEPRECATION") getParcelableArrayListExtra(key) | |
} | |
//////////////////////////////////////////////////////////////////////////////////////////////////////// | |
Solving Android multiple clicks problem with coroutines — Kotlin | |
import kotlinx.coroutines.Job | |
fun Job?.runOnceUntilComplete(callback: () -> Unit) { | |
this?.let { | |
if (it.isCompleted) { | |
callback.invoke() | |
} | |
} ?: run { | |
callback.invoke() | |
} | |
} | |
private var initJob: Job? = null | |
// call this function where you want | |
fun onLoginClicked() { | |
initJob.runOnceUntilComplete { | |
doLogin() | |
} | |
} | |
private fun doLogin() { | |
initJob = viewModelScope.launch { | |
viewState = PayLoginViewState.Loading | |
when (val result = loginUserUseCase(email = email, password = password)) { | |
is Outcome.Error -> setLoginErrorState(result.message) | |
is Outcome.Completed -> { | |
navigateToOnboarding() | |
viewState = PayLoginViewState.Idle | |
} | |
else -> Unit | |
} | |
} | |
} | |
//////////////////////////////////////////////////////// | |
/** | |
* Configures an [ImageView] passing a [Drawable] and an ID of a color resource | |
* | |
* @param drawable A [Drawable] to set to the [ImageView] | |
* @param colorResId A resource ID from the desired color | |
*/ | |
internal fun ImageView.setDrawableWithColor( | |
drawable: Drawable?, | |
@ColorRes colorResId: Int | |
) { | |
setImageDrawable(drawable) | |
setColor(colorResId) | |
} | |
internal fun ImageView.setColor( | |
@ColorRes colorResId: Int | |
) = ImageViewCompat.setImageTintList( | |
this, | |
ColorStateList.valueOf( | |
ResourcesCompat.getColor(resources, colorResId, null) | |
) | |
) | |
/////////////////////////////////////////////////////// | |
/** | |
* Get all children of a [View] | |
* | |
* @return A [List] of [View] with the children of a given [View] | |
*/ | |
fun View.getAllChildren(): List<View> { | |
val childrenList: MutableList<View> = mutableListOf() | |
val viewGroup = this as? ViewGroup | |
// null-check because if this is not an instance of ViewGroup, | |
// val viewGroup will be null | |
viewGroup?.let { | |
for (i in 0 until viewGroup.childCount) { | |
childrenList.add(viewGroup.getChildAt(i)) | |
} | |
} | |
return childrenList | |
} | |
/////////////////////////////////////////////////////// | |
/** | |
* Change view children state to the value in parameter | |
* | |
* @param state The [Boolean] state to set | |
*/ | |
fun View.setChildrenEnabledState(state: Boolean) { | |
this.getAllChildren().forEach { it.isEnabled = state } | |
} | |
/////////////////////////////////////////////////////////////// | |
/** | |
* Takes an object of type [T], makes a null-check and if it's not null, executes [block] with [V] | |
* as the receiver, and [T] as implicit param. **If [data] is null, the view will be hidden.** | |
* @param data An object with useful data to pass to the view | |
* @param view The view to configure | |
* @param block A function to execute in the context of [view] with param [T] | |
*/ | |
fun <T, V : View> configureViewWithNullableData(data: T?, view: V, block: V.(T) -> Unit) { | |
with(view) { | |
data?.let { block(it) } ?: this.hide() | |
} | |
} | |
///////////////////////////////////////////////////////////// | |
// adding some useful imports to simplify the code | |
import android.view.View | |
import androidx.constraintlayout.widget.ConstraintLayout | |
import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams | |
import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.MATCH_CONSTRAINT | |
import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.WRAP_CONTENT | |
import androidx.constraintlayout.widget.ConstraintSet | |
import androidx.constraintlayout.widget.ConstraintSet.START | |
import androidx.constraintlayout.widget.ConstraintSet.END | |
import androidx.constraintlayout.widget.ConstraintSet.TOP | |
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM | |
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID | |
/** | |
* Add a view into a ConstraintLayout, below of the last view (like a stack). | |
* | |
* @param child A [View] to add | |
*/ | |
fun ConstraintLayout.addChildOnStack( | |
child: View, | |
layoutParams: LayoutParams = LayoutParams(MATCH_CONSTRAINT, WRAP_CONTENT) | |
) { | |
// get all children of this, and the last child | |
val children = this.getAllChildren() | |
val lastChild = if (children.isNotEmpty()) children.last() else null | |
// check if no exists lastChild, then attach child to parent in top constraint | |
val topParentId = lastChild?.id ?: PARENT_ID | |
val parentSide = if (lastChild == null) TOP else BOTTOM | |
// generate an ID for child, and add child into this | |
child.id = View.generateViewId() | |
this.addView(child, layoutParams) | |
// create constraints to child | |
val constraintSet = ConstraintSet() | |
constraintSet.clone(this) | |
constraintSet.connect(child.id, TOP, topParentId, parentSide) | |
constraintSet.connect(child.id, START, PARENT_ID, START) | |
constraintSet.connect(child.id, END, PARENT_ID, END) | |
constraintSet.applyTo(this) | |
} | |
/** | |
* Add a view into a ConstraintLayout, at the right of the last view (like a horizontal row). | |
* | |
* @param child A [View] to add | |
* @param layoutParams (optional) A [LayoutParams] object with some previous configurations | |
*/ | |
internal fun ConstraintLayout.addChildOnRow( | |
child: View, | |
layoutParams: LayoutParams = LayoutParams(MATCH_CONSTRAINT, WRAP_CONTENT).also { | |
it.horizontalChainStyle = LayoutParams.CHAIN_SPREAD | |
} | |
) { | |
// get all children of this, and the last child | |
val children = this.getAllChildren() | |
val lastChild = if (children.isNotEmpty()) children.last() else null | |
// check if doesn't exists lastChild, then attach child to parent view in start constraint; | |
// otherwise, it attaches the view at end constraint of lastChild. | |
val topParentId = lastChild?.id ?: PARENT_ID | |
val parentSide = if (lastChild == null) START else END | |
// generate an ID for child, and add child into this | |
child.id = View.generateViewId() | |
this.addView(child, layoutParams) | |
// create constraints to child | |
val constraintSet = ConstraintSet() | |
constraintSet.clone(this) | |
constraintSet.connect(child.id, TOP, PARENT_ID, TOP) | |
constraintSet.connect(child.id, BOTTOM, PARENT_ID, BOTTOM) | |
constraintSet.connect(child.id, START, topParentId, parentSide) | |
constraintSet.connect(child.id, END, PARENT_ID, END) | |
if (lastChild != null) constraintSet.connect(lastChild.id, END, child.id, START) | |
constraintSet.applyTo(this) | |
} | |
///////////////////////////////////////////////////////////////////// | |
/** | |
* Executes a function inside a [TextWatcher] [onTextChanged] method. | |
*/ | |
internal fun TextView.onTextChanged(block: (currentText: String) -> Unit) { | |
textWatcher = object : TextWatcher { | |
override fun afterTextChanged(s: Editable?) { | |
/* Nothing to do */ | |
} | |
override fun beforeTextChanged( | |
s: CharSequence?, | |
start: Int, | |
count: Int, | |
after: Int | |
) { | |
/* Nothing to do */ | |
} | |
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { | |
block(s?.toString().orEmpty()) | |
} | |
} | |
} | |
//////////////////////////////////////////////////////////////////// | |
/** | |
* Get a query parameter from the Uri data in the intent, | |
* and an empty string if the data or value is null | |
*/ | |
fun Activity.deeplinkParam(key: String) = intent.data?.getQueryParameter(key) ?: "" | |
///////////////////////////////////////////////////////////////////////// | |
/** | |
* Add a new fragment with a [FragmentManager] | |
* @param fragment A [Fragment] to add | |
* @param containerView A [FrameLayout] that must contains the fragment | |
*/ | |
fun FragmentManager.addNewFragment(fragment: Fragment, containerView: FrameLayout) { | |
if (containerView.id == ConstraintLayout.NO_ID) containerView.id = View.generateViewId() | |
val transaction = beginTransaction() | |
transaction.add(containerView.id, fragment) | |
transaction.commit() | |
} | |
//////////////////////////////////////////////////////////// | |
/** | |
* Open a raw resource according to [resId] parameter and returns a [T] object. | |
*/ | |
internal inline fun <reified T> Context.getJsonFromRawResource(@RawRes resId: Int): T { | |
try { | |
val rawResource = resources.openRawResource(resId) | |
val buffer = ByteArray(rawResource.available()) | |
rawResource.read(buffer) | |
val json = String(buffer) | |
return Gson().fromJson(json, T::class.java) | |
} catch (e: Resources.NotFoundException) { | |
throw e | |
} catch (e: JsonSyntaxException) { | |
throw JsonSyntaxException("Error reading JSON: ${e.message}", e) | |
} | |
} | |
//////////////////////////////////////////////////////////////////////// | |
class BaseViewModel : ViewModel() { | |
infix fun <T> MutableLiveData<UiState<T>>.updateValueWith(call: suspend () -> T) { | |
viewModelScope.launch { | |
try { | |
value = UiState.Loading() | |
val result = call() | |
value = UiState.Success(result) | |
} catch (ex: Exception) { | |
value = UiState.Error(ex) | |
} | |
} | |
} | |
} | |
sealed class UiState<T>( | |
val data: T? = null, | |
val exception: Exception? = null | |
) { | |
internal class Loading<T> : UiState<T>() | |
internal class Success<T>(data: T) : UiState<T>(data = data) | |
internal class Error<T>(exception: Exception) : UiState<T>(exception = exception) | |
} | |
////////////////////////////////////////////////////////////////////////////////// | |
fun String.toTitleCase(): String { | |
return this.split(" ").joinToString(" ") { it.capitalize() } | |
} | |
println("hello world".toTitleCase()) // Output: "Hello World" | |
/////////////////////////////////////////////////////////////// | |
//Int to Enum | |
inline fun <reified T : Enum<T>> Int.toEnum(): T? { | |
return enumValues<T>().firstOrNull { it.ordinal == this } | |
} | |
//Enum to Int | |
inline fun <reified T : Enum<T>> T.toInt(): Int { | |
return this.ordinal | |
} | |
//////////////////////////////////////////////////////////// | |
inline fun <reified T : ViewBinding> inflateViewBinding(parent: ViewGroup): T { | |
val layoutInflater = LayoutInflater.from(parent.context) | |
return T::class.java.getMethod( | |
"inflate", | |
LayoutInflater::class.java, | |
ViewGroup::class.java, | |
Boolean::class.javaPrimitiveType | |
).invoke(null, layoutInflater, parent, false) as T | |
} | |
val binding = inflateViewBinding<MyItemBinding>(parent) | |
//////////////////////////////////////////////////////// | |
fun Any?.printToLog(tag: String = "DEBUG_LOG") { | |
Log.d(tag, toString()) | |
} | |
val text = "This is text" | |
text.printToLog() | |
////////////////////////////////////////////////////// | |
fun View.gone() = run { visibility = View.GONE } | |
fun View.visible() = run { visibility = View.VISIBLE } | |
fun View.invisible() = run { visibility = View.INVISIBLE } | |
infix fun View.visibleIf(condition: Boolean) = | |
run { visibility = if (condition) View.VISIBLE else View.GONE } | |
infix fun View.goneIf(condition: Boolean) = | |
run { visibility = if (condition) View.GONE else View.VISIBLE } | |
infix fun View.invisibleIf(condition: Boolean) = | |
run { visibility = if (condition) View.INVISIBLE else View.VISIBLE } | |
view.gone() | |
view.visible() | |
view.invisible() | |
// dataFound, loading, and condition should be a valid boolean expression | |
view goneIf dataFound | |
view visibleIf loading | |
view invisibleIf condition | |
////////////////////////////////////////////////////// | |
fun View.snackbar(message: String, duration: Int = Snackbar.LENGTH_LONG) { | |
Snackbar.make(this, message, duration).show() | |
} | |
fun View.snackbar(@StringRes message: Int, duration: Int = Snackbar.LENGTH_LONG) { | |
Snackbar.make(this, message, duration).show() | |
} | |
rootView.snackbar("This is snackbar message") | |
rootView.snackbar(R.string.snackbar_message) | |
// Custom Duration Length | |
rootView.snackbar("This is snackbar message", duration = Snackbar.LENGTH_SHORT) | |
///////////////////////////////////////////////////////////////////////// | |
// Convert px to dp | |
val Int.dp: Int | |
get() = (this / Resources.getSystem().displayMetrics.density).toInt() | |
//Convert dp to px | |
val Int.px: Int | |
get() = (this * Resources.getSystem().displayMetrics.density).toInt() | |
params.setMargins(16.px, 16.px, 16.px, 16.px) | |
////////////////////////////////////////////////////////////////////////////// | |
val String.isDigitOnly: Boolean | |
get() = matches(Regex("^\\d*\$")) | |
val String.isAlphabeticOnly: Boolean | |
get() = matches(Regex("^[a-zA-Z]*\$")) | |
val String.isAlphanumericOnly: Boolean | |
get() = matches(Regex("^[a-zA-Z\\d]*\$")) | |
val isValidNumber = "1234".isDigitOnly // Return true | |
val isValid = "1234abc".isDigitOnly // Return false | |
val isOnlyAlphabetic = "abcABC".isAlphabeticOnly // Return true | |
val isOnlyAlphabetic2 = "abcABC123".isAlphabeticOnly // Return false | |
val isOnlyAlphanumeric = "abcABC123".isAlphanumericOnly // Return true | |
val isOnlyAlphanumeric2 = "abcABC@123.".isAlphanumericOnly // Return false | |
//////////////////////////////////////////////////////////////////////////////// | |
val Any?.isNull get() = this == null | |
if (obj.isNull) { | |
// Run if object is null | |
} else { | |
// Run if object is not null | |
} | |
//////////////////////////////////////////////////////////////////////////////////// | |
fun Any?.ifNull(block: () -> Unit) = run { | |
if (this == null) { | |
block() | |
} | |
} | |
obj.ifNull { | |
// Write code | |
} | |
//////////////////////////////////////////////////////////////////////// | |
fun String.toDate(format: String = "yyyy-MM-dd HH:mm:ss"): Date? { | |
val dateFormatter = SimpleDateFormat(format, Locale.getDefault()) | |
return dateFormatter.parse(this) | |
} | |
fun Date.toStringFormat(format: String = "yyyy-MM-dd HH:mm:ss"): String { | |
val dateFormatter = SimpleDateFormat(format, Locale.getDefault()) | |
return dateFormatter.format(this) | |
} | |
val currentDate = Date().toStringFormat() | |
val currentDate2 = Date().toStringFormat(format = "dd-MM-yyyy") | |
val date = "2023-01-01".toDate(format = "yyyy-MM-dd") | |
//////////////////////////////////////////////////////////////////////////////// |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment