我希望当我点击其中一个侧边菜单击中API时,观察者将被触发。当我点击其中一个菜单时,Retrofit
实际上给了我正确值的响应。问题是,Observer
不会第二次被触发。我已经跟踪了这个问题,并发现我的Repository
没有返回值,即使我的Retrofit已经更新了MutableLiveData
。
RemoteDataSource.kt
override fun getDisastersByFilter(filter: String?): LiveData<ApiResponse<DisastersDTO?>> {
val result = MutableLiveData<ApiResponse<DisastersDTO?>>()
apiService.getDisastersByFilter(filter).enqueue(object : Callback<DisastersResponse> {
override fun onResponse(
call: Call<DisastersResponse>,
response: Response<DisastersResponse>
) {
if(response.isSuccessful) {
val data = response.body()
data?.disastersDTO?.let {
result.postValue(ApiResponse.Success(it))
Log.d("RemoteDataSource", "$it")
} ?: run {
result.postValue(ApiResponse.Error("Bencana alam tidak ditemukan"))
}
} else {
result.postValue(ApiResponse.Error("Terjadi kesalahan!"))
}
}
override fun onFailure(call: Call<DisastersResponse>, t: Throwable) {
result.postValue(ApiResponse.Error(t.localizedMessage!!))
Log.d("RemoteDataSource", t.localizedMessage!!)
}
})
return result
}
Repository.kt
override fun getDisastersByFilter(filter: String?): LiveData<Resource<List<Disaster>>> =
remoteDataSource.getDisastersByFilter(filter).map {
when (it) {
is ApiResponse.Empty -> Resource.Error("Terjadi error")
is ApiResponse.Error -> Resource.Error(it.errorMessage)
is ApiResponse.Loading -> Resource.Loading()
is ApiResponse.Success -> Resource.Success(
DataMapper.disastersResponseToDisasterDomain(
it.data
)
)
}
}
SharedViewModel.kt
fun getDisastersByFilter(filter: String? = "gempa"): LiveData<Resource<List<Disaster>>> =
useCase.getDisastersByFilter(filter)
Here's the **MapsFragment**
private val viewModel: SharedViewModel by activityViewModels()
viewModel.getDisastersByFilter("gempa").observe(viewLifecycleOwner) {
when (it) {
is Resource.Success -> {
Log.d("MapsFragmentFilter", "${it.data}")
it.data?.let { listDisaster ->
if(listDisaster.isNotEmpty()) {
map.clear()
addGeofence(listDisaster)
listDisaster.map { disaster ->
placeMarker(disaster)
addCircle(disaster)
}
}
}
}
is Resource.Error -> Toast.makeText(context, "Filter Error", Toast.LENGTH_SHORT).show()
is Resource.Loading -> {}
}
}
下面是触发函数命中API的MainActivity
private val viewModel: SharedViewModel by viewModels()
binding.navViewMaps.setNavigationItemSelectedListener { menu ->
when (menu.itemId) {
R.id.filter_gempa -> viewModel.getDisastersByFilter("gempa")
R.id.filter_banjir -> viewModel.getDisastersByFilter("banjir")
R.id.about_us -> viewModel.getDisasters()
}
binding.drawerLayoutMain.closeDrawers()
true
}
我不能从你发布的内容中确定,但是你的菜单选项在SharedViewModel
上调用getDisastersByFilter
,并且看起来最终在RemoteDataSource
中调用getDisastersByFilter
。
函数创建一个新的LiveData
并返回它,所有其他函数(包括viewModel
中的函数)只返回新的LiveData
。所以如果你想看到最终发布到它的结果,你需要observe
这个新的。
我不知道你发布的片段代码是从哪里来的,但看起来你只是调用和观察viewModel.getDisastersByFilter
一次。因此,当第一次发生时,它进行数据获取,并在它返回的LiveData
上获得结果。从代码的外观来看,LiveData
将不会接收任何更多的结果-它是一次性的,一次性的东西,稍后会接收结果,然后它就没用了。
如果我得到了正确的,你需要重新工作你如何处理你的LiveData
s。片段需要得到每个viewModel.getDisastersByFilter
调用的结果,所以它可以观察结果-如果你的活动传递一个事件到片段("这个项目被点击")和片段处理调用VM,它可能会更好,它可以观察结果,而它在它(传递它到一个函数,连接起来,所以你不必不断重复你的观察者代码)
currentData
livedata,它被连接起来以显示不同的源livedata的值。然后,当您调用getDisastersByFilter
时,源livedata将被替换为新的livedata。currentData
获取张贴到这个新源的任何新值,并且片段只需要观察单个LiveData
一次。所有的数据都由虚拟机通过管道输入。
我没有时间做一个例子,但是看看这个转换的东西(这是一个开发人员的博客):https://medium.com/androiddevelopers/livedata-beyond-the-viewmodel-reactive-patterns-using-transformations-and-mediatorlivedata-fda520ba00b7
我认为你做错的是在使用改造时首先使用LiveData。
当代码同步运行时,您正在异步获得响应。因此,您需要使用suspend
来使用挂起函数。
当从ViewModel调用这个函数时,用viewModelScope.launch{}
fun getDisastersByFilter(filter: String? = "gempa") = viewModelScope.launch {
useCase.getDisastersByFilter(filter).collect{
// do something....
// assign the values to MutableLiveData or MutableStateFlows
}
}
你应该使用RxJava或CallbackFlow。
我更喜欢流,下面给出了一个例子,如果你使用回调流,你的代码看起来会是什么样子。
suspend fun getDisastersByFilter(filter: String?): Flow<ApiResponse<DisastersDTO?>> =
callbackFlow {
apiService.getDisastersByFilter(filter)
.enqueue(object : Callback<DisastersResponse> {
override fun onResponse(
call: Call<DisastersResponse>,
response: Response<DisastersResponse>
) {
if (response.isSuccessful) {
val data = response.body()
data?.disastersDTO?.let {
trySend(ApiResponse.Success(it))
// result.postValue(ApiResponse.Success(it))
Log.d("RemoteDataSource", "$it")
} ?: run {
trySend(ApiResponse.Error("Bencana alam tidak ditemukan"))
// result.postValue(ApiResponse.Error("Bencana alam tidak ditemukan"))
}
} else {
trySend(ApiResponse.Error("Terjadi kesalahan!"))
// result.postValue(ApiResponse.Error("Terjadi kesalahan!"))
}
}
override fun onFailure(call: Call<DisastersResponse>, t: Throwable) {
trySend(ApiResponse.Error(t.localizedMessage!!))
// result.postValue(ApiResponse.Error(t.localizedMessage!!))
Log.d("RemoteDataSource", t.localizedMessage!!)
}
})
awaitClose()
}