0

I develop an app in Kotlin that has many Activities. Most of the navigation is just basic, starting the new activity, not passing anything to the intent. For a few groups of Activities I might pass flags, which is also repetitive throughout my project (e.g. 5 Activities that set intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK). So if I could find a way to make that just one implementation would make things a little nicer.

To make things easier and a little neater, I have created companion objects for the Activities that hold the following function:

class MyActivity: AppCompatActivity() {
   companion object {
        fun startActivity(context: Context) {
            context.startActivity(
                Intent(context, MyActivity::class.java)
            )
        }
    }
...

Other classes can simply call MyActivity.startActivity(context).

The function is pretty basic and the only difference between the implementation in different Activities is obviously the MyActivity::class.java-part. So id like to know if its possible to declare this function only ONCE and somehow use it within multiple companion objects for the different activities.

Now, I understand I could do something like this:

I define an object that has a function with a generic type and set it up like this:

object MyObject {
    inline fun <reified T: AppCompatActivity>startActivity(context: Context) {
        context.startActivity(
            Intent(context, T::class.java)
        )
    }
}

class MyActivity() : AppCompatActivity() {
    companion object {
        fun start(context: Context) = MyObject.startActivity<MyActivity>(context)
    }
}

But this still seems a little hacky. What I'm looking for is something like inheritance or an implemented version of an interface to simply add to my companion object. Something like:

companion object : MyObject<MyActivity>
// Assuming I can somehow put the generic parameter into the object declaration instead of the function or do something similar

or

companion object {
    import MyObject.startActivity<MyActivity>
}

The first option would be especially useful, as it would automatically set up multiple functions of MyObject for MyActivity. Is something like this even possible?

halfer
  • 19,824
  • 17
  • 99
  • 186
Benjamin Basmaci
  • 2,247
  • 2
  • 25
  • 46

3 Answers3

1

You don't even need a companion, just an extension function.

inline fun <reified T: AppCompatActivity> Activity.startActivity() {
    startActivity(Intent(this, T::class.java))
}

If you need additional configuration, you can pass a lambda.

inline fun <reified T: AppCompatActivity> Activity.startActivity(intentConfiguration: Intent.() -> Unit) {
    startActivity(Intent(this, T::class.java).apply(intentConfiguration))
}
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • Don't you need an instance of a class to call an extension function on it? – Tenfour04 Aug 12 '20 at 12:32
  • 1
    I just noticed that I once even implemented a function similar to this one, just passed the class as a normal parameter of type KClass<*> and gave the extension to Context instead of Activity to make views able to call it without casting. But the lambda is a nice addition here. Good answer!! The only question that remains now is which of those is better performance wise. Companion or Extension. – Benjamin Basmaci Aug 12 '20 at 12:49
1

Companion objects can inherit from other classes. Just make a base implementation somewhere:

abstract class ActivityCompanion<T: Activity>(val activityClass : Class<T>) {
    fun startActivity(context: Context) {
        context.startActivity(Intent(context, activityClass))
    }
    /** rest of functions that reference activityClass **/
}

And inherit it in your companions:

class MyActivity() : AppCompatActivity() {
    companion object : ActivityCompanion<MyActivity>(MyActivity::class.java)
}

It is a little verbose but that's just how generics work.

Pawel
  • 15,548
  • 3
  • 36
  • 36
  • This is what I was looking for. Quick explanation why I picked this answer over (the other)[(https://stackoverflow.com/a/63376412/4687402)], even though it does almost the same thing and both are pretty good. Some activities require values passed through intent and implementing that in a companion object function with an extra paraneter and hiding the tags is just cleaner. So, using this approach would make the whole project more uniform, in that all activities can be called from a static function `startActivity` with all needed parameters. – Benjamin Basmaci Aug 12 '20 at 12:57
  • Also, quck addition: The class doesnt have to be abstract, it can just be `open`. – Benjamin Basmaci Aug 12 '20 at 13:04
  • Sure you can make it open but if it's made specifically to be inherited by companion objects what is the purpose of instantiating it directly? – Pawel Aug 12 '20 at 13:26
  • Good point. I just went there because thats the "default" when using java. You need to finalize a class there to get whats a default class in kotlin. But yeah, if its not instanciated directly, there is no use for that. – Benjamin Basmaci Aug 12 '20 at 13:32
  • I have asked another question, because I wasnt quite pleased with how this solution looks. Although its definetly the right solution, there is an [easy way to make it even neater](https://stackoverflow.com/a/63390459/4687402) – Benjamin Basmaci Aug 13 '20 at 12:08
0

Just want to point out that a more concise syntax would be to do this with a reified extension function on Context. And you wouldn't need to worry about companion objects either.

inline fun <reified T: Activity> Context.startActivity() = 
    startActivity(Intent(this, T::class.java))

//Usage:
someViewOrActivity.startActivity<MyActivity>()

It also semantically makes more sense. A Context starts an activity. An Activity doesn't start itself.

Tenfour04
  • 83,111
  • 11
  • 94
  • 154