MyFamilyTracker
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Learn how Kotlin's delegation with the 'by' keyword eliminates repetitive interface implementations and reduces boilerplate code in Android development.
On this page
Every time I write
class Foo : SomeInterface by someInstanceAfter 13 years of Android development and shipping 22+ apps, I've seen countless codebases bloated with manual delegation. The
byConsider a typical Android scenario. You're wrapping a
RecyclerView.Adapterclass LoggingAdapter(
private val delegate: RecyclerView.Adapter<RecyclerView.ViewHolder>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): RecyclerView.ViewHolder {
return delegate.onCreateViewHolder(parent, viewType)
}
override fun onBindViewHolder(
holder: RecyclerView.ViewHolder,
position: Int
) {
delegate.onBindViewHolder(holder, position)
}
override fun getItemCount(): Int {
return delegate.getItemCount()
}
override fun getItemViewType(position: Int): Int {
return delegate.getItemViewType(position)
}
override fun setHasStableIds(stableIds: Boolean) {
delegate.setHasStableIds(stableIds)
}
override fun getItemId(position: Int): Long {
return delegate.getItemId(position)
}
// ... 15 more forwarding methods
}That's 80+ lines of pure boilerplate. Every method just forwards to the delegate. And if
RecyclerView.AdapterWith the
byclass LoggingAdapter(
private val delegate: RecyclerView.Adapter<RecyclerView.ViewHolder>
) : RecyclerView.Adapter<RecyclerView.ViewHolder> by delegateOne line. All interface methods are automatically forwarded.
When you write
ClassName : Interface by instance| Approach | Lines of Code | Maintenance Burden | Type Safety |
|---|---|---|---|
| Manual delegation | 80+ | High (must update on interface changes) | Compile-time |
| Kotlin code | 1 | Zero | Compile-time |
[!TIP] Use delegation whenever you find yourself writing forwarding methods. If you're typing
more than twice, it's time forcodedelegate.someMethod().codeby
SharedPreferences is a classic candidate for delegation. Instead of scattering
getStringgetIntgetBooleanclass TypedPreferences(
private val prefs: SharedPreferences
) : SharedPreferences by prefs {
fun getStringSafe(key: String, default: String = ""): String {
return prefs.getString(key, default) ?: default
}
fun putStringSafe(key: String, value: String) {
prefs.edit().putString(key, value).apply()
}
}Now your entire app uses
TypedPreferencesIn a multi-module Android project, I often need to share ViewModels across fragments. Delegation makes this clean:
class SharedViewModelDelegate(
private val fragment: Fragment,
private val viewModelStoreOwner: ViewModelStoreOwner
) : ViewModelStoreOwner by viewModelStoreOwner {
fun <T : ViewModel> viewModel(
factory: ViewModelProvider.Factory,
modelClass: Class<T>
): Lazy<T> = lazy {
ViewModelProvider(this, factory)[modelClass]
}
}Any fragment can now delegate to a shared ViewModel store without duplicating the boilerplate.
The
bylazyobservablevetoableclass UserSession {
val currentUser: User by lazy {
// Expensive operation: decrypt token, fetch from disk
User.fromEncryptedToken(readEncryptedToken())
}
var isLoggedIn: Boolean by Delegates.observable(false) { _, old, new ->
if (old != new) {
logAuthStateChange(new)
}
}
}| Delegate | Use Case | Behavior |
|---|---|---|
code | Expensive initializations | Computed once, cached |
code | Reacting to property changes | Callback on every set |
code | Conditional property updates | Callback can reject new value |
[!NOTE] Property delegation works through the
andcodeReadOnlyPropertyinterfaces. You can implement these to create custom delegates for your domain.codeMutablePropertyDelegate
Delegation isn't a silver bullet. I've seen teams abuse it in ways that hurt readability.
// DON'T do this—delegation chains become impossible to follow
class A : InterfaceX by B by C by D by EIf you're delegating to more than two levels deep, refactor. Extract the logic into a dedicated class instead.
Also, remember that delegated properties hold strong references to their delegates. In Android, this can cause memory leaks if you're not careful:
// Risky in an Activity context
class LeakyActivity : AppCompatActivity() {
private val dataManager by DataManagerDelegate(this) // Holds Activity reference
}[!WARNING] Always check your delegate implementations for memory leaks. If the delegate holds a reference to a Context, Lifecycle, or View, make sure it's released appropriately.
You can create your own delegates by implementing the delegation interfaces. Here's a practical example—a delegate that automatically clears itself when the Activity is destroyed:
class AutoClearValue<T>(
private val lifecycle: Lifecycle
) : ReadWriteProperty<Any?, T?>, LifecycleObserver {
private var value: T? = null
init {
lifecycle.addObserver(this)
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun clear() {
value = null
lifecycle.removeObserver(this)
}
override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
return value
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
this.value = value
}
}
// Usage in an Activity
class MainActivity : AppCompatActivity() {
private var cachedData: List<String>? by AutoClearValue(lifecycle)
}This pattern replaces the old
WeakReferenceIn my current project, I replaced 12 manual delegation classes with
byThat's not just about typing less. Fewer lines means fewer places for bugs, easier code reviews, and faster onboarding for new developers.
[!IMPORTANT] Kotlin delegation doesn't just reduce code volume—it eliminates an entire category of bugs caused by forgetting to forward a newly added interface method.
After shipping 22 apps, here are the rules I follow:
bylazyobservableFunction0/**
* Delegates to [SharedPreferences] but provides type-safe access.
* Automatically applies changes instead of committing.
*/
class SafePrefs(prefs: SharedPreferences) : SharedPreferences by prefsThe investment in clear documentation pays off when you're debugging at 2 AM six months later.
bylazyobservablevetoablebySudarshan Chaudhari
AI Systems Builder / Product Engineer
Bangkok, Thailand
Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.
Related Posts
Related Apps
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Building something? Available for Android dev and QA consulting.
Work with meComments — powered by Giscus
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
ReadPrivate dream journal — structured entry capture, pattern tagging, and optional Claude-powered insight generation. All data stays on-device by default.
ReadWorkout tracker — exercise logging with set/rep/weight history, goal progression, and local Room DB persistence. No account, no cloud sync required.
Read