I've followed a tutorial to build a timer application. The tutorial created methods in the MainActivity that creates and destroys the timer. Right now, I am trying to stop the timer when the user leaves the application. I am using a LifeCycleObserver to call when Lifecycle.Event.ON_STOP occurs and the app goes to the background.
I want to call a method called onTimerFinished() in the Main Activity when the user leaves the application
When I try to call the method in my LifeCycleObserver, it returns an error that it's an unresolved reference.
This is the LifecycleObserver where I am trying onTimerFinished
class ApplicationObserver() : LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onBackground() {
Log.d("myTag", "App closed")
MainActivity.onTimerFinished()
}
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onForeground() {
}
}
This is the function onTimerFinished which is located in my MainActivity
private fun onTimerFinished(){
timerState = TimerState.Stopped
setNewTimerLength()
progress_countdown.progress = 0
PrefUtil.setSecondsRemaining(timerLengthSeconds,this)
secondsRemaining = timerLengthSeconds
updateButtons()
updateCountdownUI()
}
When I move variables into the companion object for MainActivity, it doesn't seem to change the actual timer. Rather it changes the variables for the companion object.
How can I call this function in my LifecycleObserver
You can't call MainActivity private fun directly. You need a reference of it and have to make onTimeFinished method public.
in MainActivity
fun onTimerFinished(){
timerState = TimerState.Stopped
setNewTimerLength()
progress_countdown.progress = 0
PrefUtil.setSecondsRemaining(timerLengthSeconds,this)
secondsRemaining = timerLengthSeconds
updateButtons()
updateCountdownUI()
}
lifecycleobserver
class ApplicationObserver(mainActivity: MainActivity) : LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onBackground() {
Log.d("myTag", "App closed")
mainActivity.onTimerFinished()
}
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onForeground() {
}
}
you can pass this as parameter when you create ApplicationObserver object in MainActivity like
val applicationObserver = ApplicationObserver(this)
Related
I am new at Kotlin and trying to implement MVP Architecture,
Currently I am having problem initializing/setting textview's value outside onCreate() method
here is my code
SplashActivity.kt
class SplashActivity : AppCompatActivity(), Splash.ViewInterface {
lateinit var appDetail: AppDetail
lateinit var textTitle: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textTitle = findViewById(R.id.splash_txt_title) as TextView
AppSingleton.appContext = applicationContext
var splashPresentation = SplashPresentation(this)
splashPresentation.getAppDetailFromService()
}
override fun fetchAppDetailSuccessful(response: SplashServiceObject) {
AppSingleton.initializeAppDetal(Gson().fromJson(response.json_string, AppDetail::class.java))
this.appDetail = AppSingleton.appDetail
}
override fun fetchAppDetailFailed(errMsg: String) {
textTitle.text = errMsg
}
}
SplashPresenter.kt
class SplashPresentation(private val view: Splash.ViewInterface) : Splash.PresentationInterface {
fun getAppDetailFromService() {
var splashService = SplashService()
splashService.getAppDetailFromAssets(this)
}
override fun fetchAppDetailFromServiceSuccessful(response: SplashServiceObject) {
view.fetchAppDetailSuccessful(response)
}
override fun fetchAppDetailFromServiceFailed(errMsg: String) {
view.fetchAppDetailFailed(errMsg)
}
}
SplashService.kt
class SplashService {
fun getAppDetailFromAssets(splashPresentation: SplashPresentation) {
val json_filename = "appdetail.json"
var jsonResponse: JsonResponse = AppSingleton.commonUtils.fetchJsonFromAssets(json_filename, AppSingleton.appContext!!)
if (jsonResponse.json_status) {
var splashServiceObj = SplashServiceObject
splashServiceObj.json_string = jsonResponse.json_info
splashServiceObj.response_msg = "JSON Successful fetched."
splashPresentation.fetchAppDetailFromServiceSuccessful(splashServiceObj)
} else {
splashPresentation.fetchAppDetailFromServiceFailed(jsonResponse.json_info)
}
}
}
in my SplashActivity().onCreate(), I am calling a Presenter that access Service, then the Service return a value to Presenter,
Then Presenter, return value to my SplashActivity's View, one of the function is, fetchAppDetailFailed(errMsg)
when I run the app, it crashes, saying the "textaa" is not yet initialized.
back in Java exp, when the variable is already instantiated on onCreate(), you can call this variable anywhere within the activity.
Thanks in advance!
You cannot instantiate Activities on Android. They are instantiated by the OS, and the OS calls the lifecycle methods on it.
In an MVP pattern, the View and Presenter both reference each other. Since Activity (the View) is the entry point of the application, your Activity should instantiate the Presenter and pass a reference of itself to the Presenter so communication can go both ways.
Also, the reference to the activity in the Presenter should be specified as a ViewInterface, not an Activity, or you're kind of defeating the purpose of using MVP.
class SplashPresentation(private val view: Splash.ViewInterface) : Splash.PresentationInterface {
//... methods that call functions on view
}
class SplashActivity : AppCompatActivity(), Splash.ViewInterface {
private val presenter = SplashPresentation(this)
//...
}
I use foreground service for playing music.
I've done research and figured out that process keep living even if I call stopForeground(true) and stopSelf(). It doesn't seem to work if I call it from the onDestroy of my activity. But if I call them from another place for example when user clicks button, it works.
But how to do that my service to be killed when app is killed.
private const val EMPTY_ROOT = "emptyRoot"
private val TAG = SoundsService::class.java.name
class SoundsService : MediaBrowserServiceCompat() {
#Inject
lateinit var mediaSession: MediaSessionCompat
#Inject
lateinit var callback: MediaSessionCallback
override fun onCreate() {
inject()
super.onCreate()
mediaSession.apply {
setCallback(callback)
isActive = true
}
sessionToken = mediaSession.sessionToken
}
override fun onGetRoot(
clientPackageName: String,
clientUid: Int,
rootHints: Bundle?
): BrowserRoot? {
return BrowserRoot(EMPTY_ROOT, null)
}
override fun onLoadChildren(
parentId: String,
result: Result<MutableList<MediaBrowserCompat.MediaItem>>
) {}
override fun onDestroy() {
mediaSession.run {
isActive = false
release()
}
stopForeground(false)
stopSelf()
super.onDestroy()
}
}
It will work in the onDestroy. Just make sure your are calling it, before super call. And use ApplicationContenxt for that as well.
applicationContext.startForegroundService(Intent(this, ServiceTest::class.java));
applicationContext.stopService(Intent(this, ServiceTest::class.java))
Have you checkt for any crashes when you call stopForeground(true) or stopSelf()
I have a ViewModel that inherits from a base class and I would like to have a corresponding Activity also inherit from a base class. The activity would call the same method of the derived ViewModel each time. So it would be something like this:
BaseViewModel:
abstract class BaseViewModel(application: Application) : AndroidViewModel(application) {
protected val context = getApplication<Application>().applicationContext
protected var speechManager: SpeechRecognizerManager? = null
var _actionToTake : MutableLiveData<AnalyseVoiceResults.Actions> = MutableLiveData()
var actionToTake : LiveData<AnalyseVoiceResults.Actions> = _actionToTake
open fun stopListening() {
if (speechManager != null) {
speechManager?.destroy()
speechManager = null
}
open fun startListening() {
val isListening = speechManager?.ismIsListening() ?: false
if (speechManager == null) {
SetSpeechListener()
} else if (!isListening) {
speechManager?.destroy()
SetSpeechListener()
}
}
}
BaseActivity
class BaseActivity : AppCompatActivity() {
private lateinit var baseViewModel: BaseViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
fun goback() {
super.onBackPressed()
baseViewModel.stopListening()
finish()
}
fun startListening() {
baseViewModel.startListening()
}
override fun onDestroy() {
super.onDestroy()
baseViewModel.stopListening()
}
}
Derived Activity:
class DerivedActivity : BaseActivity() {
private val nextActivityViewModel: NextActivityViewModel by inject()
///^^inherits from BaseViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/*** pass reference ***/
baseViewModel = nexActivityViewModel
nextActivityViewModel.actionToTake.observe(this, object : Observer<AnalyseVoiceResults.Actions?> {
override fun onChanged(t: AnalyseVoiceResults.Actions?) {
if (t?.equals(AnalyseVoiceResults.Actions.GO_BACK) ?: false) {
goback()
}
}
})
startListening()
}
}
Will this cause memory leaks to have two instances of a view model for this activity? Is there a better way to do this? I don't want to keep repeating the same code for all my activities. (I would also have the same question if I was doing this with one base fragment).
make this var baseViewModel: BaseViewModel an abstract variable where all the children class must override it. So, when you call the startListening and stopListening, these methods will be called from children implementation.
Edit:
Make the BaseActivity an abstract class and the baseViewModel as an abstract variable
abstract class BaseActivity : AppCompatActivity() {
private abstract var baseViewModel: BaseViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
fun goback() {
super.onBackPressed()
baseViewModel.stopListening()
finish()
}
fun startListening() {
baseViewModel.startListening()
}
override fun onDestroy() {
super.onDestroy()
baseViewModel.stopListening()
}
}
So, your DerivedActivity must override the baseViewModel, and every call on father's class will trigger the child
class DerivedActivity : BaseActivity() {
override val baseViewModel: NextActivityViewModel by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Will this cause memory leaks to have two instances of a view model for
this activity?
No, there are no memory leaks with this approach. Nor do you have 2 instances of the ViewModel for the same activity. It's a single instance of ViewModel which is being referenced with different variables in BaseActivity and BaseViewModel.
Is there a better way to do this?
The first issue I see is that you have Android specific code in the ViewModels, which is not considered a good practice. You should move the speech manager code to the base activity itself, and ViewModel should only hold the "state" data that you want to retain on orientation changes. This will ensure all the Speech Management methods (create, resume, destroy) will be in the base activity. Concrete activity will only have observers if the state changes.
If you are following any architecture pattern (like MVP), once you move the Speech Manager code out to activity, it would become obvious to move this further out to the Presenter.
EDIT: I have not used the MVVM pattern in production, but this is a light variant of what you may want:
The basic idea is to move Speech management code in a lifecycle-aware component. All UI code in view/activity and business logic / non-android state in viewmodel. I don't see a point in having base activity or viewmodel based on the requirements you have shared so far.
/**
* All the speech related code is encapsulated here, so any new activity/fragment can use it by registering it's lifecycle
*/
class SpeechManager(private val context: Context): LifecycleObserver {
val TAG = "SpeechManager"
private var speechRecognizer: SpeechRecognizer? = null
fun registerWithLifecycle(lifecycle: Lifecycle) {
Log.e(TAG, "registerWithLifecycle")
lifecycle.addObserver(this)
}
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun start() {
Log.e(TAG, "start")
speechRecognizer = (speechRecognizer ?: SpeechRecognizer.createSpeechRecognizer(context)).apply {
// setRecognitionListener(object : RecognitionListener {
// //implement methods
// })
}
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stop() {
Log.e(TAG, "stop")
speechRecognizer?.run {
stopListening()
destroy()
}
}
}
ViewModel:
class SpeechViewModel: ViewModel() {
val TAG = "SpeechViewModel"
//List all your "data/state" that needs to be restores across activity restarts
private val actions: MutableLiveData<Actions> = MutableLiveData<Actions>().apply { value = Actions.ActionA }
//Public API for getting observables and all use-cases
fun getActions() = actions
fun doActionA(){
//validations, biz logic
Log.e(TAG, "doActionA")
actions.value = Actions.ActionA
}
fun doActionB(){
Log.e(TAG, "doActionB")
actions.value = Actions.ActionB
}
}
sealed class Actions{
object ActionA: Actions()
object ActionB: Actions()
}
Activity/View:
class SpeechActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_speech)
setSupportActionBar(toolbar)
initSpeechManager()
}
private lateinit var speechManager: SpeechManager
private lateinit var speechViewModel: SpeechViewModel
/**
* Register lifecycle aware components and start observing state changes from ViewModel.
* All UI related code should ideally be here (or your view equivalent in MVVM)
*/
private fun initSpeechManager() {
speechManager = SpeechManager(this).apply {registerWithLifecycle(lifecycle)}
speechViewModel = ViewModelProviders.of(this).get(SpeechViewModel::class.java).apply {
getActions().observe(this#SpeechActivity, Observer<Actions>{
when(it){
is Actions.ActionA -> {
Log.e(TAG, "Perform ActionA")
speechManager.start()
}
is Actions.ActionB -> {
Log.e(TAG, "Perform ActionB")
speechManager.stop()
super.onBackPressed()
}
}
})
}
}
}
I want to disable the WiFi when the app is closed.
i know the code to disable WiFi using this line :
wifiManager!!.isWifiEnabled = false
but i don't know how to detect the closing of the app.
This exactly what lifecycles are used for. Any clean up work that needs to done should be done in onDestroy(). This is the final call you receive before your activity is destroyed. So in the activity where you want to disable wifi you can just do:
override func onDestroy() {
super.onDestroy();
wifiManager!!.isWifiEnabled = false;
}
You might check out this blog post. It described how to do it more detail than I could.
EDIT:
Important parts of blog post are:
1 - Create our interface that will be implemented by a custom Application class:
interface LifecycleDelegate {
fun onAppBackgrounded()
fun onAppForegrounded()
}
2 - Now we a class that is going to implement the ActivityLifecycleCallbacks and ComponentCallbacks2:
class AppLifecycleHandler(
private val lifeCycleDelegate: LifeCycleDelegate
) : Application.ActivityLifecycleCallbacks, ComponentCallbacks2
{
private var appInForeground = false
override fun onActivityResumed(activity: Activity?) {
if (!appInForeground) {
appInForeground = true
lifeCycleDelegate.onAppForegrounded()
}
}
override fun onTrimMemory(level: Int) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
appInForeground = false
lifeCycleDelegate.onAppBackgrounded()
}
}
// stub other methods
}
3 - We need to use that handler in our application class:
class App : Application(), LifeCycleDelegate {
override fun onCreate() {
super.onCreate()
val lifeCycleHandler = AppLifecycleHandler(this)
registerLifecycleHandler(lifeCycleHandler)
}
override fun onAppBackgrounded() {
Log.d("App", "App in background")
}
override fun onAppForegrounded() {
Log.d("App", "App in foreground")
}
private fun registerLifecycleHandler(lifeCycleHandler: AppLifecycleHandler) {
registerActivityLifecycleCallbacks(lifeCycleHandler)
registerComponentCallbacks(lifeCycleHandler)
}
}
Having a singleton dataManager object which registers the OTTO listener in its constructor to listen to data ready event.
In the application the dataManager is referred anywhere as
DataManager.instance.functionOfIt()
If not call GlobalOttoBus.instance.unregister(this) for this dataManger, then it’s listener function (fun getMessage(event: DataEvent)) may get call multiple times after reopen the app.
class DataManager {
private var mApplication: Application? = null
companion object {
val TAG = DataManager::class.java.name
private var dataMgr: DataManager? = null
val instance: DataManager?
#Synchronized get() {
return dataMgr
}
}
constructor(application: Application) {
mApplication = application
dataMgr = this
Log.e(TAG, "+++ DataManager:ctor(), GlobalOttoBus.instance.register()");
// have no way to to unregister in this DataManager ??? ???
GlobalOttoBus.instance.register(this)
}
#Subscribe
fun getMessage(event: DataEvent) {
Log.e(Tag, "+++ dataMgr::getMessage(), ${this}, ${event}")
onDataReady(event)
}
… …
}
Where the data is ready it posts the data to otto bus:
override fun onResponse(call: Call?, response: Response?) {
if (response!!.isSuccessful()) {
// parse the data ……
// then post data ready event
var dataEvt: DataEvent = DataEvent()
dataEvt.setPostData(posts)
Log.d(“GsonParse”, "+++ onResponse(), call GlobalOttoBus.instance.post(dataEvt): ${dataEvt} ")
GlobalOttoBus.instance.post(dataEvt)
}
It is observed when minimize the app (os destroys the application) and then reopen the app this DataManager’s constructor is called (a new instance in this new session) so it does the OTTO register listener again in it’s constructor.
The problem is where to unregister from the OTTO, or more of how to manage this singleton DataManager’s life cycle? After minimize the app and then reopen the app, seems the previous one is still alive/listen but not in the new app’s scope.
This is the trace
first time start app, the constructor:
08-19 11:32:33.558 5296-5296/xxxapplication E/DataManager: +++ DataManager:ctor(), GlobalOttoBus.instance.register(), this: DataManager#94fd499
after minimize the app and the reopen the app, the constructor is called again a new instance
08-19 11:34:14.141 5296-5296/xxxapplication E/DataManager: +++ DataManager:ctor(), GlobalOttoBus.instance.register(), DataManager#661f37d
one post:
08-19 11:34:15.242 5296-5380/xxxapplication W/GsonParse: +++ onResponse(), call GlobalOttoBus.instance.post(dataEvt): DataEvent#f5a0df6
two listeners called on different dataManager instances:
08-19 11:34:15.242 5296-5380/xxxapplication E/DataManager: +++ dataMgr::getMessage(), DataManager#94fd499, DataEvent#f5a0df6
08-19 11:34:15.395 5296-5380/xxxapplication E/DataManager: +++ dataMgr::getMessage(), DataManager#661f37d, DataEvent#f5a0df6
Find a workaround, implement the LifecycleRegistryOwner in mainActivity, so on the lifeCycle Lifecycle.Event.ON_DESTROY it means the activity is killed, so unregister the DataManger’s otto listener.
class MainActivity : AppCompatActivity(), LifecycleRegistryOwner {
private val mRegistry: LifecycleRegistry = LifecycleRegistry(this);
private var theLifeCycleObserve: TheLifeCycleObserve? = null
override fun getLifecycle() : LifecycleRegistry {
return mRegistry
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val toolbar = findViewById<Toolbar>(R.id.toolbar) as Toolbar
setSupportActionBar(toolbar)
……
theLifeCycleObserve = TheLifeCycleObserve(lifecycle, object : TheLifeCycleObserve.OnLifeCycleChange {
override fun onStar() {
}
override fun onDestroy() {
DataManager.instance!!.unregisterOttoBus()
lifecycle.removeObserver(theLifeCycleObserve)
}
})
lifecycle.addObserver(theLifeCycleObserve)
}
………
}
class TheLifeCycleObserve(private var lifecycle: LifecycleRegistry?, private var lifeCycleChangeListener: OnLifeCycleChange?) : LifecycleObserver {
interface OnLifeCycleChange {
fun onStar()
fun onDestroy()
}
init {}
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onStart() {
lifeCycleChangeListener!!.onStar()
}
#OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
lifeCycleChangeListener!!.onDestroy()
}
}