Search code examples
androidapimvvmretrofitandroid-livedata

Retrofit execute API multiple times using ViewModel and LiveData


So I have a problem when I perform action click to request API GET, the endpoint is hit multiple times I'm using combinations MVVM and LiveData to get the value from API. The code below

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

The Interface

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

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

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

Solution

  • I solved this problem, so I move all of the code inside oncreateView to onviewcreated and move viewModelRegisterMerchant.getDataStatusMerchant(auth!!.token!!) outside item clicked.