The Questions:
- 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.
- 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 NetworkCallback
s in ViewModel
s, 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
?