Proper way to unregister a callback from an Application class - android

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

Related

An issue with App Open Ads in Kotlin | Android

App Open Ads-
I want to show ads with the first launch "onStart() function" but,
The problem is that the Ads don't show in the first launch (onStart() function) it shows in the onResume() function after I exit the application and resume it.
Are there any Suggestions or Editing Guys?
MyApplication Class
class MyApplication : Application() {
private var appOpenManager: AppOpenManager? = null
override fun onCreate() {
super.onCreate()
MobileAds.initialize(this) {
Log.d("tag", "MobileAds init ")
}
appOpenManager = AppOpenManager(this)
ProcessLifecycleOwner.get().lifecycle.addObserver(appOpenManager!!.defaultLifecycleObserver)
}
}
AppOpenManager Class
class AppOpenManager(private val myApplication: MyApplication) :
Application.ActivityLifecycleCallbacks,
LifecycleObserver {
private var appOpenAd: AppOpenAd? = null
private var loadCallback: AppOpenAd.AppOpenAdLoadCallback? = null
private var currentActivity: Activity? = null
private var isShowingAd = false
private val adRequest: AdRequest
get() = AdRequest.Builder().build()
private val isAdAvailable: Boolean
get() = appOpenAd != null
companion object {
private const val AD_UNIT_ID = "ca-app-pub-3940256099942544/3419835294" //test id
}
init {
myApplication.registerActivityLifecycleCallbacks(this)
}
private fun fetchAd() {
if (isAdAvailable) {
return
} else {
Log.d("tag", "fetching... ")
loadCallback = object : AppOpenAd.AppOpenAdLoadCallback() {
override fun onAdFailedToLoad(p0: LoadAdError) {
super.onAdFailedToLoad(p0)
Log.d("tag", "onAppOpenAdFailedToLoad: ")
}
override fun onAdLoaded(ad: AppOpenAd) {
super.onAdLoaded(ad)
appOpenAd = ad
Log.d("tag", "isAdAvailable = true")
}
}
val request = adRequest
AppOpenAd.load(
myApplication,
AD_UNIT_ID,
request,
AppOpenAd.APP_OPEN_AD_ORIENTATION_PORTRAIT,
loadCallback!!
)
}
}
fun showAdIfAvailable() {
Log.d("tag", "$isShowingAd - $isAdAvailable")
if (!isShowingAd && isAdAvailable) {
Log.d("tag", "will show ad ")
val fullScreenContentCallback: FullScreenContentCallback =
object : FullScreenContentCallback() {
override fun onAdDismissedFullScreenContent() {
appOpenAd = null
isShowingAd = false
fetchAd()
}
override fun onAdFailedToShowFullScreenContent(p0: AdError) {
}
override fun onAdShowedFullScreenContent() {
isShowingAd = true
}
}
appOpenAd!!.fullScreenContentCallback = fullScreenContentCallback
appOpenAd!!.show(currentActivity!!)
} else {
Log.d("tag", "can't show ad ")
fetchAd()
}
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
currentActivity = activity
}
override fun onActivityStarted(activity: Activity) {
currentActivity = activity
}
override fun onActivityResumed(activity: Activity) {
currentActivity = activity
}
override fun onActivityPaused(activity: Activity) {
}
override fun onActivityStopped(activity: Activity) {
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
currentActivity = activity
}
override fun onActivityDestroyed(activity: Activity) {
currentActivity = null
}
var defaultLifecycleObserver = object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
Log.d("tag", "onStart()")
showAdIfAvailable()
}
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
Log.d("tag", "onCreate() ")
}
}
}
onStart calls showAdIfAvailable() which only shows one if isAdAvailable is true, but it's initialised as false - so it calls fetchAd() instead. All that does is store an ad (so isAdAvailable is true for the next time you hit onStart).
Maybe you want to call showAdIfAvailable() again from your success callback when you fetch one?
override fun onAdLoaded(ad: AppOpenAd) {
super.onAdLoaded(ad)
appOpenAd = ad
Log.d("tag", "isAdAvailable = true")
// you got an ad - try and display it
showAdIfAvailable()
}
Just be careful, because your ads are being fetched asynchronously, it's possible your Activity could be destroyed while that request is still ongoing. That means you'd set currentActivity to null, your fetch would succeed and run showAdIfAvailable, and that tries to use currentActivity.
You need to null-check that - none of this !! stuff
if (!isShowingAd && isAdAvailable) {
val fullScreenContentCallback = object : FullScreenContentCallback() {
...
}
// ensure neither of these are null -while we're using them-
// let and run basically create new variables that can't be changed by callbacks etc
currentActivity?.let { activity ->
appOpenAd?.run {
fullScreenContentCallback = fullScreenContentCallback
show(activity)
}
}
Not necessarily the most elegant way (it would be better to null-check first so you don't do anything you don't need to, like creating that callback object which might not be used) but I don't want to rewrite your code. You get the idea hopefully!

How to use Coroutine in singleton properly

I want to create a singleton with Coroutine to load image from network. I have done implement the singleton and can load network image into imageView. Here is my singleton class.
class Singleton(context: Context) {
private val TAG = "Singleton"
private val scope =
CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineExceptionHandler { _, exception ->
Log.e(TAG, "Caught $exception")
})
private var job:Job? = null
companion object {
private var INSTANCE: Singleton? = null
#Synchronized
fun with(context: Context): Singleton {
require(context != null) {
"ImageLoader:with - Context should not be null."
}
return INSTANCE ?: Singleton(context).also {
INSTANCE = it
Log.d("ImageLoader", "First Init")
}
}
}
private fun onAttachStateChange(imageView: ImageView, job: Job) {
imageView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View?) {
}
override fun onViewDetachedFromWindow(v: View?) {
job.cancel()
}
})
}
fun loadImage(url: String, imageView: ImageView) {
job = scope.launch {
try {
updateData(URL(url), imageView)
} catch (e: CancellationException) {
Log.d(TAG, "work cancelled!")
}
}.also {
onAttachStateChange(imageView, it)
}
}
suspend fun updateData(url: URL, imageView: ImageView) = run {
fetchImage(url)?.apply { imageView.setImageBitmap(this) }
?: imageView.setImageResource(R.drawable.ic_launcher_background)
}
fun stopUpdate() {
scope.cancel()
}
private suspend fun fetchImage(url: URL): Bitmap? {
return withContext(Dispatchers.IO) {
try {
val connection = url.openConnection() as HttpURLConnection
val bufferedInputStream = BufferedInputStream(connection.inputStream)
BitmapFactory.decodeStream(bufferedInputStream)
} catch (e: Exception) {
Log.e("TAG", e.toString())
null
}
}
}
}
My problem is when I cancel my coroutine scope in onDestroy() at ActivityB and than use my singleton again in ActivityA it won't do anything cause the scope have been cancel(). So is there any way to use Coroutine in singleton properly with scope.cancel() when activity is onDestroy(). Here is a demo:
class MainActivityA : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_activity)
Singleton.with(this).updateData(url, imageView)
}
}
class MainActivityB : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_activity)
}
override fun onDestroy() {
super.onDestroy()
// Do not need to call scope.cancel(). Cause when the view is
// detached it will cancel the job.
// Singleton.with(this).stopUpdate()
}
}
Edited
I have come up with an idea and have added into Singleton class. Using view.onAttachStateChange to detect whether the view is still attached to the window. If is detached then we can cancel the job. Is this a good way to doing so?
Singleton by definition lives forever, so I'm not really sure it makes sense to cancel its scope. What if you would need to use your singleton from multiple components at the same time? They would cancel jobs of other components.
To make sure you don't leak jobs of destroyed components, you can either create a child job per component and put all tasks under it or just do not define a custom scope at all and reuse the coroutine context of the caller.

How do I notify Workmanager task completed to Service?

My Worker(for API call) starts from Service and I want to completion event send into Service class.
What should be best approach?
Calling from service:
PeriodicWorkRequest request = new PeriodicWorkRequest
.Builder(Worker.class, Constants.REPEAT_INTERVAL, TimeUnit.MINUTES)
.addTag(TAG)
.setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build();
WorkManager.getInstance(getApplicationContext()).enqueue(request);
Calling from WorkManager:
override fun doWork(): Result {
// API call
return Result.success()
}
Okay so what I would do is I would create common object for both Worker and Service class and utilize Observer pattern. This WorkObserver object would behave as a proxy between Service and Worker. Using Koin for example, it would look something like that:
class MyWorker: Worker(), KoinComponent {
private val workObserver: WorkObserver by inject()
override fun doWork(): Result {
val result = api.call().execute()
if(result.isSuccessful) {
workObserver.notifySuccess()
return Result.success()
} else {
workObserver.notifyError()
return Result.failure()
}
}
}
class MyService: Service(), KoinComponent {
private val workObserver: WorkObserver by inject()
override fun onCreate() {
workObserver.setOnResultListener { result ->
if(result) {
//do something
} else {
// do something else
}
}
override fun onDestroy() {
workObserver.setOnResultListener(null)
}
}
class WorkObserver {
private var onResultListener: ((Result) -> Unit)? = null
fun setOnResultListener(listener: ((Result) -> Unit)?) {
this.onResultListener = listener
}
fun notifySuccess() {
this.onResultListener?.invoke(true)
}
fun notifyError() {
this.onResultListener?.invoke(false)
}
}
Of course you can use other DI tools for that, you can have a list of listeners and remove particular ones, you can pass any other object through the listener in WorkObserver with the payload you need. I just created a simple boolean passing
For that simple case however if you don't want to use DI, simple Object would do the work. However when your codebase grows and you are dealing with multithreading issues, or even accessing this object in other parts of the application it may lead to problems. I am using it only to pass information between objects, I don't recommend using it for storing data etc:
class MyWorker: Worker() {
override fun doWork(): Result {
val result = api.call().execute()
if(result.isSuccessful) {
WorkObserver.notifySuccess()
return Result.success()
} else {
WorkObserver.notifyError()
return Result.failure()
}
}
}
class MyService: Service() {
override fun onCreate() {
WorkObserver.setOnResultListener { result ->
if(result) {
//do something
} else {
// do something else
}
}
override fun onDestroy() {
WorkObserver.setOnResultListener(null)
}
}
object WorkObserver {
private var onResultListener: ((Result) -> Unit)? = null
fun setOnResultListener(listener: ((Result) -> Unit)?) {
this.onResultListener = listener
}
fun notifySuccess() {
this.onResultListener?.invoke(true)
}
fun notifyError() {
this.onResultListener?.invoke(false)
}
}

Fragment onDestroy() vs Service onDestroy()

I need to execute some code when the user swipes up on their recents list (kills the app).
When the app is killed, both my Fragment/Activity and Foreground Service are destroyed.
However, which onDestroy() method should I use to execute the code?
Here is my current setup:
Fragment.kt
override fun onDestroy() {
countDownTimer.cancel()
user1Listener?.remove()
stopService()
super.onDestroy()
mapView?.onDestroy()
}
private fun stopMeetupService(){
val intent = Intent(mContext.applicationContext, MyService::class.java)
activity?.stopService(intent)
}
MyService.kt
val db = FirebaseFirestore.getInstance()
override fun onDestroy() {
super.onDestroy()
sharedPref = this.getSharedPreferences(getString(R.string.user_data), Context.MODE_PRIVATE)
val docId = sharedPref.getString("docId", null)
val docData = mapOf<String, Any?>(
"user2" to "",
"location" to null
)
if (docId != null && docId.isNotBlank()) {
db.collection("docs").document(docId).update(docData).addOnSuccessListener {
}
}
}
Realistically, both sets of code could be merged into the one onDestroy() - ideally whichever method is more reliable to fire.
I guess you should use ActivityLifecycleCallbacks, if you want to stop service only when kill app from recent. Because in some case your service may also stop if you replace the fragments.
So stoping service should Application level and not a Activity/Fragment level in your case
Here is code
AppLifecycleHandler
import android.app.Activity
import android.app.Application
import android.content.ComponentCallbacks2
import android.content.res.Configuration
import android.os.Bundle
class AppLifecycleHandler(private val lifeCycleDelegate: LifeCycleDelegate) : Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {
override fun onActivityPaused(p0: Activity?) {
}
override fun onActivityResumed(p0: Activity?) {
}
override fun onActivityStarted(p0: Activity?) {
}
override fun onActivityDestroyed(p0: Activity?) {
lifeCycleDelegate.onAppDestroyed(p0)
}
override fun onActivitySaveInstanceState(p0: Activity?, p1: Bundle?) {
}
override fun onActivityStopped(p0: Activity?) {
}
override fun onActivityCreated(p0: Activity?, p1: Bundle?) {
}
override fun onLowMemory() {}
override fun onConfigurationChanged(p0: Configuration?) {}
override fun onTrimMemory(level: Int) {
}
}
interface LifeCycleDelegate {
fun onAppDestroyed(p0: Activity?)
}
Application class : override onAppDestroyed in Application class and stop service here
class App : LifeCycleDelegate {
....
....
override fun onAppDestroyed(p0: Activity?) {
val intent = Intent(p0, MyService::class.java)
p0?.stopService(intent)
}
}

how to detect the closing of the app and take acction ? ( kotlin )

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

Categories

Resources