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
}
Related
I have a specific UseCase where initialize app data. I store every <reference, listener> in a dispatchListeners list to unsubscribe later.
typealias EventListener = Pair<DatabaseReference, ValueEventListener>
class InitAppDataUseCase(
private val subscribeUserUseCase: SubscribeUserUseCase,
private val subscribeNewsUseCase: SubscribeNewsUseCase,
private val subscribeStoriesUseCase: SubscribeStoriesUseCase,
private val subscribeMeetingsUseCase: SubscribeMeetingsUseCase,
private val subscribeCategoriesUseCase: SubscribeCategoriesUseCase,
private val dispatchers: AppDispatchers
): UseCase<Unit, Unit> {
private val dispatchListeners = mutableListOf<EventListener>()
override suspend fun execute(input: Unit) {
init()
}
private fun EventListener.add() = dispatchListeners.add(this)
private suspend fun init() = CoroutineScope(dispatchers.io).launch {
runCatching {
listOf(
async { subscribeUserUseCase.execute().add() },
async { subscribeNewsUseCase.execute().add() },
async { subscribeStoriesUseCase.execute().add() },
async { subscribeMeetingsUseCase.execute().add() },
async { subscribeCategoriesUseCase.execute().add() }
).awaitAll()
}
}
fun clearSubscribed() = CoroutineScope(dispatchers.io).launch {
dispatchListeners.forEach { referenceToListener ->
with(referenceToListener) {
first.removeEventListener(second)
}
}
}
}
But where should I unsubscribe?
When the user remove an account or sign out from my app, I do this in specific ViewModel and redirect him to AuthScreen after this executed.
But what should I do if user just close my app? Is this correct way to unsubscribe in onDestroy() of my MainActivity? I have doubts because clearSubscribed() is a heavy operation. Am I right if the user have a poor internet connection and - this operation couldn't be executed because applicationScope will be dead?
class MainActivity : ComponentActivity() {
private val initAppDataUseCase by inject<InitAppDataUseCase>()
override fun onCreate() {}
override fun onDestroy() {
super.onDestroy()
initAppDataUseCase.clearSubscribed()
}
}
You have to remove the listener according to the life cycle of your activity. Since you're using Kotlin, most likely in an MVVM architecture, I would rather use callbackFlow. There is a very helpful example in the documentation. However, in the case of Firebase, to attach and dettach the listener, please use the following lines of code:
override fun getDataFromRealtimeDatabase() = callbackFlow {
val listener = object: ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
//Do what you need to do with the data.
}
override fun onCancelled(e: DatabaseError) {
Log.d("TAG", "${e?.message}") //Never ignore potential errors!
}
}
yourRef.addValueEventListener(listener) //Attach the listener.
awaitClose {
yourRef.removeEventListener(listener) //Dettach the listener.
}
}
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 had an Activity with a calculation and I extracted the functionality of that Activity in MVP pattern, for simplicity:
CalcActivity
CalcPresenter
Earlier I had all the calculation in one single CalcActivity. There I did some calculations in that activity:
private fun Calculator.doCalculation() {
this.complexCalcualtion(intArrayOf(1, 2, 3, 4, 5, 6), object : CalculationCallback {
override fun onSuccess(result: String) {
runOnUiThread {
result_textview.text = result
}
}
})
}
This whole doCalculation() is done on another thread I guess. I moved this method to the presenter and I wanted to forward result to view:
private fun Calculator.doCalculation() {
this.complexCalcualtion(intArrayOf(1, 2, 3, 4, 5, 6), object : CalculationCallback {
override fun onSuccess(result: String) {
view?.showResult(result)
}
})
}
But view? is never called since it is null in the CalculationCallback.onSuccess() and I cant see view there.
Also I do not have access to activity there, so I can not runOnUiThread there..
How can I forward my result back to view/activity?
You can deliver calculation result by LiveData. This tool is integrated with activity life cycle and your data will be delivered when activity (and it views) will be in active state.
You can implement your calculator like this:
class Calculator {
private val resultLiveData = MutableLiveData<String>().apply { value = "" }
// expose result as live data
val result: LiveData<String> = resultLiveData
fun calculate(input: String) {
// calculation in worker thread
// ...
// return result for live data observers
resultLiveData.postValue("$input CALCULATED!")
}
fun cancel() {
// depending on background worker
}
}
And use it in activity (or fragment)
class MyActivity : Activity() {
private val calculator = Calculator()
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
calculator.result.observe(this::getLifecycle) { result ->
// setup result in your view
text_view.text = result
}
}
override fun onStart() {
super.onStart()
calculator.calculate("Input data")
}
override fun onStop() {
super.onStop()
calculator.cancel()
}
}
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)
}
}
In My project sometimes the created thread does not start as fast as it should be, This happens on a minimal occasions but mostly will happen on slow/older phones.
I my Thread like..
class DBThread(threadName: String) : HandlerThread(threadName) {
private var mWorkerHandler: Handler? = null
override fun onLooperPrepared() {
super.onLooperPrepared()
mWorkerHandler = Handler(looper)
}
fun createTask(task: Runnable) {
mWorkerHandler?.post(task)
}
}
and when i use it and call on activity..
//this will handle db queries on background and not on main ui thread
var mDbThread: DBThread = DBThread("dbThread")
//use this to interact to main ui thread from different thread
val mUiHandler = Handler()
var mDb: LocalDatabase? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mDbThread.start()
mDb = LocalDatabase.getInstance(this)
fetchAndSetList()
}
override fun onDestroy() {
super.onDestroy()
LocalDatabase.destroyInstance()
mDbThread.quitSafely()
}
private fun fetchAndSetList(){
mDbThread.createTask(Runnable {
val list = getList()
mUiHandler.post {
// this sometimes does not trigger
setList(list)
}
})
}
the function setList does not trigger on sometimes.
And so i did something like this.
fun createTask(task: Runnable) {
if(mWorkerHandler == null ){
createTask(task)
return
}
mWorkerHandler?.post(task)
}
the modified action seems to work however I'm not quite sure if this is a SAFE way to do this. Thank you in advance.
I think the reason why mWorkerhandler is null is because Thread.start will create the new VMThread and start the looper in the VMThread. The whole flow is asynchronous so when onLooperPrepared actually is called, it's too late because "fetchAndSetList" is already trying to use mWorkerHandler
The solution is create the handler outside of the HandlerThread:
Handler workerHandler;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mDbThread.start()
workerHandler = new Handler(mDbThread.getLooper());
mDb = LocalDatabase.getInstance(this)
fetchAndSetList()
}
private fun fetchAndSetList(){
workerHandler.post(Runnable {
val list = getList()
mUiHandler.post {
// this sometimes does not trigger
setList(list)
}
})
}