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)
}
}
}
I solved this problem, so I move all of the code inside oncreateView to onviewcreated and move viewModelRegisterMerchant.getDataStatusMerchant(auth!!.token!!) outside item clicked.