-
-
Save marenovakovic/9466bfc1b765e06e1b3600efbc4da2f8 to your computer and use it in GitHub Desktop.
import android.content.Context | |
import android.net.ConnectivityManager | |
import android.net.Network | |
import android.net.NetworkCapabilities | |
import android.net.NetworkRequest | |
import android.os.Bundle | |
import android.widget.TextView | |
import androidx.appcompat.app.AppCompatActivity | |
import androidx.lifecycle.ViewModel | |
import androidx.lifecycle.ViewModelProvider | |
import kotlinx.coroutines.Dispatchers | |
import kotlinx.coroutines.FlowPreview | |
import kotlinx.coroutines.channels.awaitClose | |
import kotlinx.coroutines.flow.Flow | |
import kotlinx.coroutines.flow.callbackFlow | |
import kotlinx.coroutines.flow.distinctUntilChanged | |
import kotlinx.coroutines.flow.flatMapConcat | |
import kotlinx.coroutines.flow.map | |
class MainActivity : AppCompatActivity() { | |
private val viewModel: NetworkStatusViewModel by lazy { | |
ViewModelProvider( | |
this, | |
object : ViewModelProvider.Factory { | |
override fun <T : ViewModel?> create(modelClass: Class<T>): T { | |
val networkStatusTracker = NetworkStatusTracker(this@MainActivity) | |
return NetworkStatusViewModel(networkStatusTracker) as T | |
} | |
}, | |
).get(NetworkStatusViewModel::class.java) | |
} | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContentView(R.layout.activity_main) | |
viewModel.state.observe(this) { state -> | |
findViewById<TextView>(R.id.textView).text = when (state) { | |
MyState.Fetched -> "Fetched" | |
MyState.Error -> "Error" | |
} | |
} | |
} | |
} | |
sealed class MyState { | |
object Fetched : MyState() | |
object Error : MyState() | |
} | |
class NetworkStatusViewModel( | |
networkStatusTracker: NetworkStatusTracker, | |
) : ViewModel() { | |
val state = | |
networkStatusTracker.networkStatus | |
.map( | |
onAvailable = { MyState.Fetched }, | |
onUnavailable = { MyState.Error }, | |
) | |
.asLiveData(Dispatchers.IO) | |
} | |
sealed class NetworkStatus { | |
object Available : NetworkStatus() | |
object Unavailable : NetworkStatus() | |
} | |
class NetworkStatusTracker(context: Context) { | |
private val connectivityManager = | |
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager | |
val networkStatus = callbackFlow<NetworkStatus> { | |
val networkStatusCallback = object : ConnectivityManager.NetworkCallback() { | |
override fun onUnavailable() { | |
println("onUnavailable") | |
offer(NetworkStatus.Unavailable) | |
} | |
override fun onAvailable(network: Network) { | |
println("onAvailable") | |
offer(NetworkStatus.Available) | |
} | |
override fun onLost(network: Network) { | |
println("onLost") | |
offer(NetworkStatus.Unavailable) | |
} | |
} | |
val request = NetworkRequest.Builder() | |
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) | |
.build() | |
connectivityManager.registerNetworkCallback(request, networkStatusCallback) | |
awaitClose { | |
connectivityManager.unregisterNetworkCallback(networkStatusCallback) | |
} | |
} | |
.distinctUntilChanged() | |
} | |
@FlowPreview | |
inline fun <Result> Flow<NetworkStatus>.map( | |
crossinline onUnavailable: suspend () -> Result, | |
crossinline onAvailable: suspend () -> Result, | |
): Flow<Result> = map { status -> | |
when (status) { | |
NetworkStatus.Unavailable -> onUnavailable() | |
NetworkStatus.Available -> onAvailable() | |
} | |
} | |
@FlowPreview | |
inline fun <Result> Flow<NetworkStatus>.flatMap( | |
crossinline onUnavailable: suspend () -> Flow<Result>, | |
crossinline onAvailable: suspend () -> Flow<Result>, | |
): Flow<Result> = flatMapConcat { status -> | |
when (status) { | |
NetworkStatus.Unavailable -> onUnavailable() | |
NetworkStatus.Available -> onAvailable() | |
} | |
} |
And one tiny request: if you could add the imports at the top of the file, it would help clarify where everything comes from.
Am glad you find this helpful! Thank you for kind words. I will add imports, thanks for suggestion.
The first time you enter it is not called to know the status of the connection, it is only called when it changes. Only happens when you dont have connection. Google issue? https://issuetracker.google.com/issues/144891976
excellent code
@thotluna thank you very much. am really glad you like it!
@PatricioIN I didn't encounter any issues. I will retest it and update gist if I encounter any errors
If your requirement is "NOTIFY only if no network connection exists neither Wifi
nor Mobile Internet
or one of them exists" then this modification can be helpful --> NetworkCheck
The first time you enter it is not called to know the status of the connection, it is only called when it changes. Only happens when you dont have connection. Google issue?
The same issue as @PatricioIN
It doesn't trigger for the first time. Although, I use this collectIn
directly to collect the emission:
inline fun <T> Flow<T>.collectIn(
owner: LifecycleOwner,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
crossinline action: suspend (T) -> Unit,
) = owner.lifecycleScope.launch(coroutineContext) {
owner.lifecycle.repeatOnLifecycle(minActiveState) {
collect {
action(it)
}
}
}
but when I change the wifi/data, it works!
Update:
I followed what you did and still, it doesn't work when the internet is already disconnected.
Update:
I had to add connectivityManager.requestNetwork(request, networkStatusCallback, 1000)
too, now it works 🎉
I'm just starting to tinker with this (and Compose). With latest studio and up-to-date implementations I have a error I don't know how to fix.
In the construction of the ViewModel via the Factory, I received an error pinned on the "object" line which reads:
Inheritance from an interface with '@JvmDefault' members is only allowed with -Xjvm-default option
Have you seen this and do you have any suggestions on what I should do to fix it?
thanks,
steve
@sralpert Better to ask that on Stackoverflow but maybe this helps: https://stackoverflow.com/questions/70992947/how-do-i-resolve-error-message-inheritance-from-an-interface-with-jvmdefault
@sralpert I will check it out
@sralpert sorry for late response. I can't reproduce that, no issues when I try to implement it. best is to follow that StackOverflow link
@Drjacky sorry. Where did you put the line connectivityManager.requestNetwork(request, networkStatusCallback, 1000)
? Thank you very much
...
val request = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(request, callback)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
connectivityManager.requestNetwork(request, callback, 1000) //THISSSS
}
awaitClose {
connectivityManager.unregisterNetworkCallback(callback)
}
}
.distinctUntilChanged()
Can have checking connection in kotlin multiplatform instead of seperate android and ios?
@hafiz013 sure you can.
there are libraries like:
https://github.com/jordond/connectivity
and
https://github.com/ln-12/multiplatform-connectivity-status
This is super helpful — thank you for generously taking the time to share such an important example!