Reputation: 4731
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
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
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