我正在学习通过MVVM体系结构在Kotlin中实现Rx Java。我有一个简单的应用程序,可以接收用户关于"意大利面"、"披萨"等菜肴名称的输入,然后按下按钮,我通过Rx-java调用Spoonacural Api,在logcat中显示Api调用返回的菜肴名称。
现在,每次我按下按钮,电话号码就会增加两个,每次我都会收到多个名字。有人能指导我做这件事吗。
Spoonacular Api数据类(这是一个很大的类,因此我刚刚包含了重要部分(->
object FilteredDishes {
data class DishesFromAPI(
val recipes: List<Recipe>
)
data class Recipe(
val aggregateLikes: Int,
val analyzedInstructions: List<AnalyzedInstruction>,
val cheap: Boolean,
.....
接口代码->
interface DishInterface {
@GET(Constants.spoonacularEndPoint)
fun getDishes(
@Query(Constants.apiKey) apiKey : String,
@Query(Constants.limitLicense) limitLicense : Boolean,
@Query(Constants.tags) tags : String,
@Query(Constants.number) number : Int
) : Single<FilteredDishes.DishesFromAPI>
}
改装生成器代码->
class DishApiService {
private val api=Retrofit.Builder().baseUrl(Constants.spoonacularBaseURL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build()
.create(DishInterface::class.java)
fun getDishFromInternet(type:String) : Single<FilteredDishes.DishesFromAPI> {
return api.getDishes(Constants.spoonacularAPiKeyValue,false,type,1)
}
}
查看模型代码->
class DishApiViewModel : ViewModel(){
private val dishApiService : DishApiService = DishApiService()
private val compositeDisposable : CompositeDisposable = CompositeDisposable()
val loadDish = MutableLiveData<Boolean>()
val dishResponse = MutableLiveData<FilteredDishes.DishesFromAPI>()
val dishLoadingError = MutableLiveData<Boolean>()
fun getRecipesFromAPI(filter:String){
loadDish.value=true
compositeDisposable.add(
dishApiService.getDishFromInternet(filter)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableSingleObserver<FilteredDishes.DishesFromAPI>(){
override fun onSuccess(value: FilteredDishes.DishesFromAPI?) {
loadDish.value=false
dishResponse.value=value!!
dishLoadingError.value=false
}
override fun onError(e: Throwable?) {
loadDish.value=false
dishLoadingError.value=true
e!!.printStackTrace()
}
})
)
}
}
XML文件->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.SearchFragment">
<EditText
android:id="@+id/et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/_16sdp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:autofillHints="" />
<Button
android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/clcick"
android:layout_marginTop="@dimen/_200sdp"
app:layout_constraintTop_toBottomOf="@id/et"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
片段代码->
class SearchFragment : Fragment() {
private var mBinding: FragmentSearchBinding? = null
private lateinit var mDishApiViewModel : DishApiViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
mBinding = FragmentSearchBinding.inflate(inflater, container, false)
return mBinding!!.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mDishApiViewModel= ViewModelProvider(this)[DishApiViewModel::class.java]
mBinding!!.btn.setOnClickListener{
if(mBinding!!.et.text.isEmpty()){
Toast.makeText(requireContext(), "Please enter dish type", Toast.LENGTH_SHORT).show()
}else{
apiCall(mBinding!!.et.text.toString())
}
}
}
private fun apiCall(filter:String){
mDishApiViewModel.getRecipesFromAPI(filter)
dishViewModelObserver()
}
private fun dishViewModelObserver(){
mDishApiViewModel.dishResponse.observe(viewLifecycleOwner,
{
if(it!=null){
if(it.recipes.isNotEmpty()){
Log.e("Response", it.recipes[0].title)
}else{
Toast.makeText(requireContext(), "error", Toast.LENGTH_SHORT).show()
}
}
})
mDishApiViewModel.dishLoadingError.observe(viewLifecycleOwner,
{
if(it!=null){
Log.e("loadingError","$it")
}
}
)
mDishApiViewModel.loadDish.observe(viewLifecycleOwner,
{
if(it!=null){
Log.e("load","$it")
}
})
}
override fun onDestroyView() {
super.onDestroyView()
mBinding = null
}
}
这是因为您调用dishViewModelObserver
,并且每次向LiveData
的观察者列表中添加另一个观察者时。
您需要将该函数从apiCall
移动到onViewCreated
:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mDishApiViewModel = ViewModelProvider(this)[DishApiViewModel::class.java]
dishViewModelObserver()
mBinding!!.btn.setOnClickListener{
if (mBinding!!.et.text.isEmpty()) {
Toast.makeText(requireContext(), "Please enter dish type", Toast.LENGTH_SHORT).show()
} else {
apiCall(mBinding!!.et.text.toString())
}
}
}
private fun apiCall(filter: String) {
mDishApiViewModel.getRecipesFromAPI(filter)
}
提示:您还需要摆脱使用m
前缀的习惯,这个约定被添加到Java中以区分属性类型,但在Kotlin getter和setter中,JVM函数是为您的属性生成的。
即Java
void setThing(thing: Thing) {
mThing = thing
}
在上有一些意义
void setThing(thing: Thing) {
this.Thing = thing
}