Livedata观察者第二次触发

发布于 2025-02-05 16:49:02 字数 4118 浏览 2 评论 0 原文

我希望当我单击一个侧菜单之一时,观察者将触发API时会触发。当我单击菜单之一时, 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
                    )
                )
            }
        }

shardviewmodel.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 -> {}
            }
        }

这是 mainActivity ,它触发了点击API的功能

    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
        }

I'm expecting that the observer will be triggered when I'm hitting API by clicking one of the side menu. When I clicked one of the menu, Retrofit actually gave me the response with the correct value. The problem is, the Observer isn't getting triggered for the second time. I've trace the problem and find out that my Repository isn't returning a value even though my Retrofit already update the 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 -> {}
            }
        }

Here's the MainActivity that triggers the function to hit API

    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
        }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

旧时模样 2025-02-12 16:49:04

我无法确定您的发布内容,但是您的菜单选项调用 getDisastersbyfilter sharedViewModel ,看起来最终会调用 getdisasterbyfilter remotedatasource 中。

该功能创建一个新的 livedata 并返回,以及您的所有其他功能(包括 viewModel 中的一个功能),只需返回该新 livedata 。因此,如果您想查看最终发布的结果,则需要观察该新的。

我不知道您发布的片段代码来自何处,但看起来您只是在打电话和观察 viewModel.getDisastersbyfilter 曾经 。因此,当第一次发生这种情况时,它会进行数据获取,然后在 livedata 上获得结果。 livedata 从代码的外观中不会再收到任何结果 - 这是一次一次性的一次性东西,以后再接收结果,然后毫无用处。


如果我做对了,您需要重新处理如何处理 livedata s。片段需要获得每个 viewModel.getDisastersbyfilter 调用的结果,因此可以观察结果 - 如果您的活动将事件传递给片段可能会更好(“此项目已单击”)和片段处理呼叫VM,并且可以在结果时观察结果(将其传递给函数,以使其启动,以便您不必继续重复观察者代码)

另一种方法是让片段观察 currentData livedata,这是有线的,以显示A 不同的源源的值。然后,当您调用 getDisastersbyfilter 时,该源将交换为新的。 CurrentData 一个人将任何新值发布到此新来源,而片段只需要观察一次 livedata 一次。所有数据都通过VM将其输送到其中。

我没有时间做一个示例,但请看一下此转换内容(这是开发人员的博客之一):https://medium.com/androiddevelopers/livedata-beyond-the-viewmodel-reactive-patterns-using-transformations-和纳入属于fda520ba00b7的

I can't be sure from what you've posted, but your menu options call getDisastersByFilter on your SharedViewModel, and it looks like that eventually calls through to getDisastersByFilter in RemoteDataSource.

That function creates a new LiveData and returns it, and all your other functions (including the one in viewModel) just return that new LiveData. So if you want to see the result that's eventually posted to it, you need to observe that new one.

I don't know where the fragment code you posted is from, but it looks like you're just calling and observing viewModel.getDisastersByFilter once. So when that first happens, it does the data fetch and you get a result on the LiveData it returned. That LiveData won't receive any more results, from the looks of your code - it's a one-time, disposable thing that receives a result later, and then it's useless.


If I've got that right, you need to rework how you're handling your LiveDatas. The fragment needs to get the result of every viewModel.getDisastersByFilter call, so it can observe the result - it might be better if your activity passes an event to the fragment ("this item was clicked") and the fragment handles calling the VM, and it can observe the result while it's at it (pass it to a function that wires that up so you don't have to keep repeating your observer code)

The other approach would be to have the Fragment observe a currentData livedata, that's wired up to show the value of a different source livedata. Then when you call getDisastersByFilter, that source livedata is swapped for the new one. The currentData one gets any new values posted to this new source, and the fragment only has to observe that single LiveData once. All the data gets piped into it by the VM.

I don't have time to do an example, but have a look at this Transformations stuff (this is one of the developers' blogs): https://medium.com/androiddevelopers/livedata-beyond-the-viewmodel-reactive-patterns-using-transformations-and-mediatorlivedata-fda520ba00b7

初吻给了烟 2025-02-12 16:49:04

我相信您做错了的是首先使用改造时使用Livedata。

当您的代码同步运行时,您会异步获得响应。因此,您需要使用 sustend 来利用暂停功能。

并且在从ViewModel调用此函数时,将其用 viewModelsCope.launch {} 将其包裹起来

fun getDisastersByFilter(filter: String? = "gempa") = viewModelScope.launch {
    useCase.getDisastersByFilter(filter).collect{
  // do something....
  // assign the values to MutableLiveData or MutableStateFlows
  }
}

我更喜欢流,下面给出的是一个示例,说明了您使用回调流程的情况。

    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()
        }

What I believe you are doing wrong is using LiveData in the first place while using a retrofit.

You are getting a response asynchronously while your code is running synchronously. So, you need to make use of suspending functions by using suspend.

And while calling this function from ViewModel, wrap it with viewModelScope.launch{}

fun getDisastersByFilter(filter: String? = "gempa") = viewModelScope.launch {
    useCase.getDisastersByFilter(filter).collect{
  // do something....
  // assign the values to MutableLiveData or MutableStateFlows
  }
}

You should either be using RxJava or CallbackFlow.

I prefer Flows, given below is an example of how your code might look if you use callback flow.

    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()
        }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文