I've this code in my fragment and it is really problematic. Cannot find any way to refactor or clean it. It is giving me error regarding mutliple observers and crashing when i click btnSwitch to switch between personal / business profile. I need help in majorly sorting this code and refactoring it and resolving the observer issues. due to stackoverflow's word limit, i've removed a few private functions from the code in this prompt, thos functions have nothing to do with observer
import android.annotation.SuppressLint
import android.app.Activity
import android.content.res.ColorStateList
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.PurchasesUpdatedListener
import com.bumptech.glide.Glide
import com.github.dhaval2404.imagepicker.ImagePicker
import com.truevast.app.R
import com.truevast.app.data.network.Resource
import com.truevast.app.data.response.Inventory
import com.truevast.app.data.response.LinksItem
import com.truevast.app.data.response.User
import com.truevast.app.databinding.FragmentProfileBinding
import com.truevast.app.ui.home.HomeActivity
import com.truevast.app.ui.home.ui.connects.ConnectionsActivity
import com.truevast.app.ui.inventory.InventoryActivity
import com.truevast.app.ui.socialLink.SocialLinkActivity
import com.truevast.app.util.Constant
import com.truevast.app.util.EventObserver
import com.truevast.app.util.GeneralListener
import com.truevast.app.util.Loading.cancelLoading
import com.truevast.app.util.Loading.showLoading
import com.truevast.app.util.Purchasing
import com.truevast.app.util.Purchasing.Companion.displayPurchasedBottomSheet
import com.truevast.app.util.Purchasing.Companion.verifySubPurchase
import com.truevast.app.util.bool
import com.truevast.app.util.displayPaymentSuccessPopUp
import com.truevast.app.util.displayPopUp
import com.truevast.app.util.doAfterTextChanged
import com.truevast.app.util.handleApiError
import com.truevast.app.util.openActivity
import com.truevast.app.util.openWebsite
import com.truevast.app.util.toEditable
import com.truevast.app.util.toast
import dagger.hilt.android.AndroidEntryPoint
import io.paperdb.Paper
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Collections
import java.util.Locale
@AndroidEntryPoint
class ProfileFragment : Fragment(R.layout.fragment_profile) {
private lateinit var binding: FragmentProfileBinding
private val updateUserViewModel: ProfileViewModel by viewModels()
private lateinit var billingClient: BillingClient
private val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchases ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
purchases.forEach {
requireActivity().verifySubPurchase(it, billingClient)
}
}
}
private val viewModel: ProfileViewModel by viewModels()
var user = Paper.book().read<User>(Constant.k_user) ?: User()
private var profileAdapter = ProfileAdapter()
private lateinit var mListLinks: MutableList<LinksItem>
private val itemTouchHelper by lazy {
val simpleItemTouchCallback =
object : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP or
ItemTouchHelper.DOWN or
ItemTouchHelper.START or
ItemTouchHelper.END, 0
) {
var from = -1
var to = -1
override fun isLongPressDragEnabled() = true
override fun isItemViewSwipeEnabled() = false
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
val dragFlags =
ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
val swipeFlags =
if (isItemViewSwipeEnabled) ItemTouchHelper.START or ItemTouchHelper.END else 0
return makeMovementFlags(dragFlags, swipeFlags)
}
override fun onSelectedChanged(
viewHolder: RecyclerView.ViewHolder?,
actionState: Int
) {
super.onSelectedChanged(viewHolder, actionState)
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
viewHolder?.itemView?.alpha = 0.5f
}
}
override fun clearView(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
) {
super.clearView(recyclerView, viewHolder)
viewHolder.itemView.alpha = 1.0f
viewHolder.itemView.requestLayout()
val adapter = recyclerView.adapter as ProfileAdapter
Log.e("ProfileFragment", "getItems: ${adapter.getItems()}")
val requestBody = MultipartBody.Builder().setType(MultipartBody.FORM).apply {
addFormDataPart("user_id", user.id.toString())
addFormDataPart(
"links[]",
adapter.getItems()
)
}.build()
viewModel.updateLinksArrangement(requestBody)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
from = viewHolder.absoluteAdapterPosition
to = target.absoluteAdapterPosition
if (from < to) {
for (i in from until to) {
Collections.swap(mListLinks, i, i + 1)
}
} else {
for (i in from downTo to + 1) {
Collections.swap(mListLinks, i, i - 1)
}
}
(recyclerView.adapter as ProfileAdapter).notifyItemMoved(from, to)
return false
}
override fun onSwiped(
viewHolder: RecyclerView.ViewHolder,
direction: Int
) {
}
}
ItemTouchHelper(simpleItemTouchCallback)
}
@SuppressLint("NotifyDataSetChanged")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentProfileBinding.bind(view)
checkExpiryDate()
binding.btnAddNew.setOnClickListener {
requireActivity().openActivity(SocialLinkActivity::class.java, false)
}
binding.btnSwitchProfile.setOnClickListener {
requireActivity().showLoading()
if (binding.btnSwitchProfile.text == getString(R.string.switch_to_business)) {
viewModel.getUserData(Constant.k_roleBusiness)
} else {
viewModel.getUserData(Constant.k_rolePersonal)
}
}
binding.ivUser.setOnClickListener {
openImagePicker()
}
binding.civEdit.setOnClickListener {
openImagePicker()
}
binding.ivCompany.setOnClickListener {
com.github.drjacky.imagepicker.ImagePicker.with(requireActivity())
.crop()
.cropOval()
.maxResultSize(512, 512, true)
.provider(com.github.drjacky.imagepicker.constant.ImageProvider.BOTH)
.createIntentFromDialog {
logoImageResult.launch(it)
}
}
binding.civBannerEdit.setOnClickListener {
com.github.drjacky.imagepicker.ImagePicker.with(requireActivity())
.crop(16f, 9f)
.maxResultSize(512, 512, true)
.provider(com.github.drjacky.imagepicker.constant.ImageProvider.BOTH)
.createIntentFromDialog {
coverImageResult.launch(it)
}
}
binding.ivBanner.setOnClickListener {
com.github.drjacky.imagepicker.ImagePicker.with(requireActivity())
.crop(16f, 9f)
.maxResultSize(512, 512, true)
.provider(com.github.drjacky.imagepicker.constant.ImageProvider.BOTH)
.createIntentFromDialog {
coverImageResult.launch(it)
}
}
binding.sbPersonal.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
binding.sbLead.isChecked = false
if (user.directMode == 0) {
if (profileAdapter.mList.isNotEmpty()) {
val item = profileAdapter.mList[0]
profileAdapter.directMode = true
item.isDirect = true
viewModel.updateDirectMode(
userId = user.id,
status = 1,
linkId = item.linkId.toString(),
linkValue = item.value
)
}
}
} else {
profileAdapter.directMode = false
viewModel.updateDirectMode(
userId = user.id,
status = 0,
linkId = null,
linkValue = null
)
}
profileAdapter.notifyDataSetChanged()
}
binding.tilAbout.setEndIconOnClickListener {
requireActivity().displayPopUp(
getString(R.string.information),
"You can add your bio here"
)
}
binding.tilAbout.editText?.doAfterTextChanged(delay = 2000) {
if (binding.tilAbout.hasFocus()) {
updateBio(it)
}
}
viewModel.userResponse.observe(viewLifecycleOwner) {
when (it) {
is Resource.Failure -> {
requireActivity().handleApiError(it)
}
is Resource.Success -> {
it.value.response?.let { it1 -> Paper.book().write(Constant.k_user, it1) }
requireActivity().cancelLoading()
requireActivity().openActivity(HomeActivity::class.java)
}
}
}
}
override fun onResume() {
super.onResume()
mListLinks = user.mListLinks.toMutableList()
val profileLink = "${user.baseUrl}"
binding.ivEdit.setOnClickListener {
// requireActivity().share("Hey, \nYou can find my profile link below: \n\n$profileLink")
findNavController().navigate(R.id.nav_edit_profile)
}
binding.ivPreview.setOnClickListener {
requireActivity().openWebsite(profileLink)
}
binding.btnInventory.setOnClickListener {
if (hasActiveSubscription()) {
findNavController().navigate(R.id.inventoryActivity)
} else {
// launchBillingFlow()
requireActivity().displayPurchasedBottomSheet(billingClient) {
if (it!!) {
val user = Paper.book().read<User>(Constant.k_user)!!
if (Constant.pro) {
user.subscription = Constant.k_proMonthly_user
} else {
user.subscription = Constant.k_basicMonthly_user
}
user.versionPurchaseDate = Purchasing.getPurchaseDate()
user.versionExpiryDate = Purchasing.getExpiryDate()
val requestBody =
MultipartBody.Builder().setType(MultipartBody.FORM).apply {
addFormDataPart("id", user?.id.toString())
addFormDataPart("subscription", user?.subscription.toString())
addFormDataPart(
"versionPurchaseDate",
user?.versionPurchaseDate.toString()
)
addFormDataPart(
"versionExpiryDate",
user?.versionExpiryDate.toString()
)
}.build()
requireActivity().showLoading()
updateUserViewModel.updateUser(requestBody)
}
}
}
}
if (!::billingClient.isInitialized) {
launchBillingFlow()
}
if (user.role == Constant.k_roleBusiness) {
setBusinessRoleButton()
binding.ivCompany.visibility = View.VISIBLE
} else {
setSocialRoleButton()
binding.ivCompany.visibility = View.GONE
}
if (user.profileUrl?.isNotEmpty() == true) {
// loadProfileImage(user.profileUrl, binding.ivUser)
Glide.with(this)
.load(user.profileUrl)
.into(binding.ivUser)
// loadImage(item?.image, binding.ivSocial)
}
if (user.logoUrl?.isNotEmpty() == true) {
Glide.with(this)
.load(user.logoUrl)
.into(binding.ivCompany)
}
if (user.coverUrl?.isNotEmpty() == true) {
Glide.with(this)
.load(user.coverUrl)
.into(binding.ivBanner)
} else {
Glide.with(this)
.load(R.drawable.ic_cover_placeholder)
.into(binding.ivBanner)
}
binding.sbLead.isChecked = user.leadMode.bool()
binding.sbLead.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
binding.sbPersonal.isChecked = false
}
updateLeadMode(isChecked)
}
if (user.name?.isNotEmpty() == true) {
binding.tilFullName.editText?.text = user.name?.toEditable()
binding.tvFullName.text = user.name
} else {
}
if (user.designation?.isNotEmpty() == true) {
binding.tvDesignation.text = user.designation
} else {
binding.tvDesignation.visibility = View.GONE
}
if (user.company?.isNotEmpty() == true) {
binding.tvCompany.text = user.company
} else {
binding.tvCompany.visibility = View.GONE
}
if (user.bio?.isNotEmpty() == true) {
binding.tilAbout.editText?.text = user.bio?.toEditable()
} else {
}
if (mListLinks.isNotEmpty()) {
user.directMode.bool().apply {
binding.sbPersonal.isChecked = this
profileAdapter.directMode = this
if (this) {
mListLinks.forEach {
if (it.linkId == user.directLinkId) {
it.isDirect = true
}
}
}
}
profileAdapter.apply {
mList = mListLinks
itemClickListener = { item, isChecked, directMode ->
if (!directMode) {
updateSocialLink(item, isChecked)
} else {
viewModel.updateDirectMode(
userId = user.id,
status = 1,
linkId = item?.linkId.toString(),
linkValue = item?.value
)
}
}
}
binding.rvSocialLinks.apply {
adapter = profileAdapter
itemTouchHelper.attachToRecyclerView(this)
}
}
Constant.k_purchasing.observe(requireActivity())
{
Log.e("Profile", "onResume: $it")
}
updateUserViewModel.updateUser.observe(viewLifecycleOwner, EventObserver {
Log.e("EditContactActivity", "onCreate: $it")
requireActivity().cancelLoading()
when (it) {
is Resource.Failure -> {
requireActivity().handleApiError(it)
}
is Resource.Success -> {
Log.e("Profile", "onResume: ${it.value.response}")
requireActivity().displayPaymentSuccessPopUp(
getString(R.string.success),
resources.getString(R.string.subscribed_successfully),
object : GeneralListener {
override fun buttonClick(clicked: Boolean) {
it.value.response?.let { user ->
Paper.book().write(Constant.k_user, user)
}
requireActivity().openActivity(
InventoryActivity::class.java,
false
)
}
})
}
}
})
}
private fun hasActiveSubscription(): Boolean {
val subscription = Paper.book().read<User>(Constant.k_user)?.subscription
return subscription == Constant.k_basicMonthly_user || subscription == Constant.k_proMonthly_user
}
private fun launchBillingFlow() {
billingClient = BillingClient.newBuilder(requireActivity())
.setListener(purchasesUpdatedListener)
.enablePendingPurchases()
.build()
}
private val profileImageResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val resultCode = it.resultCode
val data = it.data
when (resultCode) {
Activity.RESULT_OK -> {
val fileUri = data?.data!!
binding.ivUser.setImageURI(fileUri)
updateImage("profileUrl", fileUri)
}
ImagePicker.RESULT_ERROR -> {
toast { ImagePicker.getError(data) }
}
else -> {
toast { "Task Cancelled" }
}
}
}
private val coverImageResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val resultCode = it.resultCode
val data = it.data
when (resultCode) {
Activity.RESULT_OK -> {
val fileUri = data?.data!!
// loadImage(fileUri,binding.ivBanner)
Glide.with(this)
.load(fileUri)
.into(binding.ivBanner)
// binding.ivBanner.setImageURI(fileUri)
updateImage("coverUrl", fileUri)
}
ImagePicker.RESULT_ERROR -> {
toast { ImagePicker.getError(data) }
}
else -> {
toast { "Task Cancelled" }
}
}
}
private val logoImageResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val resultCode = it.resultCode
val data = it.data
when (resultCode) {
Activity.RESULT_OK -> {
val fileUri = data?.data!!
binding.ivCompany.setImageURI(fileUri)
updateImage("logoUrl", fileUri)
}
ImagePicker.RESULT_ERROR -> {
toast { ImagePicker.getError(data) }
}
else -> {
toast { "Task Cancelled" }
}
}
}
private fun setBusinessRoleButton() {
binding.btnSwitchProfile.text = getString(R.string.switch_to_social)
// binding.btnSwitchProfile.iconGravity = MaterialButton.ICON_GRAVITY_TEXT_END
binding.btnSwitchProfile.iconTint =
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.white))
binding.btnSwitchProfile.backgroundTintList =
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.blue))
binding.btnSwitchProfile.strokeColor =
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.blue))
binding.btnSwitchProfile.setTextColor(
ContextCompat.getColor(
requireContext(),
R.color.white
)
)
}
private fun setSocialRoleButton() {
binding.btnSwitchProfile.text = getString(R.string.switch_to_business)
// binding.btnSwitchProfile.iconGravity = MaterialButton.ICON_GRAVITY_TEXT_START
binding.btnSwitchProfile.iconTint =
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.black))
binding.btnSwitchProfile.backgroundTintList =
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.white))
binding.btnSwitchProfile.strokeColor =
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.black))
binding.btnSwitchProfile.setTextColor(
ContextCompat.getColor(
requireContext(),
R.color.black
)
)
}
private fun updateImage(key: String, value: Uri) {
val requestBody = MultipartBody.Builder().setType(MultipartBody.FORM).apply {
addFormDataPart("id", user.id.toString())
val file = File(value.path!!)
addFormDataPart(
key, "${file.name}.jpg", file.asRequestBody(
"multipart/form-data".toMediaTypeOrNull()
)
)
}.build()
viewModel.updateUser(requestBody)
// viewModel.updateUser.observe(viewLifecycleOwner) {
//
// when (it) {
// is Resource.Failure -> {
// requireActivity().handleApiError(it)
// }
//
// is Resource.Success -> {
// it.value.response?.let { updatedUser ->
// when (key) {
// "profileUrl" -> user.profileUrl = updatedUser.profileUrl
// "coverUrl" -> user.coverUrl = updatedUser.coverUrl
// "logoUrl" -> user.logoUrl = updatedUser.logoUrl
// }
//
// when (key) {
// "profileUrl" -> Glide.with(this).load(user.profileUrl)
// .into(binding.ivUser)
//
// "coverUrl" -> Glide.with(this).load(user.coverUrl)
// .into(binding.ivBanner)
//
// "logoUrl" -> Glide.with(this).load(user.logoUrl)
// .into(binding.ivCompany)
// }
// Paper.book().write(Constant.k_user, user)
// }
// }
// }
// }
}
private fun updateBio(bio: String) {
val requestBody = MultipartBody.Builder().setType(MultipartBody.FORM).apply {
addFormDataPart("id", user.id.toString())
addFormDataPart("bio", bio)
}.build()
viewModel.updateUser(requestBody)
// viewModel.updateUser.observe(viewLifecycleOwner) {
// Log.e("ProfileFragment", "updateBio: $it")
// when (it) {
// is Resource.Failure -> {
// requireActivity().handleApiError(it)
// }
//
// is Resource.Success -> {
// it.value.response?.let { it1 -> Paper.book().write(Constant.k_user, it1) }
// }
// }
// }
}
private fun updateSocialLink(item: LinksItem?, isChecked: Boolean) {
requireActivity().showLoading()
viewModel.updateSocialLink(item?.linkId, user.id, if (isChecked) 1 else 0)
viewModel.updateSocialLink.observe(viewLifecycleOwner, EventObserver {
requireActivity().cancelLoading()
Log.e("ProfileFragment", "updateSocialLink: $it")
when (it) {
is Resource.Failure -> {
requireActivity().handleApiError(it)
}
is Resource.Success -> {
if (isChecked) {
mListLinks?.get(mListLinks!!.indexOf(item))?.status = 1
} else {
mListLinks?.get(mListLinks!!.indexOf(item))?.status = 0
}
Paper.book().write(Constant.k_user, user)
}
}
})
}
override fun onPause() {
super.onPause()
viewModel.userResponse.removeObservers(viewLifecycleOwner)
updateUserViewModel.updateUser.removeObservers(viewLifecycleOwner)
}
}