Search code examples
androidandroid-connectivitymanager

Callback called twice when I rotate the device but once in application launch


I have a sample in Github (https://github.com/alirezaeiii/Movies) where I have a utility class in order to check internet network connection :

class NetworkUtils(context: Context) : ConnectivityManager.NetworkCallback() {

    private val networkLiveData: MutableLiveData<Boolean> = MutableLiveData()

    private val connectivityManager =
        context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    fun getNetworkLiveData(): LiveData<Boolean> {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            connectivityManager.registerDefaultNetworkCallback(this)
        } else {
            val builder = NetworkRequest.Builder()
            connectivityManager.registerNetworkCallback(builder.build(), this)
        }

        var isConnected = false

        connectivityManager.allNetworks.forEach { network ->
            val networkCapability = connectivityManager.getNetworkCapabilities(network)

            networkCapability?.let {
                if (it.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
                    isConnected = true
                    return@forEach
                }
            }
        }

        networkLiveData.postValue(isConnected)

        return networkLiveData
    }

    override fun onAvailable(network: Network) {
        networkLiveData.postValue(true)
    }

    override fun onLost(network: Network) {
        networkLiveData.postValue(false)
    }

    fun unRegister() {
        connectivityManager.unregisterNetworkCallback(this)
    }
}

In an Activity I observe in onCreate() and unRegisterNetworkCallback in onDestroy() :

override fun onCreate() {
        super.onCreate(savedInstanceState)
        handleNetwork()
    }

override fun onDestroy() {
    super.onDestroy()
    networkUtils.unRegister()
}

private fun handleNetwork() {
    networkUtils.getNetworkLiveData().observe(this) { isConnected: Boolean ->
        if (!isConnected) {
            ...
        } else {
            Log.d("Test", "Connected")
            ...
        }
    }
}

The 1st time that I launch the app "Connected" tag will be called once, but when I rotate the device it will be called twice. Why is that?

I have another sample where I did not use Navigation architecture component in it and it get called once when I rotate :https://github.com/alirezaeiii/TMDb-Paging/blob/master/app/src/main/java/com/sample/android/tmdb/ui/BaseActivity.kt

Provider using hilt :

@Module
@InstallIn(SingletonComponent::class)
class AppUtilsModule {
    @Singleton
    @Provides
    fun provideNetworkUtils(context: Context): NetworkUtils {
        return NetworkUtils(context)
    }
}

Solution

  • So as this is singleton. After the First creation of the activity and invoking getNetworkLiveData, internal networkLiveData is filled with value. After rotation, you are using the same NetworkUtils object, that already has value stored in networkLiveData, during registration existing LiveData is returned, but jus before returning it there is code that posts new value to it.

        networkLiveData.postValue(isConnected)
    
        return networkLiveData
    

    As postValue is invoked asynchronously in most cases changing of the internal value will be invoked after the return statement.

    So at the end we are registering to livaData which has old value, and just a few ms later we receive new value.

    There are a few options to avoid that kind of situation:

    1. Post new value only if it's different from the previous one (Still not perfect)
    2. add Transformations.distinctUntilChanged(networkLiveData) when returning live data (Still not perfect)
    3. Rework a bit of utils, and separate logic related to observing live data and log related to receiving updates from ConnectivityManager

    Edit:

    There is also another problem after registerNetworkCallback, the callback is invoked with the current status. So there again there is an update of the networkLiveData