Модифицируйте выполнение API несколько раз, используя ViewModel и LiveData

#android #api #mvvm #retrofit #android-livedata

#Android #API #mvvm #модернизация #android-livedata

Вопрос:

Итак, у меня возникает проблема, когда я выполняю действие click для запроса 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>
    }
  

Viewmodel

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

The fragment that performs an action to call the function from 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()
        }
    }
  

Notice that when viewModelRegisterMerchant is called and observe, the intent is executing multiple times, when I see Logcat, the endpoint is also executed multiple times.

what I thought is the observer keep updating the data, but I don’t know how it is possible

another class to make it more clear

the class that call multiple times when the intent is started

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

the util class to save shared preferences

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

Зарегистрируйте merchantviewmodel.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!!.токен !!) щелкнул внешний элемент.