Photo by Glenn Carstens-Peters on Unsplash
Kotlin - delegate that interface!
Learn how to use Kotlin delegates to defer your interface implementation and reduce code!
Kotlin supports a beautiful feature called delegation. According to the docs:
Kotlin supports "delegation" design pattern by introducing a new keyword "by". Using this keyword or delegation methodology, Kotlin allows the derived class to access all the implemented public methods of an interface through a specific object.
Key points from this:
- Delegation is achieved using 'by'
- The derived class can access all the public methods of an interface via an object.
Ok, so what does it mean?
Let me explain in detail.
Use-Case: We want access to the currently signed in user from the UI to show the user's name (let's say). The usual process would be, access the viewmodel
, usecase
object connects with the repository and finally a call to Room database
.
Using the delegate pattern, you can do this in half the steps and much cleaner code. Let's see how.
Access via android ViewModel
:
Define a ViewModel like this:
@HiltViewModel
class HomeViewModel @Inject constructor()
Let's define an interface called SignedInUserDelegate which tracks the currently signed in user.
interface SignedInUserDelegate {
fun getUser() : LiveData<User>
}
This just an interface. It does nothing. We need a concrete implementation to get the user.
Suppose we are going to get the user from Room Database. You'll probably have a DAO like this:
@Dao
interface UserDao {
@Query("SELECT * FROM current_user")
fun getUser() : LiveData<User>
}
Pay attention to the method name getUser()
.
It has the same signature as the method in SignedInUserDelegate interface.
Going back to the viewmodel
, the UI wants the currently signed in user. How do we do it. Very easy. We'll make a quick change in the viewmodel using a delegate.
@HiltViewModel
class HomeViewModel @Inject constructor(
private val userDao: UserDao) : SignedInUserDelegate by userDao
This is a bit confusing I know. Let me explain whats going on here. 2 things:
private val userDao: UserDao
We are getting an instance ofuserDao
injected.SignedInUserDelegate by userDao
We are specifying the viewmodel
to implement SignedInUserDelegate
but the implementation will be handled by userDao.
Why would we want the VM to implement SignedInUserDelegate
? So that the UI (activity/fragment) can access the method getUser()
directly!
Now, the methods defined in the dao, will be available in the viewmodel
directly.
The implementation has been delegated to the Dao So now one can do directly from the UI:
homeVM.getUser().observe(viewLifeCycleOwner, Observer { user ->
// Do something with the user
}
Remember, we didn't implement any getUser()
method in the viewmodel, YET the compiler didn't complain.
This is because the compiler knows that the implementation is handled via userDao
.
This call will now directly execute the query in the dao. Super cool right?
Usage in the backend
A similar usage can be done in the backend as well. I am talking about a Springboot
backend.
We usually follow the controller → service → repository → dao pattern in the backend. Define the same method in the repository & the dao and use this delegate pattern to reduce some code in the repository to fetch data.
Any method which requires some special handing before inserting data or fetching, implement in the repository and the rest of the methods can be handled directly by the dao.
Example:
@Repository
class DefaultRepo @Autowired constructor(private val dbMapper: DatabaseMapper) : Repo, DatabaseMapper by dbMapper {
From service you can directly call the DB methods. That's all folks!