1

The Questions:

  1. Could you please suggest a best practice way of getting network connectivity status in a repository that needs to determine from where to fetch data. My min Api is 23.
  2. Why does Hilt construct the class multiple times even if it's supposed to be installed in SingletonComponent?

I have borrowed from a Medium article and built a NetworkStatusManager. It is injected into my DetailsRepository with Hilt through @Binds. This NetworkStatusManager emits a flow when network becomes available or unavailable. I want to use that flow in repository and have an always up-to-date status of connectivity so DetailsRepository can determine where to fetch data (locally or remotely).

I've seen many examples of how people use these NetworkCallbacks in ViewModels, but to me it doesn't make sense to use them there. ViewModel shouldn't know where data comes from. Repository determines which data to provide, but to do that, it must know if internet is available to know where to get data from. I also want, in case of failure, to return a ResultOf that will have a NoInternetException so THEN ViewModel will get to provide an appropriate error message to UI.

Here's what I tried so far:

class DetailsRepositoryImpl @Inject constructor(
     private val networkStatusManager: NetworkStatusManager,
     @IoDispatcher private val dispatcher: CoroutineDispatcher
) : DetailsRepository {

private var mNetwork : Boolean = false
val networkStatus : StateFlow<NetworkStatus> = networkStatusManager.networkStatus
    .flowOn(dispatcher)
    .onEach {
        Log.d(TAG, "ON EACH: networkStatus received = ${it.toString()}")
        mNetwork = when (it) {
            NetworkStatus.Available -> true
            NetworkStatus.Unavailable -> false
        }
        Log.d(TAG, "ON EACH: Current mNetwork value is : $mNetwork")
}.stateIn(
    CoroutineScope(dispatcher),
    SharingStarted.Eagerly,
    initialValue = NetworkStatus.Unavailable
)
...
}

Now in the same repository I have a method getTitle that checks that mNetwork value. For interest's sake I also check the networkStatus.value:

override suspend fun getTitle(mediaId: Long, type: TitleType): ResultOf<Title> =
    withContext(dispatcher) {
        Log.d(TAG, "getTitle: LAUNCHED GET TITLE")
        Log.d(TAG, "getTitle: networkStatus = ${networkStatus.value}")

        Log.d(TAG, "getTitle: mNetwork = ${mNetwork}")

        // when (mNetwork) {
        //     This is where I would determine what to do next based on connectivity
        // }
        Log.d(TAG, "getTitle: NETWORK CHECK DONE")
...
}

The problem: This repository is injected with @Binds by Hilt to some viewmodel. The networkStatus then get's triggered and the mNetwork gets set correctly. BUT, then I click on a movie, this is where DetailsRepository has to actually start providing data, and the DetailsRepository gets AGAIN constructed?? (I checked, init block gets launched again) and then the getTitle method gets called, but at that time the mNetwork value is "new" and is equal to false.

Other solution I've tried: Simply accessing the networkStatusManager.networkStatus first() operator and just getting the current value of the network status, but if the app starts with no connectivity available then this line freezes seemingly indefinetly:

val request = NetworkRequest.Builder()
        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        .build()
    connectivityManager.registerNetworkCallback(request, networkStatusCallback)

This is how NetworkStatusManager and DetailsRepository are bound:

@Module
@InstallIn(SingletonComponent::class)
abstract class AbstractionsModule {

    @Binds
    abstract fun bindDetailsRepository(detailsRepositoryImpl: DetailsRepositoryImpl): DetailsRepository

    @Binds
    abstract fun bindNetworkStatusManager(networkStatusManagerImpl: NetworkStatusManagerImpl): NetworkStatusManager
    ...
}

My Logcat showing the values of DetailsRepository. In the red block you can see that NetworkStatusManager emitted Available value, DetailsRepository received it in onEach and mNetwork was set. Then after 5 seconds I clicked on a Movie, it led to DetailsScreen and the DetailsViewmodel seemingly constructed the DetailsRepository AGAIN, because you can that first the getTitle method gets called, it checks the mNetwork value which is false by default and ONLY THEN NetworkStatusManager emits a value again and then my StateFlow in DetailsRepository gets it and changes mNetwork but at that point it's too late :) If it's installed in the SingletonComponent, shouldn't it be the same DetailsRepository?

Logcat

Karolis
  • 53
  • 5
  • Did you find an answer? I am wondering the same thing. – IgorGanapolsky Mar 08 '23 at 14:32
  • Not really... I ended up using two things. For data layer: repositories, datasources etc. I just have a ConnectivityChecker class that checks for connectivity on the spot, but it has to be called each time. 2) I copied and used the approach from Now in Android app (it's available on GitHub) and I listen to connectivity changes in the ui layer and show appropriate errors and/or snackbars from there. – Karolis Mar 09 '23 at 12:00

0 Answers0