1

I am implementing offline mode for my application, my plan is to put the local db between the UI and API Requests.

I have this fragment and his viewmodel with this init block:

init {
   viewModelScope.launch(Dispatchers.IO) {
      // context required here
      loadVehicles()
   }
}

Now, inside loadVehicles, I want to check if I am online or not, if I am, I will simply make a call to the API to update my local database in case there is anything new.

fun isOnline(context: Context): Boolean {
    val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    if (connectivityManager != null) {
        val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
        if (capabilities != null) {
            return true
        }
    }
    return false
}

This was the simplest code I could find to test if I am online or not, and this function needs the context, which is unaccessable from the init block of the view model.

Looking forward hearing other suggestions of doing things if there is something I can improve with mine.

SKREFI
  • 177
  • 2
  • 13
  • 2
    *Can I get the context inside ViewModel's init block?* deriving your ViewModel from `AndroidViewModel` ? – Selvin Mar 16 '21 at 15:45
  • Is this a title suggestion or what? I am confused. So you are adding "deriving from AndroidViewModel", well, yes for sure it is... if that, for any reason, is ambiguous to you – SKREFI Mar 16 '21 at 19:14
  • Then move init code to constructor – Selvin Mar 16 '21 at 22:57
  • Also why is context is unaccessible? [It should be](https://pl.kotl.in/ZbG_YwqtD) – Selvin Mar 16 '21 at 23:21
  • Well I can't, it has to be coroutine, I am making requests with retrofit and room to the local db as well as getting new data from the API, all being suspended functions. – SKREFI Mar 17 '21 at 06:47

1 Answers1

3

Your code "must" look like this:

class SomeHelperClass @Inject constructor(private val  context: Context) {

    fun isOnline(): Boolean {
        val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        if (connectivityManager != null) {
            val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
            if (capabilities != null) {
                return true
            }
        }
        return false
    }
}


class MyViewModel @Inject constructor(helper: SomeHelperClass):ViewModel()  {
    init {
        viewModelScope.launch(Dispatchers.IO) {

            // call to helper.isOnline() !!!!!!

            loadVehicles()
        }
    }
}
  • That is how the code in MyViewModel will not dependent on the Context, which means no instrumental tests are needed. You can test it with plain Kotlin/Java tests.

  • I know that you might not want to test it, but one of the good results of testing is that it makes the code better. If you make testable code it will be better even if you do not test it. And in this case, you are not making heavy ViewModels which is a bad idea.

  • Also, I am suggesting you to use Dagger-Hilt if you are not using it already - @Inject

Yavor Mitev
  • 1,363
  • 1
  • 10
  • 22
  • If @Inject <=> Dagger-Hilt, than yes, I am using it, but I don't understand why can't I inject the context directly, isn't it the same thing? If I inject the context in the Helper class and inject helper class in the ViewModel. I started slowing converting my code so instead of sending context as parameter to functions, I am using utilities.context which I used alost everywhere, therefore, I have the context in each viewmodel anyways. – SKREFI Jun 14 '21 at 14:10
  • 1
    You do not have the context in each ViewModel. The helper class has the Context. So you can mock the helper class and execute the ViewModel without the Android Environment. What you are saying might has sense if you want to split the helper classes and the ViewModels in different modules. Than it would make sense to have an interface in the VM module and the helper class in the other module to implement it. This would make the whole module of the VM "not Context-dependent". If you feel like doing it go for it. But in either case you have some win. – Yavor Mitev Jun 26 '21 at 11:55
  • 1
    Also, it is about principles. It is not only about what you win now with a concrete decision. It is also about what this decision will gain you in the future. The more you split stuff the more you will prevent yourself and your colleagues to make a mess in the future by calling everything in one place. Otherwise someone in 3 months will be like: "Oh, great! I have a context here! Let me just call my code here!" – Yavor Mitev Jun 26 '21 at 11:58
  • Insightful, I still find better ways of doing things, I am Android "seriously" coding for only few months now. Thank you! – SKREFI Jun 28 '21 at 06:27
  • 1
    Basically, my idea is based on the Clean Code principles of Robert Martin and SOLID. But in Android reality, if you do it wrong the end result is too big ViewModel which handles a lot of logic. Most of the people totally break the SRP idea. – Yavor Mitev Jun 28 '21 at 08:55