sagar suri
sagar suri

Reputation: 4731

proper place to use location api in android mvvm architecture

I have a scenario, I want to show user current weather data for that I am getting his/her current lat/lng and reverse geocoding it to get the city name. Once I have the city name I will make a network call and show the weather data. Apart from this, there are many location operations I need to perform.

So I have created a class named as LocationUtils.kt. I am following MVVM architecture and want to know which is the ideal layer to call the LocationUtils methods, is it the view layer or the viewmodel layer or the data layer. Since FusedLocationProvider needs context and if I use it in ViewModel it will leak. So how to solve this problem?

LocationUtils.kt:

class LocationUtils {
  private lateinit var fusedLocationClient: FusedLocationProviderClient

  private fun isLocationEnabled(weakContext: Context?): Boolean {
    return when {
      Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> {
        // This is new method provided in API 28
        val locationManager = weakContext?.getSystemService(Context.LOCATION_SERVICE) as LocationManager
        locationManager.isLocationEnabled
      }
      Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT -> {
        // This is Deprecated in API 28
        val mode = Settings.Secure.getInt(
            weakContext?.contentResolver, Settings.Secure.LOCATION_MODE,
            Settings.Secure.LOCATION_MODE_OFF
        )
        mode != Settings.Secure.LOCATION_MODE_OFF

      }
      else -> {
        val locationProviders = Settings.Secure.getString(weakContext?.contentResolver, Settings.Secure.LOCATION_PROVIDERS_ALLOWED)
        return !TextUtils.isEmpty(locationProviders)
      }
    }
  }

  @SuppressLint("MissingPermission")
  fun getCurrentLocation(
    weakContext: WeakReference<Context>,
    success: (String?) -> Unit,
    error: () -> Unit
  ) {
    if (isLocationEnabled(weakContext.get())) {
      weakContext.get()
          ?.let { context ->
            fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
            fusedLocationClient.lastLocation.addOnSuccessListener { location ->
              getCurrentCity(context, location, success)
            }
          }
    } else {
      error()
    }
  }

  private fun getCurrentCity(
    context: Context,
    location: Location?,
    success: (String?) -> Unit
  ) {
    val city = try {
      location?.let {
        val geocoder = Geocoder(context, Locale.getDefault())
        val address = geocoder.getFromLocation(it.latitude, it.longitude, 1)
        address[0].locality
      }
    } catch (e: Exception) {
      "Bangalore"
    }
    success(city)
  }
}

Upvotes: 10

Views: 2651

Answers (2)

Waldmann
Waldmann

Reputation: 1827

I put it in my ViewModel. In order to pass context as an argument to your ViewModel you can extend AndroidViewModel instead of ViewModel. Example:

class CurrentViewModel(application: Application) : AndroidViewModel(application) {
    val context = application
    val locationResolver = LocationResolver(context)//this one converts latitude and longitude into City name of type String


fun detectCity() {
        Log.d(LocationResolver.TAG, "entered detectLocation()")
        val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
        fusedLocationClient.lastLocation
            .addOnSuccessListener { location ->
                if (location != null) {
                    Repository._currentName.value = locationResolver.getLocationFromCoordinates(
                        location.latitude,
                        location.longitude
                    )
                    Log.d(
                        LocationResolver.TAG,
                        "New city name is:" + Repository.currentLocationCity
                    )
                }

            }
    }

Then you can observe the ouput via DataBinding.

Upvotes: -1

Abdul Mateen
Abdul Mateen

Reputation: 1684

I am also working on the same problem. I also have to deal with showing weather data to user using MVVM architecture. At the moment, I am stuck at the same point where you are right now. The solution seems to be something called 'Dependency Injection (DI)'. Basically, we can inject dependencies like Context to our ViewModel using tools/frameworks like 'Dagger 2'. DI has lower coupling than directly passing Context to ViewModel and results in better compliance with MVVM. So, the actual place of FusedLocationProvider, IMO, will be in ViewModel but after implementing DI. Maybe someone else can better elaborate on my explanation. I will update my answer once I implement Dependency Injection myself.

Upvotes: 3

Related Questions