Caricamento
Via Mario Giuntini, 29 - 56021 Navacchio (PI) (+39) 329 223 7273 info@intouch-srl.com
Nel panorama dello sviluppo Android moderno, Kotlin è diventato lo standard per creare applicazioni robuste, leggibili e scalabili. Una delle sue funzionalità più potenti sono le extension functions, strumenti che permettono di aggiungere comportamenti a classi preesistenti senza modificarne l’implementazione.
Molti team non sfruttano appieno questo meccanismo, perdendo l’opportunità di ridurre drasticamente il boilerplate, centralizzare comportamenti comuni e rendere il codice più espressivo. Le estensioni non sono una scorciatoia: rappresentano un modo per creare un vero e proprio linguaggio interno al progetto, condiviso tra sviluppatori e facile da mantenere.
In questo articolo vedremo 10 extension functions realmente utili, tratte da casi d’uso frequenti, insieme a un capitolo avanzato che mostra come applicarle in una struttura architetturale moderna composta da ViewModel, Repository e Networking.
Gestire la visibilità con View.VISIBLE o View.GONE genera ripetizioni che appesantiscono il codice. Queste estensioni rendono il tutto più espressivo:
fun View.show() { visibility = View.VISIBLE }
fun View.hide() { visibility = View.GONE }
fun View.invisible() { visibility = View.INVISIBLE }
E l’uso diventa più naturale:
loader.show()
button.hide()
Tre funzioni semplici che migliorano sensibilmente la leggibilità della UI.
Un listener tradizionale richiede spesso più righe. Una piccola estensione semplifica tutto:
fun View.onClick(action: () -> Unit) =
setOnClickListener { action() }
E il codice chiamante appare più ordinato:
submitButton.onClick { viewModel.submit() }
Queste estensioni eliminano duplicazioni comuni in tutta l’app:
val Int.dp: Intget() = (this * Resources.getSystem().displayMetrics.density).toInt()
val Int.px: Int
get() = (this / Resources.getSystem().displayMetrics.density).toInt()
Particolarmente utili nelle custom view e nelle animazioni.
Molti listener richiedono un Boolean di ritorno che determina il comportamento dell’evento. Una estensione può rendere più chiaro il flusso:
inline fun <T> T.consume(block: T.() -> Unit): Boolean {
block()
return true
}
E il suo utilizzo:
view.setOnTouchListener { _, _ ->
consume {
// logica del touch
}
}
Un adapter può risultare molto più leggibile se si aggiunge questa estensione:
fun ViewGroup.inflate(@LayoutRes res: Int): View =
LayoutInflater.from(context).inflate(res, this, false)
Il ViewHolder si semplifica:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
MyViewHolder(parent.inflate(R.layout.item_row))
Molti cast possono essere eliminati:
inline fun <reified T> Intent.getExtraSafe(key: String): T? =
extras?.get(key) as? T
Chiaro, sicuro e leggibile.
Impostare errori ripetendo sempre la stessa logica non è necessario:
fun TextInputLayout.showError(message: String?) {
error = message
isErrorEnabled = message != null
}
E l’uso diventa naturale:
emailInput.showError("Email non valida")
A volte è utile aggiungere comportamenti descrittivi:
fun <T> List<T>.ifNotEmpty(block: (List<T>) -> Unit) {
if (isNotEmpty()) block(this)
}
Adatta a casi dove presenza o assenza di dati cambia il flusso.
Una delle estensioni più utili per evitare crash legati al lifecycle:
fun <T> Flow<T>.collectWhenStarted(
owner: LifecycleOwner,
collector: suspend (T) -> Unit
) {
owner.lifecycleScope.launch {
owner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
collect { collector(it) }
}
}
}
Esempio:
viewModel.uiState.collectWhenStarted(viewLifecycleOwner) { render(it) }
Questa estensione centralizza il formato dei log:
inline fun <reified T> T.log(message: String) {
Log.d(T::class.java.simpleName, message)
}
``
Più chiaro e uniforme.
Le estensioni esprimono tutta la loro potenza quando vengono integrate in una Clean Architecture moderna. Possono infatti:
Vediamo alcuni esempi.
Per gestione coerente degli stati:
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val throwable: Throwable) : Result<Nothing>()
object Loading : Result<Nothing>()
}
Molto utile per aggiornare lo UI State:
inline fun <T> Result<T>.onResult(
success: (T) -> Unit = {},
error: (Throwable) -> Unit = {},
loading: () -> Unit = {}
) {
when (this) {
is Result.Success -> success(data)
is Result.Error -> error(throwable)
is Result.Loading -> loading()
}
}
Esempio d’uso:
repository.getUser().onResult(
success = { user -> _uiState.value = UiState(user = user) },
error = { _uiState.value = UiState(error = it) },
loading = { _uiState.value = UiState(isLoading = true) }
)
Una sola estensione evita try/catch sparsi:
suspend fun <T> safeCall(
call: suspend () -> Response<T>
): Result<T> {
return try {
val response = call()
if (response.isSuccessful && response.body() != null) {
Result.Success(response.body()!!)
} else {
Result.Error(Exception(response.errorBody()?.string()))
}
} catch (e: Exception) {
Result.Error(e)
}
}
Repository molto più pulito:
class UserRepository(private val api: UserApi)
suspend fun getUser(): Result<User> =
safeCall { api.getUser() }
}
fun OkHttpClient.Builder.addAuthHeader(tokenProvider: () -> String): OkHttpClient.Builder {
addInterceptor { chain ->
val newRequest = chain.request().newBuilder()
.addHeader("Authorization", "Bearer ${tokenProvider()}")
.build()
chain.proceed(newRequest)
}
return this
}
fun UserDto.toDomain(): User =
User(id, name, email)
Abbinato a:
inline fun <T, R> Result<T>.map(transform: (T) -> R): Result<R> =
when (this) {
is Result.Success -> Result.Success(transform(data))
else -> this
}
Repository:
suspend fun getUser(): Result<User> =
safeCall { api.getUser() }.map { it.toDomain() }
fun String?.orUnknown(default: String = "N/A"): String =
this?.takeIf { it.isNotBlank() } ?: default
Applicata in:
fun UserDto.toDomain() = User(
id = id,
name = name.orUnknown(),
email = email.orUnknown()
)
Collaboriamo con startup innovative e grandi multinazionali, unendo l’esperienza nelle architetture software delle imprese più strutturate con la flessibilità delle soluzioni mobile e dei servizi web.
Via Mario Giuntini, 29 - 56021 Navacchio (PI)
(+39) 329 223 7273
info@intouch-srl.com
Orario: 09:00-18:00