Most common implementation of Delegation Pattern is when the responsibility of the class is delegated to a real object of a concrete implementation of the same interface.
class HomeAcrobat implements Acrobat {
private final Acrobat mProAcrobat = new CircusAcrobat();
@Override
public void doAmazingStuff(){
mProAcrobat.doAmazingStuff();
}
}
Kotlin gives amazing and neat way to implement Delegation pattern using the keyword by
. This gives impression of having multiple inheritance, it is still a polymorphisms in a sense.
In activity-ktx:1.3.+
, requesting and receiving results where taken to a new level with introduction of ActivityResultContract<I, O>
and ActivityResultCallback
. ActivityResultContract<I, O>
is an interface that performs how and who will get the request for result, and ActivityResultCallback
is how the consumer will consume the result.
But Google left us with ActivityResultContract
abstract class which is the only obstacle we have to jump.
Consider we are advocates of separation of concerns and we want to take take the parsing of the result in another class.
@FragmentScope
class ChooseFilterContractor @Inject constructor(private val context: Context) : ActivityResultContractor<Unit, String> {
override fun createIntent(input: Unit): Intent {
return Intent(context, ChooseFilterActivity::class.java)
}
override fun parseResult(resultCode: Int, intent: Intent?): String {
return if (resultCode == Activity.RESULT_OK) {
intent?.getStringExtra(KEY_FILTER)?: error("Dispatched RESULT_OK, but no payload)
} else {
""
}
}
}
You may notice the interface ActivityResultContractor<I, O>
, this is extracted interface from ActivityResultContract
.
With a minor tweak to the prepareCall()
function, we can wrap our contractor to work as library provided ActivityResultContract
.
fun <I, O> prepareCall(
activityResultContractor: ActivityResultContractor<I, O>,
activityResultCallback: ActivityResultCallback<O>
): ActivityResultLauncher<I> {
return prepareCall(object : ActivityResultContract<I, O>() {
override fun createIntent(input: I): Intent = activityResultContractor.createIntent(input)
override fun parseResult(resultCode: Int, intent: Intent?): O =
activityResultContractor.parseResult(resultCode, intent)
}, activityResultCallback)
}
This function will be called from the Fragment
or Activity
, since they are only able to launch another Activity
.
Our goal is to make the ViewModel be able to parse and receive the result. You may notice that now the ProductViewModel
also acts like a contractor but the parsing of the result is delegated to the activityResultContractor
using this syntax ActivityResultContractor<Unit, String> **by** activityResultContractor
.
class ProductsViewModel @Inject constructor(
private val activityResultContractor: ActivityResultContractor<Nothing, String>,
private val coroutineProvider: CoroutineProvider,
private val productInteractor: ProductInteractor
) : ViewModel(), ActivityResultCallback<String>,
ActivityResultContractor<Unit, String> by activityResultContractor{
override fun onActivityResult(result: String?) {
// do something with the result
}
}
Final call to the tweaked prepareCall()
would be:
chooseProductFilter.setOnClickListener {
prepareCall(productViewModel, productViewModel).launch(Unit)
}
Another example of calling launch
, when the input type is not Unit
:
textLiveData.observe(viewLifecycle){
prepareCall(productViewModel, productViewModel).launch(it)
}
Top comments (0)