#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!!.токен !!) щелкнул внешний элемент.