3

I want to update view from my service class using static method inside my activity so when i have create method in companion object it will now allow to inherit view class inside companion object

Here is the code sample

class MainActivity : AppCompatActivity() {

companion object {
    private const val MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE: Int = 0x01

       fun updateUI(product: Product, activity: Context) {
          /*Error Line*/
          titleMain.text = product.title
       }
    }
}

Service Class

override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        MainActivity.updateUI(product,this)
}

I think this is not the correct way. Is there any other way to achieve the solution?

Thank in advance.

Nilesh Panchal
  • 1,059
  • 1
  • 10
  • 24
  • 1
    *I want to update view from my service class using static method inside my activity* - please don't do this. Instead you should allow the service to inform the activity of events, and let the activity decide what to do. The service should now know anything about how the activity works. You can do this for example by binding the activity to the service and using a 'serviceconnection' – Tim Aug 13 '18 at 10:19

1 Answers1

2

I want to update view from my service class using static method inside my activity so when i have create method in companion object it will now allow to inherit view class inside companion object

As you already are aware then, companion objects are static. Static methods, variables, etc, cannot access non-static variables.

Here's an example:

class Something(var x: Int){
    // The companion object is static
    companion object{
        fun someFun(){
            x = 3;//Unresolved reference: x
        }
    }
}

In Kotlin, x isn't found. Java does a slightly better job at explaining it:

private int x;//getter and setter
private static void someFun(){
    x = 3;//Non-static field cannot be referenced from static context
}

The same applies in Kotlin, but it handles it differently. The point, however, still applies: you cannot access non-static variables from a static class, method, field, or anything else.

To bring this back to your problem, you cannot access the non-static field titleMain from a static object, even if it's nested. It has to be non-static for you to access it.

Before I go on to a possible solution, I want to explain why you shouldn't use static methods at all when it comes to activities.

In order for you to access a non-static field in the activity, you need a static instance of the activity. Or a view for that matter. However, all of these have a Context in them. Context fields should never be static, and IntelliJ/Android Studio will warn you if you try. It can cause memory leaks, which would be the biggest problem here. It also makes instant run unusable, but that's not a problem unless you actually use it. See this Stack Overflow post.

Now, for the solution: Use callbacks. It's more complicated than using static methods, but it doesn't involve memory leaks

You didn't include that much code, so I've written most of it from scratch. It should still give you some pointers on getting started.

First off, you'll need to add an interface to the service, containing all the callback methods. It could be one, or it could be 100. Declare as many as you need, with or without return values and arguments.

Here is an example Service class. There are comments explaining what everything does.

class SomeService : Service(){
    private val binder = SomeServiceBinder()
    private var callback: SomeServiceCallback? = null

    // The rest of the service

    /**
     * In this method, you return the binder instance created earlier. It's necessary
     * for the connection to the Activity
     */
    override fun onBind(intent: Intent?): IBinder {
        // Do something here if necessary
        return binder;
    }

    /**
     * Method containing example use of callbacks
     */
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // If a callback has been registered, call the callback
        // Could be done in a separate thread, after some action, before some action,
        // TL;DR: anywhere you'd like.
        callback?.updateUI(product)

        // The return value could of course be anything.
        return START_STICKY
    }

    /**
     * Register the callback, or null for removing it
     */
    fun registerCallback(callback: SomeServiceCallback?){
        this.callback = callback
    }

    /**
     * The binder. Contains a `getService` method, returning the active instance of the service.
     */
    inner class SomeServiceBinder : Binder() {
        fun getService() = this@SomeService
    }

    /**
     * Add methods to this as you need. They can have arguments, or not. They can have a return type, or not. It's up to you
     */
    interface SomeServiceCallback{
        fun updateUI(product: Product);
    }
}

And finally, the activity. In addition to extending an Activity (here: AppCompatActivity), it also implements the callback interface.

In addition, it implements ServiceConnection. The ServiceConnection could also be an inner class, or declared as a field.

class SomeActivity : AppCompatActivity(), SomeService.SomeServiceCallback, ServiceConnection{
    /**
     * Stored externally to help with shutdown
     */
    lateinit var someServiceIntent: Intent

    override fun callbackForSomething(product: Product) {
        println("Service called activity!")
        runOnUiThread{
            titleMain.text = product.title;
        }
    }

    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)
        /**
         * The intent needs to be created here. if it's created outside, it might reference the context before it's
         * been initialized, which makes it throw an NPE
         */
        someServiceIntent = Intent(this, SomeService::class.java)
    }

    private fun connectToService(){

        startService(someServiceIntent)// This is the normal part. You need this to start the service
        // However, it doesn't register a ServiceConnection. In addition to that, you also need
        // to call bindService:
        bindService(someServiceIntent, this, Context.BIND_AUTO_CREATE)
    }

    private fun disconnect(){
        //Equivalently, on shutdown, there's an extra call
        // First stop the service
        stopService(someServiceIntent)
        // Then unbind it
        unbindService(this)
    }


    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        println("The service is connected")
        /**
         * This uses an unsafe cast since there is just one service and one service binder. If you have multiple,
         * use a when statement or something else to check the type
         */
        val binder = service as SomeService.SomeServiceBinder? ?: return
        binder.getService().registerCallback(this)
    }

    override fun onServiceDisconnected(name: ComponentName?) {
        TODO("Do something here if you want")
    }

}
Zoe
  • 27,060
  • 21
  • 118
  • 148