Search code examples
androidkotlinservice

How to StartService outside of OnCreate methot/outside of activity? (beginner)


Everybody hello. Currently, I'm building a part of my app that by press of a button should enable and disable GPS location tracking.

The problem is that I can't understand how to properly start it and stop it by press of a button.

Here is my code:

package red.button.flipper_navigator.ui

import android.Manifest
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.lifecycle.ViewModel

import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import red.button.flipper_navigator.ui_logic.MainScreenLogic
import red.button.flipper_navigator.R
import red.button.flipper_navigator.databinding.ScreenMainBinding
import red.button.flipper_navigator.databinding.TextFieldBinding
import red.button.flipper_navigator.gps.LocationService
import red.button.flipper_navigator.gps.gps_activation


@Suppress("DEPRECATION")
class MainScreen : AppCompatActivity(), gps_activation {
    private lateinit var bindingMain: ScreenMainBinding
    private lateinit var bindingField: TextFieldBinding
    private var context: Context? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ActivityCompat.requestPermissions(
            this,
            arrayOf(
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.POST_NOTIFICATIONS
            ),
            0
        )

        bindingMain = DataBindingUtil.setContentView(this, R.layout.screen_main)
        bindingField = bindingMain.textField

        val viewModel = ViewModelProvider(this)[MainScreenLogic::class.java]

        bindingMain.mainview = viewModel
        bindingField.textfieldview = viewModel
        context =  getApplicationContext()

//        Intent(context, LocationService::class.java).apply {
//            action = LocationService.ACTION_START
//            startService(this)
//        }

    }

    override fun start() {
        Intent(context, LocationService::class.java).apply {
            action = LocationService.ACTION_START
            startService(this)
        }
    }

    override fun stop() {
        Intent(context, LocationService::class.java).apply {
            action = LocationService.ACTION_STOP
            startService(this)
        }
    }
}

//Organise string. Evidence link, make all strange symbols disappear
//val str: String = (" " + editText.text.toString() + " ").replace("\n", " ")

//
// Toast.makeText(this, fLink, Toast.LENGTH_SHORT).show()



this is my gps_activation interface

package red.button.flipper_navigator.gps

import android.content.Context
import red.button.flipper_navigator.ui.MainScreen

interface gps_activation {
    fun start()
    fun stop()

    companion object {
        fun create(): gps_activation {
            return MainScreen()
        }
    }
}



And here is my View Model where I trigger Start function to start the service.

package red.button.flipper_navigator.ui_logic

import android.app.Application
import android.content.Context
import android.content.Intent
import android.util.Log
import android.view.View
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import red.button.flipper_navigator.core.impl.link_logic.TextRef
import red.button.flipper_navigator.data.remote.GetLink
import red.button.flipper_navigator.gps.LocationService
import red.button.flipper_navigator.gps.gps_activation

@OptIn(ExperimentalCoroutinesApi::class)
@Suppress("UNUSED_PARAMETER")
class MainScreenLogic() : ViewModel() {
    var link: String = ""

    val gpsSet = gps_activation.create()
    fun generateBt(view: View) {

        val linkUnzip = TextRef.create()
        val service = GetLink.create()

        val glink = viewModelScope.async {
            service.getPosts(linkUnzip.linkUnfolder(link))
        }
        glink.invokeOnCompletion {
            if (it == null) {
                Log.d("link111", linkUnzip.placeFind(glink.getCompleted())[1])

            }
        }
        Log.d("link111", "ok")

    }

    fun connectBt(view: View) {
    }


// This Button i'm trying to use to start gps //
    fun StartNavBt(view: View) {

        Log.d("gps", "ok")
        gpsSet.start()

    }


}


I'm getting this error:

java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
                                                                                                        at android.content.ComponentName.<init>(ComponentName.java:131)
                                                                                                        at android.content.Intent.<init>(Intent.java:6858)
                                                                                                        at red.button.flipper_navigator.ui.MainScreen.start(MainScreen.kt:58)
                                                                                                        at red.button.flipper_navigator.ui_logic.MainScreenLogic.StartNavBt(Main_screen_logic.kt:47)
                                                                                                        at red.button.flipper_navigator.databinding.ScreenMainBindingImpl$OnClickListenerImpl1.onClick(ScreenMainBindingImpl.java:189)
                                                                                                        at android.view.View.performClick(View.java:7448)
                                                                                                        at android.view.View.performClickInternal(View.java:7425)
                                                                                                        at android.view.View.access$3600(View.java:810)
                                                                                                        at android.view.View$PerformClick.run(View.java:28305)
                                                                                                        at android.os.Handler.handleCallback(Handler.java:938)
                                                                                                        at android.os.Handler.dispatchMessage(Handler.java:99)
                                                                                                        at android.os.Looper.loop(Looper.java:223)
                                                                                                        at android.app.ActivityThread.main(ActivityThread.java:7656)
                                                                                                        at java.lang.reflect.Method.invoke(Native Method)
                                                                                                        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
                                                                                                        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

With a press of a button I'm starting a function from the interface, that I initialize in the main activity, but I'm always getting an error.

Maybe I'm doing it completely wrong? And I should make it with some dependencies? I'm still learning, and it could.

With a press of a button I'm starting a function from the interface, that I initialize in the main activity, but I'm always getting an error.

Perhaps I'm doing it completely wrong? And I should make it with some dependencies? I'm still learning, and it could that the procedure is outdated or wrong.

Thank you for any help!


Solution

  • Root cause of the problem is in this line:

    return MainScreen()
    

    Activities, Services and other components of Android are managed by the Android platform. They are created automatically when needed, they go through their initialization process, they run in a context created by the platform. We can't create these objects directly and expect them to be working properly.

    Generally, ViewModel should know nothing about the activity, it shouldn't keep references to it or call its methods. Model is for handling data, not performing actions. What we usually do is to expose some information in ViewModel in a form of Flow or LiveData. We then observe this data in the activity and perform actions in response to the changing data.

    It would be hard to provide a fully working example for you, but it could be something along lines:

    class MainScreen : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            ...
            lifecycleScope.launch {
                viewModel.gpsEnabled.collect {
                    if (it) start() else stop()
                }
            }
        }
    }
    
    class MainScreenLogic : ViewModel() {
        private val _gpsEnabled = MutableStateFlow(false)
        val gpsEnabled = _gpsEnabled.asStateFlow()
    
        fun StartNavBt(view: View) {
            _gpsEnabled.value = true
        }
    }