I am fetching weather data from an API. The problem that I am facing is when I get the cityName in textView from the current locations and then I have to send that cityName to the API so I can get the temperature of that city. But I am unable to do this. Any help will be appreciated.
HomeRepositery
suspend fun getWeatherDataFromApi(cityName : String,appid : String) =
weatherServiceApi.getchWeatherData(cityName, appid)
HomeViewModel
val weatherData: MutableLiveData<Resource<WeatherDataClass>> = MutableLiveData()
init {
getWeatherDataFromRepo("" ,"appid")
}
//here we make a network response
fun getWeatherDataFromRepo(cityName: String, appId: String) = viewModelScope.launch {
weatherData.postValue(Resource.Loading())
val response = homeRepositery.getWeatherDataFromApi(cityName, appId)
weatherData.postValue(handleWeatherResponseData(response))
}
Fragment
#SuppressLint("MissingPermission")
private fun getLocation() {
val fusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(requireActivity())
val locationRequest = LocationRequest().setInterval(100000).setFastestInterval(100000)
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
fusedLocationProviderClient.requestLocationUpdates(
locationRequest,
object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
for (location in locationResult.locations) {
latTextView.text = location.latitude.toString()
lngTextView.text = location.longitude.toString()
tv_cityName.text = getCityName(location.latitude, location.longitude)
onObserveLiveData()
}
// Few more things we can do here:
// For example: Update the location of user on server
}
},
Looper.myLooper()
)
}
private fun onObserveLiveData() {
viewModel.weatherData.observe(viewLifecycleOwner, androidx.lifecycle.Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { weatherResponse ->
tv_humidity.text = weatherResponse.main.humidity.toString()
tv_weather.text = weatherResponse.clouds.all.toString()
tv_temp.text = weatherResponse.main.temp.toString()
}
}
is Resource.Error -> {
response.message?.let {
Toast.makeText(
requireContext(),
"THeir is am error in fetching",
Toast.LENGTH_SHORT
).show()
}
}
is Resource.Loading -> {
Toast.makeText(
requireContext(),
"Is in Loading state so please wait foe while!!",
Toast.LENGTH_SHORT
).show()
}
}
})
}
Related
Intention:
I am trying to get user's current location after user grants (coarse/fine)location permission. I am using jetpack compose accompanist lib to manager permission.
So when user grants the permission, am using getCurrentLocation of FusedLocationProviderClient to get the location object, and fetching lat lang from it.
Problem:
In the below code block, Logcat logs: Coordinates[0.0,0.0]
class LocationManager #Inject constructor(
private val fusedLocation: FusedLocationProviderClient
) {
#SuppressLint("MissingPermission")
#Composable
#ExperimentalPermissionsApi
fun getLatLang(context: Context): Coordinates {
val coordinates = Coordinates(0.0, 0.0)
/**
* Checking and requesting permission. If granted it will fetch current lat lang,
* else it will request for permission.
* If denied, will show popup to open app settings and grant location permission.
*/
LocationPermissionManager.RequestPermission(
actionPermissionGranted = {
fusedLocation.getCurrentLocation(LocationRequest.PRIORITY_HIGH_ACCURACY, null)
.addOnSuccessListener { location ->
if (location != null) {
coordinates.lat = location.latitude
coordinates.long = location.longitude
}
}
},
actionPermissionDenied = { context.openAppSystemSettings() }
)
return coordinates
}
}
data class Coordinates(var lat: Double, var long: Double)
Consuming LocationManager below:
#ExperimentalPermissionsApi
#Composable
fun AirQualityLayout(locationManager: LocationManager) {
val context: Context = LocalContext.current
val coordinates: Coordinates = locationManager.getLatLang(context = context)
if (coordinates.lat != 0.0 && coordinates.long != 0.0) {
Timber.d("Current location: $coordinates")
ShowUI()
}
}
Expecting suggestions/help what I am doing wrong here.
Do you have your manifest right? (Access_fine_location and access_coarse_location
This is a class i made sometime ago:
class LocationLiveData(var context: Context): LiveData<LocationDetails>() {
//add dependency implementation "com.google.android.gms:play-services-maps:18.0.2"
private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
override fun onActive() {
super.onActive()
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) { // alse geen permissie hebben just return, anders voer functie location uit
return
}
fusedLocationClient.lastLocation.addOnSuccessListener {
location -> location.also {
setLocationData(it)
}
}
}
internal fun startLocationUpdates() {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return
}
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
}
private fun setLocationData(location: Location?) {
location?.let { it ->
//value is observed in LiveData
value = LocationDetails(
longitude = it.longitude.toString(),
lattitude = it.latitude.toString()
)
}
println("value $value")
}
override fun onInactive() {
super.onInactive()
fusedLocationClient.removeLocationUpdates(locationCallback)
}
private val locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
println("we have a new location result")
locationResult ?: return //als er een result is dan prima, zo niet dan just return (elvis operator)
for (location in locationResult.locations) {
setLocationData(location = location)
}
}
}
companion object {
val ONE_MINUTE: Long = 1000
#RequiresApi(Build.VERSION_CODES.S)
val locationRequest : com.google.android.gms.location.LocationRequest = com.google.android.gms.location.LocationRequest.create().apply {
interval = ONE_MINUTE
fastestInterval = ONE_MINUTE/4
priority = LocationRequest.QUALITY_HIGH_ACCURACY
}
}
}
I've just resolved it with UiState, placing function into ViewModel class
Here my solution:
UiState:
data class MyCityUiState(
...
val currentLocation: Location? = null
...
)
update funtion in ViewModel:
fun updateCurrentLocation(location: Location?) {
_uiState.update {
it.copy(
currentLocation = location
)
}
}
fun that use ".addOnListener":
#SuppressLint("MissingPermission")
fun displayDistance(placeLocation: LatLng, context: Context): String? {
var fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
fusedLocationClient.lastLocation
.addOnSuccessListener { location: Location? ->
if (location != null) {
updateCurrentLocation(location)
}
}
var result: Float?
var formatResult: String? = null
val placeLocationToLocationType = Location("Place")
placeLocationToLocationType.latitude = placeLocation.latitude
placeLocationToLocationType.longitude = placeLocation.longitude
result =
uiState.value.currentLocation?.distanceTo(placeLocationToLocationType)
if (result != null) {
formatResult = "%.1f".format(result / 1000) + " km"
}
return formatResult
}
``
Iam retrieving the users location (longitude and lattitude). I made a class LocationLiveData, this is working fine.
What i want is to show the longitude in a TextField, so when the location of the user changes it has to update the TextField.
At this moment the textfield is only updating the longitude when i trigger a recompose of the composable.
It seems like there is going something wrong in the observeAsState()
The composable:
#Composable
fun HomeScreen(application:Application) {
//connect to viewModel
val viewModelLocations: ApplicationViewModel = ApplicationViewModel(application = application)
//observe the data in viewModel
val location by viewModelLocations.getLocationLiveData().observeAsState()
Column() {
OutlinedTextField(value = location.longitude, onValueChange = { location.longitude = it})
Text("The textfield has this text: " + location.longitude)
}
}
The ViewModel:
class ApplicationViewModel(application: Application): AndroidViewModel(application) {
private val locationLiveData = LocationLiveData(context = application)
fun getLocationLiveData(): LocationLiveData {
var data = locationLiveData
return data
}
//start the location updates
fun startLocationUpdates() {
locationLiveData.startLocationUpdates()
}
}
Class LocationLiveData (here iam retrieving the users location)
Note: if i print the .value its printing the userslocation longitude and lattitude
class LocationLiveData(var context: Context): MutableLiveData<LocationDetails>() {
private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
override fun onActive() {
super.onActive()
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return
}
fusedLocationClient.lastLocation.addOnSuccessListener {
location -> location.also {
setLocationData(it)
}
}
}
fun startLocationUpdates() {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return
}
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
}
private fun setLocationData(location: Location?) {
//location.let (maak een functie, deze wordt alleen uitgvoerd indien locatie niet null is)
location?.let { it ->
//value is observed in LiveData
value = LocationDetails(
longitude = it.longitude.toString(),
lattitude = it.latitude.toString()
)
println("value $value")
}
}
//onInactive als de LiveData niet meer geobserveerd wordt
//we willen unsubscriben van de location updates (moet dus stoppen met updaten)
override fun onInactive() {
super.onInactive()
fusedLocationClient.removeLocationUpdates(locationCallback)
}
private val locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
println("we have a new location result")
locationResult ?: return //als er een result is dan prima, zo niet dan just return (elvis operator)
for (location in locationResult.locations) {
setLocationData(location = location)
}
}
}
companion object {
val ONE_MINUTE: Long = 1000
#RequiresApi(Build.VERSION_CODES.S)
val locationRequest : com.google.android.gms.location.LocationRequest = com.google.android.gms.location.LocationRequest.create().apply {
interval = ONE_MINUTE
fastestInterval = ONE_MINUTE/4
priority = LocationRequest.QUALITY_HIGH_ACCURACY
}
}
}
Essentially my problem is in the title, where I get permissions for location and from there try to get an API response based on the location. Problem is that the thread seems to continue before getting the answer? Here are the relevant snippets of code (and apologies for any beginner mistakes)
class ApiViewModel : ViewModel() {
private val _listOfRegions = MutableLiveData<List<Region>>()
val listOfRegions: LiveData<List<Region>> = _listOfRegions
fun getRegionsData(country: String) {
viewModelScope.launch {
Log.d("Workflow", "We will try to fetch info for $country")
try {
val listResult = SpotApi.retrofitService.getRegions(country)
Log.d("Workflow", listResult.toString())
if (listResult.isSuccessful) {
_listOfRegions.postValue(listResult.body())
Log.d("Workflow", listResult.body().toString())
} else {
_listOfRegions.value = emptyList()
}
} catch (e: Exception) {
Log.d("Workflow", "Failure: ${e.message}")
_listOfRegions.value = emptyList()
}
}
}
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
interface ApiService {
#GET("{country}")
suspend fun getRegions(#Path("country") country: String): Response<List<Region>>
}
object SpotApi {
val retrofitService: ApiService by lazy {
retrofit.create(ApiService::class.java)
}
}
object RegionsHelper {
fun getCurrentLocation(context: Context, lat: Double, long: Double): String {
val geocoder = Geocoder(context)
val locationResult = geocoder.getFromLocation(lat, long, 1)
val country = locationResult[0].countryCode
Log.d("Workflow", "Country is $country")
Log.d(
"Workflow",
locationResult[0].latitude.toString() + locationResult[0].longitude.toString()
)
return if (locationResult[0] != null) {
country
} else {
"Something Else"
}
}
}
class MainActivity : AppCompatActivity() {
private lateinit var fusedLocationClient: FusedLocationProviderClient
val viewModel: ApiViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
// TODO: If LastLocation available use that to trigger result, else use default value
getLastLocation()
}
private fun getLastLocation() {
if (checkLocationPermission()) {
if (isLocationEnabled()) {
fusedLocationClient.lastLocation.addOnCompleteListener(this) { task ->
val location: Location? = task.result
if (location == null) {
requestNewLocationData()
} else {
Log.d("Workflow", "Permission Granted")
Log.d("Workflow", "Location is $location")
val retrievedCurrentCountry = getCurrentLocation(this#MainActivity, location.latitude, location.longitude)
Log.d("Workflow", "Current country is $retrievedCurrentCountry")
viewModel.getRegionsData(retrievedCurrentCountry)
//Log.d("Workflow", listOfRegions.toString())
}
}
}
}
}
}
Logs result are:
D/Workflow: Country is PT
D/Workflow: 38.7211345-9.139605
D/Workflow: Current country is PT
D/Workflow: We will try to fetch info for PT
D/Workflow: null
D/Workflow: Response{protocol=http/1.0, code=200, message=OK, url=http://192.168.1.181:5000/PT/}
D/Workflow: [Region(region=cascais, country=PT, long=-9.4207, lat=38.6968), Region(region=sintra, country=PT, long=-9.3817, lat=38.8029), Region(region=caparica, country=PT, long=-9.2334, lat=38.6446), Region(region=ericeira, country=PT, long=-9.4176, lat=38.9665)]*
Even though getting last location using fused location provider is very quick, sometimes it may take some time (upto 1000ms).
In your code, your API is called before getting last location. you can achieve this by using observer for your retrievedCurrentLocation.
class MainActivity : AppCompatActivity() {
private lateinit var fusedLocationClient: FusedLocationProviderClient
val viewModel: ApiViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
// TODO: If LastLocation available use that to trigger result, else use default value
getLastLocation()
observeRetrievedLocation()
}
private fun observeRetrievedLocation() {
viewModel.retrievedLocation.observe(this) {
if (it.isNotEmpty) {
viewModel.getRegionsData(it)
}
}
}
private fun getLastLocation() {
if (checkLocationPermission()) {
if (isLocationEnabled()) {
fusedLocationClient.lastLocation.addOnCompleteListener(this) { task ->
val location: Location? = task.result
if (location == null) {
requestNewLocationData()
} else {
Log.d("Workflow", "Permission Granted")
Log.d("Workflow", "Location is $location")
val retrievedCurrentCountry = getCurrentLocation(this#MainActivity, location.latitude, location.longitude)
Log.d("Workflow", "Current country is $retrievedCurrentCountry")
viewModel.loadRetrievedLocation(retrievedCurrentCountry)
}
}
}
}
}
}
And your viewModel,
class ApiViewModel : ViewModel() {
private val _listOfRegions = MutableLiveData<List<Region>>()
val listOfRegions: LiveData<List<Region>> = _listOfRegions
private val _retrievedLocation = MutableLiveData<String>()
val retrievedLocation: LiveData<String> = _retrievedLocation
fun loadRetrievedLocation(retrievedLocation: String) {
_retrievedLocation.value = retrievedLocation
}
fun getRegionsData(country: String) {
viewModelScope.launch {
Log.d("Workflow", "We will try to fetch info for $country")
try {
val listResult = SpotApi.retrofitService.getRegions(country)
Log.d("Workflow", listResult.toString())
if (listResult.isSuccessful) {
_listOfRegions.postValue(listResult.body())
Log.d("Workflow", listResult.body().toString())
} else {
_listOfRegions.value = emptyList()
}
} catch (e: Exception) {
Log.d("Workflow", "Failure: ${e.message}")
_listOfRegions.value = emptyList()
}
}
}
so your API will be called only when the value of retrievedCurrentLocation Livedata changes and not null.
I want to when turn off the gps I see the settings dialog(requires) to turn of the gps Because my app needs GPS on. I have written code for this and put it in the onCreate() in MainActivity, but the dialog only shows me when the app runs, but I want to see this dialog wherever I turned off the GPS in app.
val settingsClient = LocationServices.getSettingsClient(this)
val locationRequest = LocationRequest()
val builder =
LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
.setAlwaysShow(false)
.setNeedBle(false)
settingsClient.checkLocationSettings(builder.build())
.addOnCompleteListener { task ->
if (task.isSuccessful) {
val response = task.result ?: return#addOnCompleteListener
val locationSettingsStates =
response.locationSettingsStates
Log.e("yyy", locationSettingsStates.toString())
// TODO
}
}
.addOnFailureListener { e ->
Timber.i("checkLocationSetting onFailure:" + e.message)
when ((e as ApiException).statusCode) {
LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> {
Timber.i("Location settings are not satisfied. Attempting to upgrade " + "location settings ")
try {
// Show the dialog by calling startResolutionForResult(), and check the
// result in onActivityResult().
val rae = e as ResolvableApiException
rae.startResolutionForResult(this, 0)
} catch (sie: IntentSender.SendIntentException) {
Timber.i("PendingIntent unable to execute request.")
}
}
else -> {
}
}
}
}
It's a way to do this:
I created a LocationUtil class:
class LocationUtil(context: Context) {
companion object {
const val MIN_TIME: Long = 1000L
const val MIN_DISTANCE: Float = 0.0f
}
private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private var locationListener: LocationListener? = null
val locationStateFlow = MutableStateFlow<Location>(Location(LocationManager.GPS_PROVIDER))
val gpsProviderState = mutableStateOf(false)
val isStart: MutableState<Boolean> = mutableStateOf(false)
private val locHandlerThread = HandlerThread("LocationUtil Thread")
init {
locHandlerThread.start()
}
#SuppressLint("MissingPermission")
fun start(minTimeMs: Long = MIN_TIME_MS, minDistanceM: Float = MIN_DISTANCE_M) {
locationListener().let {
locationListener = it
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, minTimeMs, minDistanceM, it, locHandlerThread.looper)
}
gpsProviderState.value = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
isStart.value = true
}
fun stop() {
locationListener?.let {
locationManager.removeUpdates(it)
}
isStart.value = false
}
private fun locationListener() = object : LocationListener {
override fun onLocationChanged(location: Location) {
locationStateFlow.value = location
}
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
}
override fun onProviderEnabled(provider: String) {
gpsProviderState.value = true
}
override fun onProviderDisabled(provider: String) {
gpsProviderState.value = false
}
}
}
and I create a file with some functions:
private const val REQUEST_CODE_LOCATION_SOURCE_SETTINGS = 200
fun getLocationManager(context: Context): LocationManager =
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
fun isLocEnable(context: Context): Boolean {
val locationManager = getLocationManager(context)
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
}
#Composable
fun IsLocationEnable(context: Context) {
if (!isLocEnable(context)) {
SimpleAlertDialog(
title = stringResource(id = R.string.title),
text = stringResource(id = R.string.dialog_gps_setting),
singleButton = false,
confirmText = stringResource(R.string.settings),
dismissText = stringResource(R.string.cancel),
onConfirm = {
if (it) {
Intent(
Settings.ACTION_LOCATION_SOURCE_SETTINGS,
Uri.fromParts(
"package",
context.packageName,
null
)
).also { intent ->
try {
context.startActivity(
intent
)
} catch (e: ActivityNotFoundException) {
Intent(
Settings.ACTION_LOCATION_SOURCE_SETTINGS
).also { intentCatch ->
context.startActivity(
intentCatch
)
}
}
}
}
})
}
}
#Composable
fun LocationState(context: Activity) {
IsLocationEnable(context)
}
and finally I used codes in the MainActivity:
Box(modifier = Modifier.fillMaxSize()) {
Column(modifier = Modifier.fillMaxSize()) {
ConnectivityStatus()
ClientScaffold(clientNavigator)
}
val gpsEnable by locationUtil.gpsProviderState
if (!gpsEnable) {
IsLocationEnable(this#MainActivity)
}
}
How about having a Data layer LocationRepository with a gpsStatus flow like this
class LocationRepository(/** inject context **/) {
val gpsStatus = flow {
val manager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
while (currentCoroutineContext().isActive) {
emit(
manager.isProviderEnabled(LocationManager.GPS_PROVIDER)
)
delay(3000)
}
}
}
Then observe it at UI layer, to hide/ show the dialog.
I've been experiencing a strange bug when retrieving a user location and displaying data based on the location retrieved in a recycler view. For context, whenever the application is started from fresh (no permissions granted) can retrieve the location. However, it doesn't display the expected content in the recycler view unless I close the application or press the bottom navigation bar.
Demonstration:
https://i.imgur.com/9kc1Zxc.gif
Fragment
const val TAG = "ForecastFragment"
#AndroidEntryPoint
class ForecastFragment : Fragment(), SearchView.OnQueryTextListener,
SwipeRefreshLayout.OnRefreshListener{
private val viewModel: WeatherForecastViewModel by viewModels()
private var _binding: FragmentForecastBinding? = null
private val binding get() = _binding!!
private lateinit var forecastAdapter: ForecastAdapter
private lateinit var searchMenuItem: MenuItem
private lateinit var searchView: SearchView
private lateinit var swipeRefreshLayout: SwipeRefreshLayout
private var currentQuery: String? = null
private lateinit var client: FusedLocationProviderClient
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.forecast_list_menu, menu)
searchMenuItem = menu.findItem(R.id.menu_search)
searchView = searchMenuItem.actionView as SearchView
searchView.isSubmitButtonEnabled = true
searchView.setOnQueryTextListener(this)
return super.onCreateOptionsMenu(menu, inflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentForecastBinding.inflate(inflater, container, false)
binding.lifecycleOwner = this
client = LocationServices.getFusedLocationProviderClient(requireActivity())
swipeRefreshLayout = binding.refreshLayoutContainer
setupRecyclerView()
getLastLocation()
updateSupportActionbarTitle()
swipeRefreshLayout.setOnRefreshListener(this)
return binding.root
}
private fun updateSupportActionbarTitle() {
viewModel.queryMutable.observe(viewLifecycleOwner, Observer { query ->
(activity as AppCompatActivity).supportActionBar?.title = query
})
}
private fun setupRecyclerView() {
forecastAdapter = ForecastAdapter()
binding.forecastsRecyclerView.apply {
adapter = forecastAdapter
layoutManager = LinearLayoutManager(activity)
}
}
private fun requestWeatherApiData() {
lifecycleScope.launch {
viewModel.weatherForecast.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
// nothing for now
swipeRefreshLayout.isRefreshing = false
}
}
})
}
}
private fun searchWeatherApiData(searchQuery: String) {
viewModel.searchWeatherForecast(viewModel.applySearchQuery(searchQuery))
viewModel.searchWeatherForecastResponse.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
swipeRefreshLayout.isRefreshing = false
}
}
})
}
private fun getWeatherApiDataLocation(city: String) {
viewModel.weatherForecastLocation(viewModel.applyLocationQuery(city))
viewModel.weatherForecastLocation.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
forecastAdapter.notifyDataSetChanged()
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
swipeRefreshLayout.isRefreshing = false
}
}
})
}
private fun fetchForecastAsync(query: String?) {
if (query == null)
requestWeatherApiData()
else
searchWeatherApiData(query)
}
override fun onQueryTextSubmit(query: String?): Boolean {
if (query != null) {
currentQuery = query
viewModel.queryMutable.value = query
searchWeatherApiData(query)
}
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
return true
}
override fun onRefresh() {
currentQuery = viewModel.queryMutable.value
fetchForecastAsync(currentQuery)
}
private fun isLocationEnabled(): Boolean {
val locationManager: LocationManager =
requireActivity().getSystemService(Context.LOCATION_SERVICE) as LocationManager
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|| locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
}
private fun checkPermissions(): Boolean {
if (ActivityCompat.checkSelfPermission(requireContext(),
Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(requireContext(),
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED){
return true
}
return false
}
private fun requestPermissions() {
ActivityCompat.requestPermissions(
requireActivity(),
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION), PERMISSION_ID)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == PERMISSION_ID) {
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
getLastLocation()
}
}
}
#SuppressLint("MissingPermission")
private fun getLastLocation() {
if (checkPermissions()) {
if (isLocationEnabled()) {
client.lastLocation.addOnCompleteListener(requireActivity()) { task ->
val location: Location? = task.result
if (location == null) {
requestNewLocationData()
} else {
val geoCoder = Geocoder(requireContext(), Locale.getDefault())
val addresses = geoCoder.getFromLocation(
location.latitude, location.longitude, 1)
val city = addresses[0].locality
viewModel.queryMutable.value = city
getWeatherApiDataLocation(city)
}
}
} else {
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
startActivity(intent)
}
} else {
requestPermissions()
}
}
#SuppressLint("MissingPermission")
private fun requestNewLocationData() {
val mLocationRequest = LocationRequest.create().apply {
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
interval = 0
fastestInterval = 0
numUpdates = 1
}
client = LocationServices.getFusedLocationProviderClient(requireActivity())
client.requestLocationUpdates(
mLocationRequest, mLocationCallback,
Looper.myLooper()
)
}
private val mLocationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
val mLastLocation: Location = locationResult.lastLocation
mLastLocation.latitude.toString()
mLastLocation.longitude.toString()
}
}
}
View Model
#HiltViewModel
class WeatherForecastViewModel
#Inject constructor(
private val repository: WeatherForecastRepository,
application: Application) :
AndroidViewModel(application) {
private val unit = "imperial"
private var query = "Paris"
val weatherForecast: MutableLiveData<Resource<WeatherForecastResponse>> = MutableLiveData()
var searchWeatherForecastResponse: MutableLiveData<Resource<WeatherForecastResponse>> = MutableLiveData()
val weatherForecastLocation: MutableLiveData<Resource<WeatherForecastResponse>> = MutableLiveData()
var queryMutable: MutableLiveData<String> = MutableLiveData()
init {
//queryMutable.value = query
//getWeatherForecast(queryMutable.value.toString(), unit)
}
private fun getWeatherForecast(query: String, units: String) =
viewModelScope.launch {
weatherForecast.postValue(Resource.Loading())
val response = repository.getWeatherForecast(query, units)
weatherForecast.postValue(weatherForecastResponseHandler(response))
}
private suspend fun searchWeatherForecastSafeCall(searchQuery: Map<String, String>) {
searchWeatherForecastResponse.postValue(Resource.Loading())
val response = repository.searchWeatherForecast(searchQuery)
searchWeatherForecastResponse.postValue(weatherForecastResponseHandler(response))
}
private suspend fun getWeatherForecastLocationSafeCall(query: Map<String, String>) {
weatherForecastLocation.postValue(Resource.Loading())
val response = repository.getWeatherForecastLocation(query)
weatherForecastLocation.postValue(weatherForecastResponseHandler(response))
}
fun searchWeatherForecast(searchQuery: Map<String, String>) =
viewModelScope.launch {
searchWeatherForecastSafeCall(searchQuery)
}
fun weatherForecastLocation(query: Map<String, String>) =
viewModelScope.launch {
getWeatherForecastLocationSafeCall(query)
}
fun applySearchQuery(searchQuery: String): HashMap<String, String> {
val queries: HashMap<String, String> = HashMap()
queries[QUERY_CITY] = searchQuery
queries[QUERY_UNITS] = unit
queries[QUERY_COUNT] = API_COUNT
queries[QUERY_API] = API_KEY
return queries
}
fun applyLocationQuery(query: String): HashMap<String, String> {
val queries: HashMap<String, String> = HashMap()
queries[QUERY_CITY] = query
queries[QUERY_UNITS] = unit
queries[QUERY_COUNT] = API_COUNT
queries[QUERY_API] = API_KEY
return queries
}
private fun weatherForecastResponseHandler(
response: Response<WeatherForecastResponse>): Resource<WeatherForecastResponse> {
if (response.isSuccessful) {
response.body()?.let { result ->
return Resource.Success(result)
}
}
return Resource.Error(response.message())
}
}
I feel the problem lies in the way, you are setting up your observers, you are setting them up in a function call, I believe that is wrong and that is setting up multiple observers for the same thing. It should just be setup once.
So I suggest you take out your observers to a separate function and call it once like observeProperties()
So your code will be like this
private fun observeProperties() {
viewModel.searchWeatherForecastResponse.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
swipeRefreshLayout.isRefreshing = false
}
}
})
viewModel.weatherForecastLocation.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
forecastAdapter.notifyDataSetChanged()
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
swipeRefreshLayout.isRefreshing = false
}
}
})
viewModel.searchWeatherForecastResponse.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
swipeRefreshLayout.isRefreshing = false
}
}
})
}
Now you can call this method at the top as such
setupRecyclerView()
getLastLocation()
updateSupportActionbarTitle()
swipeRefreshLayout.setOnRefreshListener(this)
observeProperties()
Your other methods simply will be now the queries like this
private fun getWeatherApiDataLocation(city: String) {
viewModel.weatherForecastLocation(viewModel.applyLocationQuery(city))
}
private fun searchWeatherApiData(searchQuery: String) {
viewModel.searchWeatherForecast(viewModel.applySearchQuery(searchQuery))
}