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!
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
}
}