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)
}
}
Related
I have implemented a custom Application class in my app which handles updating the app theme before the app start up.
I also registered a network callback to set a variable each time there is a connection change.
My application class is as such:
Application.kt
package com.th3pl4gu3.mes.ui
.....
class MesApplication : Application() {
companion object {
#Volatile
private var INSTANCE: MesApplication? = null
fun getInstance() =
INSTANCE ?: synchronized(this) {
INSTANCE
?: MesApplication().also { INSTANCE = it }
}
}
override fun onCreate() {
super.onCreate()
// Assigns 'this' to the singleton object
INSTANCE = this
// Updates the application's theme
updateAppTheme()
// Start a network callback to monitor internet connection
startNetworkCallback()
}
private fun startNetworkCallback(){
try{
val cm = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val builder = NetworkRequest.Builder()
cm.registerNetworkCallback(builder.build(), object: ConnectivityManager.NetworkCallback(){
override fun onAvailable(network: Network) {
super.onAvailable(network)
Log.v("INTERNET_TEST", "AC: Network Available")
Global.isNetworkConnected = true
}
override fun onLost(network: Network) {
super.onLost(network)
Log.v("INTERNET_TEST", "AC: Network Lost")
Global.isNetworkConnected = false
}
})
Global.isNetworkConnected = false
}catch (e: Exception){
Global.isNetworkConnected = false
}
}
}
However, from the docs, they recommend to unregister this callback but the Application class lifecycle doesn't have any onPause or onDestroy function.
Is there any proper way to unregister this callback to not cause any memory leaks?
Also feel free to suggest any alternatives in case I am coding this wrong
In this case , you can use ActivityLifecycleCallbacks, to detect are any Activity of your is in Foreground?
ActivityLiveCycleListener
class ActivityLiveCycleListener(private val appStateListener: AppStateListener) : Application.ActivityLifecycleCallbacks {
companion object {
var foregroundActivities = 0
}
override fun onActivityPaused(p0: Activity) {
}
override fun onActivityStarted(p0: Activity) {
if(foregroundActivities == 0){
appStateListener.onAppForeGround()
}
foregroundActivities++
}
override fun onActivityDestroyed(p0: Activity) {
}
override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) {
}
override fun onActivityStopped(p0: Activity) {
foregroundActivities--
if(foregroundActivities == 0){
appStateListener.onAppBackground()
}
}
override fun onActivityCreated(p0: Activity, p1: Bundle?) {
}
override fun onActivityResumed(p0: Activity) {
}
}
And your interface can have two methods to indicate background/foreground state
interface AppStateListener{
fun onAppForeGround()
fun onAppBackground()
}
Now in Application onCreate(), register to ActivityLifeCycleListener
override fun onCreate(){
registerActivityLifecycleCallbacks(ActivityLiveCycleListener(object : AppStateListener{
override fun onAppForeGround() {
//start network listener
}
override fun onAppBackground() {
//remove network listener
}
}))
}
I was wondering how to make the music play automatically when starting the app and how to make it stop playing in the background when pressing the home button. Right now, it starts and stops by pressing the toggle button. I was also wondering if its possible to automatically switch to other music when going to another activity?
MainActivity.kt
private lateinit var player: MediaPlayer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val secondActivity = findViewById<Button>(R.id.secondActivity)
secondActivity.setOnClickListener {
val intent = Intent(this, MainActivity2::class.java)
startActivity(intent)
}
val toggle: ToggleButton = findViewById(R.id.toggleButton)
toggle.setOnCheckedChangeListener { _, isChecked ->
val svc = Intent(this, MusicService::class.java)
if (isChecked) {
startService(svc)
} else {
stopService(svc)
}
}
}
MusicService.kt
class MusicService : Service() {
private lateinit var player: MediaPlayer
override fun onBind(intent: Intent?): IBinder? {
TODO("Return the communication channel to the service.")
return null
}
override fun onCreate() {
super.onCreate()
player = MediaPlayer.create(this, R.raw.music)
player.setLooping(true)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
player.start()
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
player.stop()
}
}
Depending on your specific requirements, you should or shouldn't use a MediaService as part of your solution.
To be more clear, a Service should only be used if you want the audio to keep going whenever you're outside the app. This solution will usually be accompanied by a media Notification which you should populate with controls, image assets, etc. (Think of Spotify or SoundCloud) If this is the solution you're looking for, take a look at this doc page from Google and follow it through. Beware that this is a longer and tougher process to maintain.
On the other hand, if all you want to do is play music/sounds while your user is inside your app, then a simple
private lateinit var localMedia: MediaPlayer
override fun onCreate() {
...
localMedia = MediaPlayer.create(context, R.raw.your_audio_file)
}
override fun onResume() {
...
localMedia.start()
}
override fun onPause() {
...
localMedia.release()
}
Furthermore, if you want different audio files to be played on different Activities/Fragments, you might want to abstract the code I provided above into it's own Manager class or so and access it the same but changing the specific .mp3 file (or whatever format) as you see fit.
EDIT:
For a Manager class, you'll have to create your own functions and handle the MediaPlayer inside of it
private class MediaPlayerManager(private val context: Context) {
private lateinit var mediaPlayer: MediaPlayer
fun setupPlayer() {
mediaPlayer = MediaPlayer.create(context, R.raw.your_audio_file)
}
fun play() {
mediaPlayer.start()
}
fun stop() {
mediaPlayer.stop()
}
}
And call these functions from their respective lifecycle method inside your Activity/Fragment, depending on your specific needs
class YourActivity {
val mediaPlayerManager = MediaPlayerManager(context)
override onCreate() {
...
mediaPlayerManager.setupPlayer()
}
override fun onResume() {
...
mediaPlayerManager.play()
}
override fun onPause() {
...
mediaPlayerManager.stop()
}
}
I should add that I'm not necessarily providing a fully-fledged answer here, but a starting point for you to massage to your own needs. The Manager class is nothing but an abstraction of the concept I'm trying to communicate. Lastly, if you want to use a different audio resource file in another Activity/Fragment, you would have to create a method to re-assign the MediaPlayer object inside it with the appropriate file.
E.g.
fun setupPlayer(audioRes: Int) {
mediaPlayer = MediaPlayer.create(context, audioRes)
}
I have done this before
create a Class and named it (for example I named it C) and extend that from Application like following (don't forget put android:name=".C" in <application> tag in manifest.xml):
class C:Application() {
fun onCreate() {
super.onCreate()
context = getApplicationContext()
app = this
}
companion object {
private val context:Context
var currentActivity:Activity
var currentActivities:ArrayList<Activity> = ArrayList()
var handler:Handler
var app:C
fun get():C {
return app
}
fun getContext():Context {
if (currentActivity != null)
{
return currentActivity
}
return context
}
}
}
I created a class for parent of AppCompatActivity and (named UAppCompactActivity)extend it form AppCompatActivity then extend all activities from UAppCompactActivity:
abstract class UAppCompatActivity:AppCompatActivity() {
fun onCreate(#Nullable savedInstanceState:Bundle, #Nullable persistentState:PersistableBundle) {
super.onCreate(savedInstanceState, persistentState)
}
protected fun onResume() {
super.onResume()
C.setCurrentActivity(this)
C.currentActivities.add(this)
/* you can play your music here or do any action you desired */
}
protected fun onPause() {
super.onPause()
C.currentActivities.remove(this)
/*
you can stop your music here or do any action you desired
if (UBase.currentActivities.size() === 0)
G.backgroundMusics.get(G.app.musicNumberBackAndNowPlay).pause()
else
play music or switch to new music
*/
}
}
I want to check that no one has interacted with fragment UI for some time and on the bases of that I want to call a Function/Method inside fragment. Android Studio
You could use a Handler for this and call resetTimeout() when the user does something:
val timeoutHandler = Handler(Looper.getMainLooper()) {
onTimeout()
true
}
fun clearTimeout() = timeoutHandler.removeMessages(0)
fun resetTimeout() =
clearTimeout().also {
timeoutHandler.sendEmptyMessageDelayed(0, TIMEOUT_IN_MILLIS)
}
override fun onResume() {
super.onResume()
resetTimeout()
}
override fun onPause() {
super.onPause()
clearTimeout()
}
private fun onTimeout() {
//we have timed out
}
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 am building an app using the NotificationListenerService. But always when I run the app in debug mode the Service is not started. I reduced my code to the following:
My Acticity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val intent = Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")
startActivity(intent)
}
override fun onResume() {
super.onResume()
val isServiceRunning = isMyServiceRunning(NLService::class.java)
Log.i("MainActivity", "service running: " + isServiceRunning)
}
private fun isMyServiceRunning(serviceClass: Class<*>): Boolean {
val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
for (service in manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.name == service.service.className) {
return true
}
}
return false
}
}
The Service:
class NLService : NotificationListenerService() {
private val TAG: String? = "NLService"
override fun onBind(intent: Intent): IBinder? {
Log.i(TAG, "onBind()")
return super.onBind(intent)
}
override fun onCreate() {
super.onCreate()
Log.i(TAG, "onCreate()")
}
override fun onDestroy() {
super.onDestroy()
}
override fun onNotificationPosted(sbn: StatusBarNotification?) {
Log.i(TAG, "onNotificationPosted() sbn: $sbn")
super.onNotificationPosted(sbn)
}
}
Of course I added this in manifest:
<service
android:name=".NLService"
android:label="MyNLService"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
When starting the app in debug mode then I always get the log output service running: false in onResume. The value is true when starting normally, without debug. What is going wrong and how to fix that?
Okay, finally I don't have a complete solution but a kind of improvement which is close to a working solution.
So what are actually the technical problems in my original app code? And how did I solve them?
I made some initialization in NLService class' onConnect() method. I moved all this initialization to onListenerConnected(), adding a handler.postDelayed(runnable, 500);.
I created an object of a class (Let's call it MyHandlerClass) within the NLService class and passed a reference to the Service into it. This is not a good solution because Android documentation says something about many methods within the NotificationListenerService:
The service should wait for the onListenerConnected() event before performing this operation.
So in MyHandlerClass I called a nlService.getActiveNotifications(). This call was made maybe before Android called NLServices' onListenerConnected. So I made wrappers for methods inside NLService, like e.g.:
fun getActiveNotifications(): Array<StatusBarNotification>?
{
return if (isConnected)
{
super.getActiveNotifications()
}
else
{
null
}
}
And I toggled my boolean variable isConnected within onListenerConnected()and onListenerDisconnected()
Now the service still crashes when running app in debug mode. But running in normal mode the amount of crashes could be reduced by the described improvements.