Here's my Repo
interface TrendingRepository{
fun getTrendingRepos()
Test Class
#RunWith(JUnit4::class)
class TrendingViewModelTest {
private val trendingRepository = mock(TrendingRepository::class.java)
private var trendingViewModel = TrendingViewModel(trendingRepository)
#Test
fun testWithNoNetwork() {
trendingViewModel.isConnected = false
verify(trendingRepository, never()).getTrendingRepos()
}
#Test
fun testWithNetwork() {
trendingViewModel.isConnected = true
verify(trendingRepository, never()).getTrendingRepos()
}
}
TrendingViewModel
fun fetchTrendingRepos() {
if (isConnected) {
loadingProgress.value = true
compositeDisposable.add(
trendingRepository.getTrendingRepos().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ response ->
run {
loadingProgress.value = false
},
{ error ->
loadingProgress.value = false
}
)
)
}
Both these test are passing, however TrendingViewModel is only calling getTrendingRepos() only when there is active network, isConnected = true
You should call function that you're testing before verification:
#RunWith(JUnit4::class)
class TrendingViewModelTest {
private val trendingRepository = mock(TrendingRepository::class.java)
private var trendingViewModel = TrendingViewModel(trendingRepository)
#Test
fun testWithNoNetwork() {
trendingViewModel.isConnected = false
trendingViewModel.fetchTrendingRepos()
verify(trendingRepository, never()).getTrendingRepos()
}
#Test
fun testWithNetwork() {
trendingViewModel.isConnected = true
trendingViewModel.fetchTrendingRepos()
verify(trendingRepository, times(1)).getTrendingRepos()
}
}
Related
I'm trying to recreate this function using LiveData to a function doing the same but with channelFlow; Always set isLoading is boring to me. Rewrite a try finally everytime too.
LiveData way :
private val _isLoading: MutableLiveData<Boolean> = MutableLiveData(false)
val isLoading: LiveData<Boolean> = _isLoading
protected fun process(job: () -> Job) {
if (isLoading.value == true) return
_isLoading.value = true
viewModelScope.launch(Default) {
try {
job().join()
} finally {
withContext(Main) { _isLoading.value = false }
}
}
}
fun onSend() {
process {
viewModelScope.launch(Default) {
myRepository.insert("data the user had insert").let { isSuccessful ->
// do something
}
}
}
}
I tried this but cant make it work properly
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
private fun process(job: () -> Job): Flow<Any?> = channelFlow {
if (isLoading.value) return#channelFlow
_isLoading.value = true
try {
val data = job().join()
send(data)
} finally {
_isLoading.value = false
}
}
fun onSend(): Flow<Parcelable?> = process {
myRepository.insert("data the user had insert").let { result ->
// return the result
}
}
Any idea please ?
In my application I want check internet connection and for this I write below codes.
But when before run application and disconnect internet, not call checked internet code!
Just when internet connection is connected and run application with connected internet then disconnect internet show internet is connect or disconnect!
Internet connection class:
class NetworkConnectivity #Inject constructor(private val manager: ConnectivityManager, private val request: NetworkRequest) : ConnectivityStatus {
override fun observe(): Flow<Boolean> {
return callbackFlow {
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
launch { send(true) }
}
override fun onLost(network: Network) {
super.onLost(network)
launch { send(false) }
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
manager.registerDefaultNetworkCallback(callback)
} else {
manager.registerNetworkCallback(request, callback)
}
awaitClose {
manager.unregisterNetworkCallback(callback)
}
}
}
}
ViewModel class:
#HiltViewModel
class DetailViewModel #Inject constructor(private val repository: MainRepository) : ViewModel() {
#Inject
lateinit var networkConnectivity: NetworkConnectivity
val detailData = MutableLiveData<NetworkRequest<ResponseDetailPage>>()
fun callDetailApi(id: Int, apiKey: String) = viewModelScope.launch {
Log.e("DetailLog","ViewModel 1")
networkConnectivity.observe().collect {
if (it) {
Log.e("DetailLog","ViewModel 2")
detailData.value = NetworkRequest.Loading()
//Response
val response = repository.remote.recipeInformation(id, apiKey)
detailData.value = NetworkResponse(response).generalNetworkResponse()
} else {
Log.e("DetailLog","ViewModel 3")
detailData.value = NetworkRequest.Error("No internet connection")
}
}
}
}
After run application (when internet is disconnected) just show ViewModel 1 log in logcat!
Why not checked internet when start application with disconnected mode?
I'm using this function to check for internet connectivity:
fun isInternetAvailable(context: Context): Boolean {
var result = false
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val networkCapabilities = connectivityManager.activeNetwork ?: return false
val actNw =
connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false
result = when {
actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
else -> false
}
} else {
connectivityManager.run {
connectivityManager.activeNetworkInfo?.run {
result = when (type) {
ConnectivityManager.TYPE_WIFI -> true
ConnectivityManager.TYPE_MOBILE -> true
ConnectivityManager.TYPE_ETHERNET -> true
else -> false
}
}
}
}
return result
}
If you target SDK23+, you can remove build version check.
Here's how I call this function in my viewmodel:
private val _internetIsAvailable = MutableLiveData<Boolean?>()
val extInternetAvailable: LiveData<Boolean?> get() = _internetIsAvailable
private val _loadStatus = MutableLiveData<LoadStatus>()
val extLoadStatus: LiveData<LoadStatus> get() = _loadStatus
init {
// runs every time VM is created (not view created)
viewModelScope.launch {
_loadStatus.value = LoadStatus.LOADING
_internetIsAvailable.value = NetworkChecker(app).isInternetAvailable()
_loadStatus.value = LoadStatus.DONE
}
}
In fragments:
private fun observeInternetAvailability() {
mainVM.extInternetAvailable.observe(this) { intAvailable ->
Logger.d("Internet is $intAvailable")
if (intAvailable == false) {
ToastUtils.updateWarning(this, getString(R.string.no_internet))
showLoading(true)
}
}
}
I following the new google guide and end with this to represent the state in viewmodel like tis
var uiState: AccountSettingUiState by
mutableStateOf(AccountSettingUiState.Initial)
private set
then I have this function
fun resetPasswordUseCase(context: Context) {
resetPasswordUseCase.execute(context)
.subscribeOn(rxSchedulers.io)
.doOnSubscribe {
uiState = AccountSettingUiState.Loading
}
.observeOn(rxSchedulers.ui)
.subscribe {
uiState = AccountSettingUiState.Result
}
}
I want to test this function by assert emitting loading then result but how
I can capture the values
I can test only final state
ViewModel
#HiltViewModel
class SearchViewModel #Inject constructor(
private val searchItemUseCase: SearchItemUseCase,
private val searchUIMapper: SearchUIMapper
) : ViewModel() {
var search by mutableStateOf<StateUI<SearchUI>>(StateUI.Init())
fun searchItem(query: String) {
viewModelScope.launch {
search = StateUI.Loading()
searchItemUseCase.execute(query)
.catch { error ->
search = StateUI.Error(error)
}
.map {
searchUIMapper.map(it)
}.collect {
search = if (it.results.isEmpty()) {
StateUI.Error(EmptySearchException())
} else {
StateUI.Success(it)
}
}
}
}
}
Test
#ExperimentalCoroutinesApi
#ExperimentalTime
class SearchViewModelTest {
private lateinit var searchViewModel: SearchViewModel
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#get:Rule
var coroutineRule = TestCoroutineRule()
#MockK(relaxed = true)
private lateinit var searchItemUseCase: SearchItemUseCase
private val searchUIMapper by lazy {
SearchUIMapper(ItemUIMapper())
}
#Before
fun setup() {
MockKAnnotations.init(this)
searchViewModel = SearchViewModel(
searchItemUseCase,
searchUIMapper
)
}
#Test
fun success() = coroutineRule.runBlockingTest {
val search = getSearchEntity(itemEntityList)
//Given
coEvery { searchItemUseCase.execute("") } returns flow {
emit(search)
}
searchViewModel.searchItem("")
assertEquals(StateUI.Success(searchUIMapper.map(search)), searchViewModel.search)
}
#Test
fun emptySearch() = coroutineRule.runBlockingTest {
val search = getSearchEntity(emptyList())
//Given
coEvery { searchItemUseCase.execute("") } returns flow {
emit(search)
}
//When
searchViewModel.searchItem("")
//Verify
assertTrue((searchViewModel.search as StateUI.Error<SearchUI>).data is EmptySearchException)
}
#Test
fun error() = coroutineRule.runBlockingTest {
val error = NoConnectivityException()
//Given
coEvery { searchItemUseCase.execute("") } returns flow {
throw error
}
//When
searchViewModel.searchItem("")
assertEquals(StateUI.Error<SearchUI>(error), searchViewModel.search)
}
}
I am almost new to android testing and following the official docs and Udacity course for learning purposes.
Coming to the issue I want to check when the task is completed or incompleted to be displayed properly or not, for this I wrote a few tests. Here I got the exception that toast can not be displayed on a thread that has not called Looper.prepare.
When I comment out the toast msg live data updating line of code then all tests work fine and pass successfully. I am new to android testing and searched out a lot but did not get any info to solve this issue. Any help would be much appreciated. A little bit of explanation will be much more helpful if provided.
Below is my test class source code along with ViewModel, FakeRepository, and fragment source code.
Test Class.
#ExperimentalCoroutinesApi
#MediumTest
#RunWith(AndroidJUnit4::class)
class TaskDetailFragmentTest {
#get:Rule
var mainCoroutineRule = MainCoroutineRule()
#get:Rule
val rule = InstantTaskExecutorRule()
private lateinit var tasksRepository: FakeTasksRepository
#Before
fun setUp() {
tasksRepository = FakeTasksRepository()
ServiceLocator.taskRepositories = tasksRepository
}
#Test
fun addNewTask_addNewTaskToDatabase() = mainCoroutineRule.runBlockingTest {
val newTask = Task(id = "1", userId = 0, title = "Hello AndroidX World",false)
tasksRepository.addTasks(newTask)
val task = tasksRepository.getTask(newTask.id)
assertEquals(newTask.id,(task as Result.Success).data.id)
}
#Test
fun activeTaskDetails_DisplayedInUi() = mainCoroutineRule.runBlockingTest {
val newTask = Task(id = "2", userId = 0, title = "Hello AndroidX World",false)
tasksRepository.addTasks(newTask)
val bundle = TaskDetailFragmentArgs(newTask.id).toBundle()
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.Theme_ToDoWithTDD)
onView(withId(R.id.title_text)).check(matches(isDisplayed()))
onView(withId(R.id.title_text)).check(matches(withText("Hello AndroidX World")))
onView(withId(R.id.complete_checkbox)).check(matches(isDisplayed()))
onView(withId(R.id.complete_checkbox)).check(matches(isNotChecked()))
}
#Test
fun completedTaskDetails_DisplayedInUI() = mainCoroutineRule.runBlockingTest {
val newTask = Task(id = "2", userId = 0, title = "Hello AndroidX World",true)
tasksRepository.addTasks(newTask)
val bundle = TaskDetailFragmentArgs(newTask.id).toBundle()
launchFragmentInContainer <TaskDetailFragment>(bundle,R.style.Theme_ToDoWithTDD)
onView(withId(R.id.title_text)).check(matches(isDisplayed()))
onView(withId(R.id.title_text)).check(matches(withText("Hello AndroidX World")))
onView(withId(R.id.complete_checkbox)).check(matches(isDisplayed()))
onView(withId(R.id.complete_checkbox)).check(matches(isChecked()))
}
#After
fun tearDown() = mainCoroutineRule.runBlockingTest {
ServiceLocator.resetRepository()
}
}
FakeRepository class.
class FakeTasksRepository: TasksRepository {
var tasksServiceData: LinkedHashMap<String,Task> = LinkedHashMap()
private val observableTasks: MutableLiveData<Result<List<Task>>> = MutableLiveData()
private var shouldReturnError: Boolean = false
fun setReturnError(value: Boolean) {
shouldReturnError = value
}
override fun observeTasks(): LiveData<Result<List<Task>>> {
return observableTasks
}
override fun observeTask(taskId: String): LiveData<Result<Task>> {
runBlocking { fetchAllToDoTasks() }
return observableTasks.map { tasks ->
when(tasks) {
is Result.Loading -> Result.Loading
is Result.Error -> Result.Error(tasks.exception)
is Result.Success -> {
val task = tasks.data.firstOrNull() { it.id == taskId }
?: return#map Result.Error(Exception("Not found"))
Result.Success(task)
}
}
}
}
override suspend fun completeTask(id: String) {
tasksServiceData[id]?.completed = true
}
override suspend fun completeTask(task: Task) {
val compTask = task.copy(completed = true)
tasksServiceData[task.id] = compTask
fetchAllToDoTasks()
}
override suspend fun activateTask(id: String) {
tasksServiceData[id]?.completed = false
}
override suspend fun activateTask(task: Task) {
val activeTask = task.copy(completed = false)
tasksServiceData[task.id] = activeTask
fetchAllToDoTasks()
}
override suspend fun getTask(taskId: String): Result<Task> {
if (shouldReturnError) return Result.Error(Exception("Test Exception"))
tasksServiceData[taskId]?.let {
return Result.Success(it)
}
return Result.Error(Exception("Could not find task"))
}
override suspend fun getTasks(): Result<List<Task>> {
return Result.Success(tasksServiceData.values.toList())
}
override suspend fun saveTask(task: Task) {
tasksServiceData[task.id] = task
}
override suspend fun clearAllCompletedTasks() {
tasksServiceData = tasksServiceData.filterValues {
!it.completed
} as LinkedHashMap<String, Task>
}
override suspend fun deleteAllTasks() {
tasksServiceData.clear()
fetchAllToDoTasks()
}
override suspend fun deleteTask(taskId: String) {
tasksServiceData.remove(taskId)
fetchAllToDoTasks()
}
override suspend fun fetchAllToDoTasks(): Result<List<Task>> {
if(shouldReturnError) {
return Result.Error(Exception("Could not find task"))
}
val tasks = Result.Success(tasksServiceData.values.toList())
observableTasks.value = tasks
return tasks
}
override suspend fun updateLocalDataStore(list: List<Task>) {
TODO("Not yet implemented")
}
fun addTasks(vararg tasks: Task) {
tasks.forEach {
tasksServiceData[it.id] = it
}
runBlocking {
fetchAllToDoTasks()
}
}
}
Fragment class.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.loadTaskById(args.taskId)
setUpToast(this,viewModel.toastText)
viewModel.editTaskEvent.observe(viewLifecycleOwner, {
it?.let {
val action = TaskDetailFragmentDirections
.actionTaskDetailFragmentToAddEditFragment(
args.taskId,
resources.getString(R.string.edit_task)
)
findNavController().navigate(action)
}
})
binding.editTaskFab.setOnClickListener {
viewModel.editTask()
}
}
ViewModel class.
class TaskDetailViewModel(
private val tasksRepository: TasksRepository
) : ViewModel() {
private val TAG = "TaskDetailViewModel"
private val _taskId: MutableLiveData<String> = MutableLiveData()
private val _task = _taskId.switchMap {
tasksRepository.observeTask(it).map { res ->
Log.d("Test","res with value ${res.toString()}")
isolateTask(res)
}
}
val task: LiveData<Task?> = _task
private val _toastText = MutableLiveData<Int?>()
val toastText: LiveData<Int?> = _toastText
private val _dataLoading = MutableLiveData<Boolean>()
val dataLoading: LiveData<Boolean> = _dataLoading
private val _editTaskEvent = MutableLiveData<Unit?>(null)
val editTaskEvent: LiveData<Unit?> = _editTaskEvent
fun loadTaskById(taskId: String) {
if(dataLoading.value == true || _taskId.value == taskId) return
_taskId.value = taskId
Log.d("Test","loading task with id $taskId")
}
fun editTask(){
_editTaskEvent.value = Unit
}
fun setCompleted(completed: Boolean) = viewModelScope.launch {
val task = _task.value ?: return#launch
if(completed) {
tasksRepository.completeTask(task.id)
_toastText.value = R.string.task_marked_complete
}
else {
tasksRepository.activateTask(task.id)
_toastText.value = R.string.task_marked_active
}
}
private fun isolateTask(result: Result<Task?>): Task? {
return if(result is Result.Success) {
result.data
} else {
_toastText.value = R.string.loading_tasks_error
null
}
}
#Suppress("UNCHECKED_CAST")
class TasksDetailViewModelFactory(
private val tasksRepository: TasksRepository
): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return (TaskDetailViewModel(
tasksRepository
) as T)
}
}
}
In this method in ViewModel when I comment out the below line of code all tests passed.
_toastText.value = R.string.loading_tasks_error
private fun isolateTask(result: Result<Task?>): Task? {
return if(result is Result.Success) {
result.data
} else {
_toastText.value = R.string.loading_tasks_error // Comment out this line then all test passed.
null
}
}
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))
}