1

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

1 Answers1

1

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.
    }
}
Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • I need my data be updated with application scope, not view model scope. Every screen in my app is related in one way or another to the data I subscribe to. I decided to subscribe and unsubscribe only once (SplashScreen & Logout/Delete Account/Close App), so as not to re-request the data each time If i'll subscribe in the way you suggest, I would request data each time when, for example, I navigate between BottomNavigationView tabs I use Jetpack Compose + MVI + Clean Arch – Andrey Larionov Feb 16 '23 at 12:00
  • and in this way, I guess, if I'll unsubscribe from 5 references, it won't be in parallel way – Andrey Larionov Feb 16 '23 at 12:39
  • In MVVM or MVI, this kind of method is placed in a repository class. Now, you can call it from a ViewModel if you want, using a viewModelScope, or from another part, using another scope. However, it's best to remove the listener, when for example you don't need data, otherwise, at some point in time, the Android OS will eventually close it anyway. Besides that, the operation of removing the listener is **not** asynchronous. – Alex Mamo Feb 16 '23 at 12:42
  • Hey, is it correct that if I do not need to unsubscribe on a specific screen, then when the user closes the application there will be no leaks and the listener itself will be removed from the link in the database? Or do I still need to unsubscribe to onDestroy()? – Andrey Larionov Feb 17 '23 at 10:21
  • If you don't remove the listener, and your app will continue to get updates from the Firebase servers even if the user isn't actually using the app, then besides the amount of bandwidth that you'll have to pay, the Android OS will eventually close it anyway. Furthermore, [`onDestroy` is not always called](https://stackoverflow.com/questions/18361719/android-activity-ondestroy-is-not-always-called-and-if-called-only-part-of-the). See the other [answer](https://stackoverflow.com/a/48862873/5246885) of mine. – Alex Mamo Feb 17 '23 at 11:33
  • Did my answer/comments help? Can I help you with other information? – Alex Mamo Feb 17 '23 at 11:34