Android/Kotlin: Composing different Activity "traits" into one activity - android

I'm looking for a way to combine different features in an Android activity, that should be reusable for different activity classes. Specifically the problem arises from overriding open methods where the super's implementation also has to be called.
open class FirstActivity : FragmentActicity() {
override fun onStart() {
super.onStart()
doSomething()
}
}
That's simple enough, but it is not reusable. I could e.g. want to have the same behavior with a different base activity class:
open class SecondActivity : AppCompatActivity() {
override fun onStart() {
super.onStart()
doSomething()
}
}
where I'd have to duplicate the code. If I have a very basic functionality like tracking the state of the activity, I would want this in more or less all of my activities which do have different base classes.
It get's even worse when I want to create some more features that can be combined:
open class ThirdActivity : FragmentActivity() {
override fun onResume() {
super.onResume()
doSomeResuming()
}
}
open Class FirstActivityAgain : ThirdActivity {
override fun onStart() {
super.onStart()
doSomething()
}
}
class MyFragmentActivity : FirstActivity() {
override fun onStop() {
doSomethingElse()
super.onStop()
}
}
class MyFragmentActivityWithResuming : FirstActivityAgain() {
override fun onStop() {
doSomethingElse()
super.onStop()
}
}
class MyTopBarActivity : SecondActivity() {
override fun onStop() {
doSomethingElse()
super.onStop()
}
}
In Scala I can use Traits to do this stackable modification, which allows for very flexible mixins of functionality. It's even possible to modify the same method over and over again, one just has to be careful with the linearization order.
None of this is possible in Kotlin because a Scala Trait is neither equivalent to a Kotlin abstract class nor to a Kotlin Interface.
It doesn't seem to be possible with Kotlin's delegates either. I also thought about using generics, which in my limited imagination could look like this:
open class FirstActivity<BaseActivity : Activity> : BaseActivity() {
...
}
which of course is also not possible.
Is there anything I've overlooked? Can it be done by using Dagger?

What you are referring to in Kotlin called interfaces in conjunction with some basic delegation.
interface Base {
fun printMessage()
fun printMessageLine()
}
class BaseImpl(val x: Int) : Base {
override fun printMessage() { print(x) }
override fun printMessageLine() { println(x) }
}
class Derived(b: Base) : Base by b {
override fun printMessage() { print("abc") }
}
fun main() {
val b = BaseImpl(10)
Derived(b).printMessage()
Derived(b).printMessageLine()
}
Though it won't save you from the super problem since it Android framework issue rather than Kotlin Language.
For your case I would do something like
interface BaseActivityContainer{
var activity: Activity
}
class MainActivity: BaseActivityContainer{
override var activity: Activity = this
}
interface BaseDoable: BaseActivityContainer{
fun doActivityStuff(){
activity.getString(...)
}
}
interface BaseDoableSecond: BaseActivityContainer{
fun doActivityStuff(){
activity.getDrawable(...)
}
}
class SomeActivity: MainActivity, BaseDoableSecond by this
Handle Lyfecycle events with the help of Android Lifecycle
This is not complete and barely functional but I hope it will clear some stuff for you.

Related

What is the best way to set my delegate variable (inner class vs. this)?

So, I have a following BLEClient class and I'm currently setting deviceDelegate using this keyword
class BLEClient(val device: Device) : Client, DeviceDelegate {
init {
device.deviceDelegate = this
}
// client interface methods
override fun connect() {...}
override fun disconnect() {...}
override fun send() {...}
// device delegate interface methods
override fun didSend() { ... }
override fun didConnect() { ... }
override fun didReceive() { ... }
}
I was wondering if this is the best way to do it, since I could also use an inner class for DeviceDelegate instead of implementing interface directly, in my eyes this would make the code more readable and simple?
The class would look like this:
class BLEClient(val device: Device) : Client {
init {
device.deviceDelegate = DeviceDelegateInner()
}
/* client interface methods */
override fun connect() {...}
override fun disconnect() {...}
override fun send() {...}
inner class DeviceDelegateInner() : DeviceDelegate {
override fun didSend() { }
override fun didConnect() { }
override fun didReceive() { }
}
}
Are there any significant drawbacks if I set my delegate using an inner class instead of implementing an interface directly and setting it using this keyword?
What would you guys prefer? Which way is better?
It really depends on your use-case, and I'm not familiar with DeviceDelegate at all. But I'll give it a try.
Using inner class provides better encapsulation and separation of concerns.
Also, implementing less interfaces makes your class a bit easier to reason about, for the same reasons above.
You can also argue that the second approach is more "composition over inheritance".
I would use the first approach only if you see a lot of duplication in your inner class, eg:
class BLEClient(val device: Device) : Client {
fun b() { }
inner class DeviceDelegateInner() : DeviceDelegate {
override fun a() = b()
}
}

How can I decrease a method count in my activity in MVP pattern?

I build my app in MVP architecture and I have a trouble with many functions in my activity and presenter. How Can I decrease a method count?
I have already heard about some solutions:
Split a big presenter into smaller ones but then I would have to create another methods in my activity for presenters connection.
Create a new class and create it instance in my activity which would implement the View interface and will require all of the views needed to manage the presenters. But I am not convinced to this solution. I think it may add another mess to my architecture.
Do you have other ideas or advantages/disadvantages about one described above?
There is more than a way to reduce methods from your Activity/Fragment
One is called inheritance, where you can extend abstract methods into your main Activity/Fragment class and manage the lifecycle from there.
For example, using BaseActivity or BaseFragment you can have more than one method inside of it and just extend that into your main Activity or Fragment
BaseActivity.kt
abstract class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
requestWindowFeature(Window.FEATURE_NO_TITLE)
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
super.onCreate(savedInstanceState)
setContentView(getLayout())
}
#LayoutRes
abstract fun getLayout(): Int
fun Context.toast(message: String?, toastDuration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, message, toastDuration).show()
}
override fun onDestroy() {
super.onDestroy()
//Do here what you want
}
override fun onStop() {
super.onStop()
//Do here what you want
}
override fun onStart(){
super.onStart()
//Do here what you want
}
override fun onPause() {
super.onPause()
//Do here what you want
}
override fun onRestart() {
super.onRestart()
//Do here what you want
}
}
This BaseActivity extends AppCompatActivity(), that means that you can manage the lifecycle of your Activity in this class, and then, just extend it in your main Activity, when you do this, all the functionality inside your BaseActivity will be applied to your MainActivity class, if you want to change or override something, just call the methods from that abstract class.
MainActivity.kt
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//You dont need setContentView since we do all the configuration in the BaseActivity
toast("This is a message with a toast since we implemented thi into the BaseActivity we do not need to do toasts all over again")
}
override fun getLayout(): Int {
return R.layout.activity_login_view
}
//For example, if you want to override the functionallity from a method inside your BaseActivity you can implement it like always
override fun onRestart() {
super.onRestart()
//Replace what BaseActivity onRestart() does
}
Doing this, you can have more than 1 method of your Activity inside your BaseActivity, this will reduce the methods inside your class that inherits from BaseActivity, also, if you need this to work with Fragments, just extend Fragment instead of AppCompatActivity an make a class called BaseFragment
Also, adding an interface for view operations and presenter operations is a great way to organize our apps, you can take a look at an example I'm making for login on Github

Is there any way to use Koin inject with generic?

I heve base class and I would like to use Koin injection on this base class like:
abstract class BasePresenterFragment<T : BasePresenter> : BaseFragment() {
lateinit var presenter: T by inject<T>() // here is problem
override fun onStart() {
super.onStart()
presenter.subscribe()
}
override fun onStop() {
super.onStop()
presenter.unSubscribe()
}
}
I know there are solutions for inject viewModel but not for simple injection. So is there any way to use Koin injection with generic type?
Well, I've found only partly solution for this question. It's use presenter like abstract val in base class. This will make it possible to use the methods of presenter in the base class but I still should use inject() in every subclasses for initialization. Example:
abstract class BasePresenterFragment<P : BasePresenter> : BaseFragment() {
abstract val presenter: P
override fun onStart() {
super.onStart()
presenter.subscribe()
}
override fun onStop() {
super.onStop()
presenter.unSubscribe()
}
}
And subclass:
class HomeFragment : BasePresenterFragment<HomeContract.Presenter>(), HomeContract.View {
...
override val presenter: HomeContract.Presenter by inject()
...
}
Koin does not support generics by default.
"Koin definitions doesn't take in accounts generics type argument."
however you are supposed to you the named argument to workaround this:
The latest Version even supports directly passing the type insted of a custom string:
module {
single(named<Int>) { ArrayList<Int>() }
single(named<String>) { ArrayList<String>() }
}
and when injecting, simply use get(named<Int>) or get(named<String>) depending on your need. For more information cf.: https://insert-koin.io/docs/reference/koin-core/definitions/

Android&Kotlin lifecycle events in interface

Is there any possibility to trigger some default methods in Kotlin interfaces with lifecycle events of, for example, an Activity that implements that interface?
So, I have such interface, that called in Swift - protocol:
interface MyInterface {
fun showToast() {
this as MyActivity
Toast.show(this, "Welcome", Toast.LENGTH_SHORT).show()
}
}
And Activity class:
class MyActivity : AppCompatActivity(), MyInterface {
fun onResume() {
super.onResume()
showToast() //I want this method be called automatically, if possible
}
}
As you can see I should call showToast() method directly. But is there any possibility to call it automatically with, for example, LifeCycleObserver events or somehow else?
You can extend LifecycleObserver interface and use appropriate annotations, for example:
interface LifecycleInterface : LifecycleObserver{
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onLifeResume(){
(this as? Context).let{Toast.makeText(it, "Resumed", Toast.LENGTH_LONG).show()}
}
#OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onLifePause(){
(this as? Context).let{Toast.makeText(it, "Paused", Toast.LENGTH_LONG).show()}
}
}
Then register activity itself (or any custom object for that matter) as listener for lifecycle events:
class MainActivity : AppCompatActivity(), LifecycleInterface {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycle.addObserver(this) // add this to trigger lifecycle methods from interface
setContentView(R.layout.activity_main)
// rest of your onCreate...
}
}
Edit:
After showing bytecode and decompiling back to java, I end up with those two methods injected into activity:
#OnLifecycleEvent(Event.ON_RESUME)
public void onLifeResume() {
DefaultImpls.onLifeResume(this);
}
#OnLifecycleEvent(Event.ON_PAUSE)
public void onLifePause() {
DefaultImpls.onLifePause(this);
}
For anyone still facing issue due to compiler adding a parameter in default function of the interface, this is how you fix it:
Instead of using kapt to process the annotations:
kapt "androidx.lifecycle:lifecycle-compiler:$archLifecycleVersion"
Use annotationProcessor for lifecycler compiler:
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$archLifecycleVersion"
This works with kotlin code.

Kotlin generics issue

The error occurs when passing this to onResume.
Somehow it doesn't recognize that this implements ActivityLifecycleType, Am I missing something?
open class BaseActivity<ViewModelType: ActivityViewModel<*>>: RxAppCompatActivity(), ActivityLifecycleType {
protected var viewModel: ViewModelType? = null
#CallSuper
override fun onResume() {
super.onResume()
viewModel?.onResume(this) ==> Error Required Nothing, Find BaseActivity<ViewModelType>
}
}
open class ActivityViewModel<in ViewType: ActivityLifecycleType> {
fun onResume(view: ViewType) {
// Do something
}
}
interface ActivityLifecycleType {
fun lifecycle(): Observable<ActivityEvent>
}
Kotlin's generics' more strict that you have you write use the code below:
open class BaseActivity<ViewModelType : ActivityViewModel<ActivityLifecycleType>> : ActivityLifecycleType, RxAppCompatActivity() {
protected var viewModel: ViewModelType? = null
#CallSuper
override fun onResume() {
super.onResume()
viewModel?.onResume(this#BaseActivity) // ==> Error Required Nothing, Find BaseActivity<ViewModelType>
}
}
open class ActivityViewModel<in ViewType : ActivityLifecycleType> {
fun onResume(view: ViewType) {
// Do something
}
}
interface ActivityLifecycleType {
fun lifecycle(): Observable<ActivityEvent>
}
What I've done is to change the declaration in the first line.
Java is too weak to check the generic type but Kotlin do.
Mention there're two things you have to do next:
implement lifecycle in BaseActivity or make it abstract.
it's recommended to use lateinit var viewModel instead of nullable types

Categories

Resources