使用 ViewModel 和 LiveData 多次改造执行 API

Posted

技术标签:

【中文标题】使用 ViewModel 和 LiveData 多次改造执行 API【英文标题】:Retrofit execute API multiple times using ViewModel and LiveData 【发布时间】:2021-02-23 15:25:13 【问题描述】:

所以当我执行操作单击以请求 API GET 时遇到问题,我使用 MVVM 和 LiveData 组合从 API 获取值多次命中端点。 下面的代码

ApiService.kt

   class ApiService 
       private var retrofit : Retrofit? = null
       private val okHttpBuilder = OkHttpClient.Builder()
           .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
           .connectTimeout(120, TimeUnit.SECONDS)
           .readTimeout(120, TimeUnit.SECONDS)
           .writeTimeout(120, TimeUnit.SECONDS)
           .retryOnConnectionFailure(false)
           .build()
   
   
       fun <S> createService(serviceClass: Class<S>?): S 
           if(retrofit == null)
               retrofit = Retrofit.Builder().addConverterFactory(GsonConverterFactory.create())
                   .baseUrl(BASE_URL)
                   .client(okHttpBuilder)
                   .build()
           
           return retrofit!!.create(serviceClass!!)
       
     

      val serviceGuestMerchants : GuestMerchantsService by lazy
          createService(GuestMerchantsService::class.java)
      
   

界面

GuestMerchantService.kt

    interface GuestMerchantsService 
        @GET(ConstantGuestMerchants.BASE_URL_GUEST_MERCHANT)
        suspend fun getListMerchant(@Query("page") page :Int?, @Query("order-direction") orderDirection : 
        String) : ResponseListMerchant
    
        @GET(ConstantGuestMerchants.BASE_URL_GUEST_MERCHANT_DETAIL)
        suspend fun getPreviewMerchant(@Path("id") id:Int) : ResponsePreviewMerchant
    
        @GET(ConstantGuestMerchants.BASE_URL_GUEST_MERCHANT_PRODUCT_LIST)
        suspend fun getListProductByMerchant(
            @Path("id") id : Int,
            @Query("page") page : Int?) : ResponseProductByMerchant
    
        @GET(ConstantGuestMerchants.BASE_URL_GUEST_MERCHANT_STATUS)
        suspend fun getStatusMerchant(@Header("Authorization") authorization : String) : 
        ResponseGetStatusMerchant
    
        @Multipart
        @POST(ConstantGuestMerchants.BASE_URL_GUEST_MERCHANT_REQUEST_MERCHANT)
        fun registerMerchant(@Header("Authorization") authorization: String,
                             @Part fotoKtp : MultipartBody.Part, @Part fotoPemilik : MultipartBody.Part, 
                             @Part fotoToko : MultipartBody.Part,
                             @Part namaToko : MultipartBody.Part,@Part noHp : MultipartBody.Part ,
                             @Part alamat : MultipartBody.Part, @Part noKtp : MultipartBody.Part) : Call<ResponseRegisterMerchant>
    
        @DELETE(ConstantGuestMerchants.BASE_URL_GUEST_MERCHANT)
        fun deleteRequestMerchant(@Header("Authorization") authorization : String) : 
        Call<ResponseDeleteReqMerchant>
    

视图模型

ProfileViewModel.kt

    class ProfileViewModel : ViewModel()
    
        private val _profile = MutableLiveData<Profile>()
        val profile : LiveData<Profile>
                get() = _profile
    
        private val _status = MutableLiveData<ApiStatus>()
        val status : LiveData<ApiStatus>
            get() = _status
    
        private suspend fun getProfileUser(token : String)
            try 
                _status.postValue(ApiStatus.LOADING)
                val apiService = ApiService().serviceProfileUser
                _profile.postValue(apiService.getProfile(token).data)
                _status.postValue(ApiStatus.SUCCESS)
            catch (e : Exception)
                Log.d("REQ_PROF_USR_FAIL", e.localizedMessage!!)
                _status.postValue(ApiStatus.FAILED)
            
        
    
        fun getDataProfileUser(token : String)
            viewModelScope.launch 
                getProfileUser(token)
            
        
    
    

执行操作以从 ViewModel 调用函数的片段

CustomerProfileFragment.kt

    class CustomerProfileFragment : Fragment() 
    
        private lateinit var adapter: AdapterUtil<ProfileMenuItem>
        private lateinit var binding: FragmentCustomerProfileBinding
        private lateinit var cacheUtil: CacheUtil
        private var auth : Login? = null
        private val viewModel : ProfileViewModel by lazy 
            ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(ProfileViewModel::class.java)
        
    
        private val viewModelRegisterMerchant: RegisterMerchantViewModel by lazy 
            ViewModelProvider(
                this,
                ViewModelProvider.NewInstanceFactory()
            ).get(RegisterMerchantViewModel::class.java)
        
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? 
            //binding init
            binding = FragmentCustomerProfileBinding.inflate(inflater, container, false)
            // Setup Cache
            cacheUtil = CacheUtil()
            cacheUtil.start(context as Activity, ConstantAuth.PREFERENCES)
            // Setup Title
            (activity as AppCompatActivity?)!!.setSupportActionBar(binding.toolbar)
            binding.toolbar.title = getString(R.string.akun_saya)
    
            //After Login
            if (getAuth(cacheUtil).token!!.isNotEmpty()) 
                auth = getAuth(cacheUtil)
                Log.d("TOKEN NYA TOKEN", "$auth!!.token TOKEN NYA TOKEN PROFILE")
                binding.llLogin.visibility=View.GONE
                setupProfileMenu()
                binding.tvRegistrasi.setOnClickListener 
                    startActivity(Intent(context, RegisterUserActivity::class.java))
                
                //login
                binding.tvLogin.setOnClickListener 
                    startActivity(Intent(context, LoginActivity::class.java))
                
    
    
                //Logout
                binding.tvLogout.setOnClickListener 
                    this.cacheUtil.clear()
                    startActivity(Intent(requireContext(), MainActivity::class.java))
                    requireActivity().finish()
                
                binding.imFotoProfil.setOnClickListener 
                    ImagePicker.create(this)
                        .single()
                        .start()
                
    
                //edit photo profile
            else
                binding.tvNamaAkun.text = ""
                binding.tvEmailAkun.text = ""
                Glide.with(requireContext()).load(R.drawable.ic_baseline_account_circle_24).into(binding.imFotoProfil)
            
    
            return binding.root
        
    
        private fun setupProfileMenu()
            //Setup Profil Menu
            binding.rvIconmenu.layoutManager = LinearLayoutManager(context)
            adapter =
                AdapterUtil(R.layout.item_list_menu_akun,
                    listOf(
                        ProfileMenuItem(
                            "Saldo Saya",
                            R.drawable.ic_monetization
                        ),
                        ProfileMenuItem(
                            "Pusat Bantuan",
                            R.drawable.ic_help_outline
                        ),
                        ProfileMenuItem(
                            "Chat dengan Leh-Oleh",
                            R.drawable.ic_chat
                        ),
                        ProfileMenuItem(
                            "Beri Kami Nilai",
                            R.drawable.ic_star_border
                        ),
                        ProfileMenuItem(
                            "Toko Saya",
                            R.drawable.ic_store
                        )
                    ),  position, itemView, item ->
                        itemView.tv_menu!!.text = item.label
                        itemView.im_akun_icon!!.setImageResource(item.icon)
                        itemView.im_chevron_right.setImageResource(R.drawable.ic_chevron_right)
                    ,  position, item ->
                        when (position) 
                            0 -> startActivity(
                                Intent(
                                    context,
                                    CustomerSaldoSayaActivity::class.java
                                )
                            )
                            1 -> startActivity(
                                Intent(
                                    context,
                                    BantuanActivity::class.java
                                )
                            )
                            4 -> 
                                viewModelRegisterMerchant.getDataStatusMerchant(auth!!.token!!)
                                viewModelRegisterMerchant.statusMerchant.observe(
                                    viewLifecycleOwner,
                                    Observer 
                                        if (it.isVisible!!.isNotEmpty()) 
                                            if (it.isVisible == "1") 
                                                //findNavController().navigate(R.id.action_navigation_register_toko_to_navigation_toko_saya)
                                                startActivity(Intent(requireContext(), MerchantTokoSayaActivity::class.java))
                                            
                                         else 
                                            Log.d("DATA_STATUS", "BELUM LOGIN")
                                        
    
                                    )
                            
                        
    //                    2 -> startActivity(Intent(context, ProductListActivity::class.java))
    //                    3 -> startActivity(Intent(context, ProductListActivity::class.java))
                    )
            binding.rvIconmenu.adapter = adapter
        
    
      
    
        override fun onResume() 
            viewModel.getDataProfileUser(auth!!.token!!)
            super.onResume()
        
    

注意,当viewModelRegisterMerchant被调用并观察时,intent被执行了多次,当我看到Logcat时,endpoint也被执行了多次。

我认为观察者不断更新数据,但我不知道这怎么可能

另一个类使其更清晰

intent启动时多次调用的类

MerchantTokoSaya.kt

    class MerchantTokoSayaActivity : AppCompatActivity() 
        private lateinit var binding : ActivityMerchantTokoSayaBinding
        private lateinit var auth: Login
        private val viewModel : ProfileTokoViewModel by lazy 
            ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(ProfileTokoViewModel::class.java)
        
        private lateinit var cacheUtil: CacheUtil
        private lateinit var adapter: AdapterUtil<ProfileMenuItem>
        override fun onCreate(savedInstanceState: Bundle?) 
            super.onCreate(savedInstanceState)
            binding = ActivityMerchantTokoSayaBinding.inflate(layoutInflater)
            setContentView(binding.root)
            cacheUtil = CacheUtil()
            cacheUtil.start(this, ConstantAuth.PREFERENCES)
            if (getAuth(cacheUtil).token!!.isNotEmpty()) 
                auth = getAuth(cacheUtil)
                Log.d("TOKEN NYA TOKEN", "$auth.token TOKEN NYA TOKEN")
                getDataProfileToko()
    
                binding.tvUbahAkun.setOnClickListener 
                    val intent = Intent(this, RegisterMerchantFragment::class.java)
                    intent.putExtra(ConstantProfileMerchant.DATA_EDIT_PROFILE_TOKO, ConstantProfileMerchant.ACTION_EDIT_PROFILE_TOKO)
                    startActivity(intent)
                    finish()
                
                title = "Toko Saya"
    
                //Setup Profil Menu
                binding.rvIconmenu.layoutManager = LinearLayoutManager(this)
                adapter =
                    AdapterUtil(R.layout.item_list_menu_akun,
                        listOf(
                            ProfileMenuItem(
                                "Kelola Barang",
                                R.drawable.ic_card_giftcard
                            ),
                            ProfileMenuItem(
                                "Kelola Pesanan",
                                R.drawable.ic_assignment
                            ),
                            ProfileMenuItem(
                                "Pesan Masuk",
                                R.drawable.ic_chat
                            ),
                            ProfileMenuItem(
                                "Keuangan",
                                R.drawable.ic_monetization
                            )
                        ),  position, itemView, item ->
                            itemView.tv_menu!!.text = item.label
                            itemView.im_akun_icon!!.setImageResource(item.icon)
                            itemView.im_chevron_right.setImageResource(R.drawable.ic_chevron_right)
                        ,  position, item ->
                            when (position) 
                                0 -> startActivity(
                                    Intent(
                                        this,
                                        MerchantKelolaBarangActivity::class.java
                                    )
                                )
                                1 -> startActivity(
                                    Intent(
                                        this,
                                        KelolaPesananActivity::class.java
                                    )
                                )
    //                    2 -> startActivity(Intent(context, ProductListActivity::class.java))
    //                    3 -> startActivity(Intent(context, ProductListActivity::class.java))
                            
                        )
                binding.rvIconmenu.adapter = adapter
    
                //init profil picture
                binding.imFotoProfil.setImageResource(R.drawable.ic_home_black_24dp)
            else 
                startActivity(Intent(this, LoginActivity::class.java))
            
        
    
        private fun getDataProfileToko()
            viewModel.getDataProfileToko(auth.token!!)
            viewModel.toko.observe(this, Observer 
                binding.tvNamaAkun.text = it.marketName
                Glide.with(this).load(it.authorUri).circleCrop().into(binding.imFotoProfil)
                Glide.with(this).load(it.marketUri).into(binding.imageViewHeader)
            )
        
    
    
    

用于保存共享首选项的 util 类

CacheUtil.kt

    class CacheUtil 
        private var sharePref: SharedPreferences? = null
    
        fun start(activity: Activity, PREFS: String) 
            sharePref = activity.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
        
    
        fun destroy()  this.sharePref = null 
    
        fun <T> set(PREFS: String, value: T) 
            this.sharePref?.let 
                with(it.edit()) 
                    putString(PREFS, Gson().toJson(value))
                    Log.d("CACHE UTIL", PREFS)
                    apply()
                
            
        
    
        fun clear() 
            this.sharePref?.edit()?.clear()?.apply()
        
    
        fun get(PREFS: String): String? 
            if (sharePref != null) return sharePref!!.getString(PREFS, null)
            return null
        
    

RegisterMerchantViewModel.kt

    class RegisterMerchantViewModel : ViewModel()
    
        private val _statusMerchant = MutableLiveData<DataGetStatusMerchant>()
        val statusMerchant : LiveData<DataGetStatusMerchant>
            get() = _statusMerchant
    
        private val _status = MutableLiveData<ApiStatus>()
        val status : LiveData<ApiStatus>
            get() = _status
    
    
        private val apiService = ApiService().serviceGuestMerchants
    
        fun getDataStatusMerchant(token: String) 
            viewModelScope.launch 
                getStatusMerchant(token)
            
        
    
        private suspend fun getStatusMerchant(token: String) 
            try 
                _status.postValue(ApiStatus.LOADING)
                _statusMerchant.postValue(apiService.getStatusMerchant(token).data)
                _status.postValue(ApiStatus.SUCCESS)
             catch (e: Exception) 
                Log.d("ERROR_REQ_STATUS", e.localizedMessage!!)
                _status.postValue(ApiStatus.FAILED)
            
    
        
    
    
    

【问题讨论】:

【参考方案1】:

我解决了这个问题,所以我将 oncreateView 中的所有代码移动到 onviewcreated 并将 viewModelRegisterMerchant.getDataStatusMerchant(auth!!.token!!) 移动到单击的项目之外。

【讨论】:

以上是关于使用 ViewModel 和 LiveData 多次改造执行 API的主要内容,如果未能解决你的问题,请参考以下文章

JetpackLiveData 架构组件 ( LiveData 简介 | LiveData 使用方法 | ViewModel + LiveData 示例 )

JetpackLiveData 架构组件 ( LiveData 简介 | LiveData 使用方法 | ViewModel + LiveData 示例 )

使用 ViewModel 和 LiveData 多次改造执行 API

MVVM 架构,ViewModel和LiveData

MVVM 架构,ViewModel和LiveData

Android Jetpack 学习之旅--> ViewModel & LiveData 的使用