Espresso AppNotIdleException when using custom View - android

I have a problem when I test my login screen which has custom EditText in it. When I try run the test, the test always loading and nothing happen until I get AppNotIdleException. I'd disabled my system animation when run the test, and finally try remove my custom EditText view and the test running.
Here is my code:
fragment_login.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:keep="#layout/fragment_login">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.airbnb.lottie.LottieAnimationView
android:id="#+id/lottie_auth"
android:layout_width="0dp"
android:layout_height="250dp"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:alpha="0"
app:layout_constraintTop_toBottomOf="#+id/tv_greeting"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_rawRes="#raw/watermelon_lottie" />
<TextView
android:id="#+id/tv_greeting"
style="#style/TextAppearance.StoryApp.Headline"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:alpha="0"
android:layout_marginTop="32dp"
android:layout_marginEnd="20dp"
android:text="#string/greeting2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/tv_login"
style="#style/TextAppearance.StoryApp.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:alpha="0"
android:layout_marginTop="16dp"
android:text="#string/login"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/lottie_auth" />
<com.ekotyoo.storyapp.ui.EmailEditText
android:id="#+id/et_email"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginStart="20dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="20dp"
android:alpha="0"
android:inputType="textEmailAddress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tv_login" />
<com.ekotyoo.storyapp.ui.PasswordEditText
android:id="#+id/et_password"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginStart="20dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="20dp"
android:alpha="0"
android:inputType="textPassword"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/et_email" />
<Button
android:id="#+id/btn_login"
style="#style/TextAppearance.StoryApp.Button"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginStart="20dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="20dp"
android:alpha="0"
android:background="#drawable/bg_button"
android:text="#string/login"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/et_password" />
<TextView
android:id="#+id/tv_or_signup"
style="#style/TextAppearance.StoryApp.Body1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:alpha="0"
android:text="#string/or_signup"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/btn_login" />
<ProgressBar
android:id="#+id/loading_progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
PasswordEditText.kt:
package com.ekotyoo.storyapp.ui
import android.content.Context
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.text.method.PasswordTransformationMethod
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import androidx.appcompat.widget.AppCompatEditText
import androidx.core.content.ContextCompat
import androidx.core.widget.doOnTextChanged
import com.ekotyoo.storyapp.R
class PasswordEditText : AppCompatEditText, View.OnTouchListener {
private val roundedBackground =
ContextCompat.getDrawable(context, R.drawable.bg_custom_edittext)
private lateinit var hidePasswordIcon: Drawable
private lateinit var showPasswordIcon: Drawable
private var isHidden = true
private val passwordTransformationMethod = PasswordTransformationMethod.getInstance()
constructor(context: Context) : super(context) {
init()
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init()
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init()
}
private fun init() {
setOnTouchListener(this)
doOnTextChanged { text, _, _, _ ->
text?.let {
if (it.length < 6 && it.isNotBlank()) setError(
context.applicationContext.getString(
R.string.password_less_than_6
), null
)
}
}
hidePasswordIcon =
ContextCompat.getDrawable(context, R.drawable.ic_eye_disabled) as Drawable
showPasswordIcon = ContextCompat.getDrawable(context, R.drawable.ic_eye) as Drawable
hint = "Password"
textSize = 14F
textAlignment = View.TEXT_ALIGNMENT_VIEW_START
background = roundedBackground
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
transformationMethod = if (isHidden) {
showPasswordVisible()
passwordTransformationMethod
} else {
showPasswordHidden()
null
}
}
private fun setDrawables(
left: Drawable? = null,
top: Drawable? = null,
right: Drawable? = null,
bottom: Drawable? = null,
) {
setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom)
}
private fun showPasswordVisible() {
setDrawables(null, null, null, null)
setDrawables(null, null, showPasswordIcon, null)
}
private fun showPasswordHidden() {
setDrawables(null, null, null, null)
setDrawables(null, null, hidePasswordIcon, null)
}
override fun onTouch(view: View?, event: MotionEvent): Boolean {
if (compoundDrawables[2] != null) {
val toggleButtonStart: Float
val toggleButtonEnd: Float
var isToggleButtonClicked = false
if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
toggleButtonEnd = (hidePasswordIcon.intrinsicWidth + paddingStart).toFloat()
if (event.x < toggleButtonEnd) isToggleButtonClicked = true
} else {
toggleButtonStart = (width - paddingEnd - hidePasswordIcon.intrinsicWidth).toFloat()
if (event.x > toggleButtonStart) isToggleButtonClicked = true
}
if (isToggleButtonClicked) {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
hidePasswordIcon = ContextCompat.getDrawable(
context,
R.drawable.ic_eye_disabled
) as Drawable
showPasswordIcon =
ContextCompat.getDrawable(context, R.drawable.ic_eye) as Drawable
isHidden = !isHidden
return true
}
}
}
}
return false
}
}
LoginFragment.kt
package com.ekotyoo.storyapp.ui.login
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.ekotyoo.storyapp.R
import com.ekotyoo.storyapp.databinding.FragmentLoginBinding
import com.ekotyoo.storyapp.utils.Utils
import com.ekotyoo.storyapp.utils.ViewModelFactory
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
class LoginFragment : Fragment() {
private var _binding: FragmentLoginBinding? = null
private val binding get() = _binding!!
private val viewModel: LoginViewModel by viewModels {
ViewModelFactory.getInstance(requireContext())
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentLoginBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupView()
playAnimation()
observeViewModel()
}
private fun setupView() {
binding.btnLogin.setOnClickListener {
val email = binding.etEmail.text.toString().trim()
val password = binding.etPassword.text.toString().trim()
viewModel.login(email, password)
}
binding.tvOrSignup.setOnClickListener {
findNavController().navigate(R.id.action_loginFragment_to_signupFragment)
}
validateForm()
}
private fun validateForm() {
val email = MutableStateFlow("")
val password = MutableStateFlow("")
with(binding) {
etEmail.doAfterTextChanged { text -> email.value = text.toString().trim() }
etPassword.doAfterTextChanged { text -> password.value = text.toString().trim() }
}
lifecycleScope.launch {
combine(email, password) { e, p ->
Utils.validateEmail(e) && Utils.validatePassword(p)
}.collect { isValid ->
binding.btnLogin.isEnabled = isValid
}
}
}
private fun playAnimation() {
val greeting = ObjectAnimator.ofFloat(binding.tvGreeting, View.ALPHA, 1F).setDuration(500L)
val lottie = ObjectAnimator.ofFloat(binding.lottieAuth, View.ALPHA, 1f).setDuration(500L)
val loginAlpha = ObjectAnimator.ofFloat(binding.tvLogin, View.ALPHA, 1F).setDuration(500L)
val loginTranslateX =
ObjectAnimator.ofFloat(binding.tvLogin, View.TRANSLATION_X, -100F, 0F).setDuration(500L)
val etEmailAlpha = ObjectAnimator.ofFloat(binding.etEmail, View.ALPHA, 1F).setDuration(500L)
val etEmailTranslateX =
ObjectAnimator.ofFloat(binding.etEmail, View.TRANSLATION_X, -100F, 0F).setDuration(500L)
val etPassAlpha =
ObjectAnimator.ofFloat(binding.etPassword, View.ALPHA, 1F).setDuration(500L)
val etPassTranslateX =
ObjectAnimator.ofFloat(binding.etPassword, View.TRANSLATION_X, -100F, 0F)
.setDuration(500L)
val btnLoginAlpha =
ObjectAnimator.ofFloat(binding.btnLogin, View.ALPHA, 1F).setDuration(500L)
val btnLoginTranslateX =
ObjectAnimator.ofFloat(binding.btnLogin, View.TRANSLATION_X, -100F, 0F)
.setDuration(500L)
val orSignup = ObjectAnimator.ofFloat(binding.tvOrSignup, View.ALPHA, 1F).setDuration(500L)
val login = AnimatorSet().apply { playTogether(loginAlpha, loginTranslateX) }
val email = AnimatorSet().apply { playTogether(etEmailAlpha, etEmailTranslateX) }
val password = AnimatorSet().apply { playTogether(etPassAlpha, etPassTranslateX) }
val btnLogin = AnimatorSet().apply { playTogether(btnLoginAlpha, btnLoginTranslateX) }
AnimatorSet().apply {
playSequentially(greeting, lottie, login, email, password, btnLogin, orSignup)
start()
}
}
private fun observeViewModel() {
viewModel.userModel.observe(viewLifecycleOwner) { userModel ->
if (userModel?.isLoggedIn == true) {
findNavController().navigate(R.id.action_loginFragment_to_homeFragment)
}
}
viewModel.errorMessage.observe(viewLifecycleOwner) { message ->
Utils.showToast(requireContext(), message)
}
viewModel.isLoading.observe(viewLifecycleOwner) { state ->
showLoading(state)
}
}
private fun showLoading(isLoading: Boolean) {
binding.loadingProgressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
MainActivityTest.kt
package com.ekotyoo.storyapp.ui
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.ekotyoo.storyapp.R
import com.ekotyoo.storyapp.utils.EspressoIdlingResource
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
#RunWith(AndroidJUnit4::class)
#LargeTest
class MainActivityTest {
#get:Rule
val activity = ActivityScenarioRule(MainActivity::class.java)
#Before
fun setUp() {
IdlingRegistry.getInstance().register(EspressoIdlingResource.countingIdlingResource)
}
#After
fun tearDown() {
IdlingRegistry.getInstance().unregister(EspressoIdlingResource.countingIdlingResource)
}
#Test
fun load_story() {
onView(withId(R.id.rv_story)).check(matches(isDisplayed()))
onView(withId(R.id.rv_story)).perform(
RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(
2
)
)
}
#Test
fun go_to_detail_story() {
onView(withId(R.id.rv_story)).check(matches(isDisplayed()))
onView(withId(R.id.rv_story)).perform(
RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(
2,
click()
)
)
}
#Test
fun go_to_map_story() {
onView(withId(R.id.btn_map)).perform(click())
onView(withId(R.id.btn_back)).check(matches(isDisplayed()))
pressBack()
}
#Test
fun go_to_post_story() {
onView(withId(R.id.btn_add_story)).perform(click())
onView(withId(R.id.btn_change_image)).perform(click())
pressBack()
onView(withId(R.id.et_caption)).perform(typeText("Hello World"))
pressBack()
}
#Test
fun open_popup_menu() {
onView(withId(R.id.btn_menu)).perform(click())
}
#Test
fun login() {
onView(withId(R.id.et_email)).perform(typeText("email#mail.com"))
onView(withId(R.id.et_password)).perform(typeText("123456"))
onView(withId(R.id.btn_login)).perform(click())
}
}

Related

Creating a favourite checkbox in a list, Kotlin

I'm hoping to create a checkbox for items that I have in a list.
I was looking at applying this by using a boolean value for my items i.e if attribute 'favourite' = true, then checkbox is highlighted and vice versa if false.
Unfortunately, so far I am unsure of the logic for this and how to connect it to my view (Checkbox).
My view is as follows:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:elevation="24dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/relativeLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<ImageView
android:id="#+id/imageIcon"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:contentDescription="#string/change_hike_image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/hikeName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textAlignment="viewStart"
android:textSize="30sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.048"
app:layout_constraintStart_toEndOf="#+id/imageIcon"
app:layout_constraintTop_toTopOf="parent"
tools:text="A Title" />
<TextView
android:id="#+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textAlignment="viewStart"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.052"
app:layout_constraintStart_toEndOf="#+id/imageIcon"
app:layout_constraintTop_toBottomOf="#id/hikeName"
tools:text="A Description" />
<CheckBox
android:id="#+id/favourite"
style="?android:attr/starStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
Adapter:
package org.wit.hikingtrails.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import org.wit.hikingtrails.databinding.CardHikeBinding
import org.wit.hikingtrails.models.HikeModel
interface HikeListener {
fun onHikeClick(hike: HikeModel)
}
class HikeAdapter constructor(private var hikes: List<HikeModel>,
private val listener: HikeListener) :
RecyclerView.Adapter<HikeAdapter.MainHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainHolder {
val binding = CardHikeBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return MainHolder(binding)
}
override fun onBindViewHolder(holder: MainHolder, position: Int) {
val hike = hikes[holder.adapterPosition]
holder.bind(hike, listener)
}
override fun getItemCount(): Int = hikes.size
class MainHolder(private val binding : CardHikeBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(hike: HikeModel, listener: HikeListener) {
binding.hikeName.text = hike.name
binding.description.text = hike.description
binding.favourite.text = hike.favourite.toString()
if (hike.image != ""){
Picasso.get()
.load(hike.image)
.resize(200, 200)
.into(binding.imageIcon)
}
binding.root.setOnClickListener { listener.onHikeClick(hike) }
}
}
}
View:
package org.wit.hikingtrails.views.hikeList
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import org.wit.hikingtrails.R
import org.wit.hikingtrails.adapters.HikeAdapter
import org.wit.hikingtrails.adapters.HikeListener
//import org.wit.hikingtrails.databinding.ActivityHikeListBinding
import org.wit.hikingtrails.main.MainApp
import org.wit.hikingtrails.models.HikeModel
import android.content.Intent
import android.view.*
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.wit.hikingtrails.databinding.ActivityHikeListBinding
import org.wit.hikingtrails.views.BaseView
import timber.log.Timber
class HikeListView : BaseView(), HikeListener {
lateinit var app: MainApp
lateinit var binding: ActivityHikeListBinding
lateinit var presenter: HikeListPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHikeListBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.toolbar.title = title
setSupportActionBar(binding.toolbar)
presenter = HikeListPresenter(this)
val layoutManager = LinearLayoutManager(this)
binding.recyclerView.layoutManager = layoutManager
updateRecyclerView()
}
// override fun showHikes(hikes: List<HikeModel>) {
// recyclerView.adapter = HikeAdapter(hikes, this)
// recyclerView.adapter?.notifyDataSetChanged()
// }
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onResume() {
//update the view
super.onResume()
updateRecyclerView()
binding.recyclerView.adapter?.notifyDataSetChanged()
Timber.i("recyclerView onResume")
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item?.itemId) {
R.id.item_add -> presenter.doAddHike()
R.id.item_map -> presenter.doShowHikesMap()
R.id.logout -> presenter.doLogout()
}
return super.onOptionsItemSelected(item)
}
override fun onHikeClick(hike: HikeModel) {
presenter.doEditHike(hike)
}
private fun updateRecyclerView(){
GlobalScope.launch(Dispatchers.Main){
binding.recyclerView.adapter =
HikeAdapter(presenter.getHikes(), this#HikeListView)
}
}
}
Add favorite property to your HikeModel like this
data class HikeModel(
... code
// use mutable property bcs you will update it later when checkbox is checked or uncheck.
var favorite: Boolean = false // false by default
)
and then
fun bind(hike: HikeModel, listener: HikeListener) {
...
binding.favourite.isChecked = hike.favorite
binding.favourite.setOnClickListener {
hike.favorite = binding.favourite.isChecked
listener.onHikeClick(hike)
}
...
}

I am making a paint application using kotlin. I wanted to erase my last path using an undo button. How can I do that?

I was trying to make a paint app through which users can paint and draw for fun.
I wanted to erase a drawing partially by using the undo button in my app. The floating action button I have used erases the whole drawing, but I want only that last part of the drawing to get erased. Kindly help me how can I do that ?
XML FILE
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/black"
tools:context=".MainActivity">
<LinearLayout
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:background="#drawable/toolbar_background"
android:gravity="center"
app:layout_constraintRight_toRightOf="parent">
<ImageButton
android:id="#+id/redColor"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="10dp"
android:background="#drawable/red_background"/>
<ImageButton
android:id="#+id/blueColor"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="10dp"
android:background="#drawable/blue_background"/>
<ImageButton
android:id="#+id/blackColor"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="10dp"
android:background="#drawable/black_background"/>
<ImageButton
android:id="#+id/whiteColor"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="10dp"
android:background="#drawable/white_background"/>
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="#id/toolbar"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<include
android:id="#+id/include"
layout="#layout/paint_view" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/resetButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="RESET"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.954"
app:layout_constraintStart_toStartOf="parent"
android:src="#drawable/ic_baseline_delete_24"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
package com.example.dapple
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import com.example.dapple.PaintView.Companion.colorList
import com.example.dapple.PaintView.Companion.currentBrush
import com.example.dapple.PaintView.Companion.pathList
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
companion object{
var path = Path()
var paintBrush = Paint()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportActionBar?.hide()
redColor.setOnClickListener{
paintBrush.color = Color.RED
currentColor(paintBrush.color)
}
blueColor.setOnClickListener{
paintBrush.color = Color.BLUE
currentColor(paintBrush.color)
}
blackColor.setOnClickListener{
paintBrush.color = Color.BLACK
currentColor(paintBrush.color)
}
whiteColor.setOnClickListener{
paintBrush.color = Color.WHITE
currentColor(paintBrush.color)
}
resetButton.setOnClickListener{
pathList.clear()
colorList.clear()
path.reset()
}
}
private fun currentColor(color: Int){
currentBrush = color
path = Path()
}
}
PaintView.kt
package com.example.dapple
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintSet
import com.example.dapple.MainActivity.Companion.paintBrush
import com.example.dapple.MainActivity.Companion.path
class PaintView : View {
var params:ViewGroup.LayoutParams? = null
companion object{
var pathList = ArrayList<Path>()
var colorList = ArrayList<Int>()
var currentBrush = Color.WHITE
}
constructor(context: Context) : this(context, null){
init()
}
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
{
init()
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init()
}
private fun init(){
paintBrush.isAntiAlias = true
paintBrush.color = currentBrush
paintBrush.style = Paint.Style.STROKE
paintBrush.strokeJoin = Paint.Join.ROUND
paintBrush.strokeWidth = 8f
params = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
var x = event.x
var y = event.y
when(event.action){
MotionEvent.ACTION_DOWN->{
path.moveTo(x,y)
return true
}
MotionEvent.ACTION_MOVE->{
path.lineTo(x,y)
pathList.add(path)
colorList.add(currentBrush)
}
else -> return false
}
postInvalidate()
return false
}
override fun onDraw(canvas: Canvas) {
for(i in pathList.indices){
paintBrush.setColor(colorList[i])
canvas.drawPath(pathList[i],paintBrush)
invalidate()
}
}
}

My Recyclerview sometimes display data and sometimes it doesnt

My Recyclerview sometimes needs a refresh to start displaying data, some times it need much time to display the data and sometimes it works like a charm. I even used the same adapter for 2 recycler views one of them displayed data immediately but the other didn't show at all like the the image below:
This problem started to show up when I added another recycler view but in a different fragment that used same adapter. I thought using same adapter was the problem so I created a new one but the problem is still there. Note: I'm using same layout for displaying the recycler view items too. Any Ideas would be really helpful.
Thanks
UPDATE: Codes
Adapter :
import android.graphics.Paint
import android.view.LayoutInflater
import android.view.TextureView
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.ecommapp.R
import com.example.ecommapp.main_activity.data.Product
import kotlinx.android.synthetic.main.product_item_layout.view.*
class ProductItemAdapter(val listener: ProductItemAdapter.onItemClickListener) : ListAdapter<Product, ProductItemAdapter.ProductViewHolder>(Comparator()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
val inflater = LayoutInflater.from(parent.context).inflate(R.layout.product_item_layout, parent, false)
return ProductViewHolder(inflater)
}
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
val currentItem = getItem(position)
if (currentItem != null) {
holder.bind(currentItem)
}
}
inner class ProductViewHolder(view : View): RecyclerView.ViewHolder(view){
init {
view.setOnClickListener{
val position = adapterPosition
val product = getItem(position)
if(position != RecyclerView.NO_POSITION){
listener.onItemClick(product)
}
}
}
private val container = view.container_main
private val imageView = view.img_product_item_not_card
private val productName = view.tv_product_item_label
private val productNewPrice = view.tv_product_item_new_price
private val productOldPrice = view.tv_product_item_old_price
fun bind(product: Product) {
productName.text = product.title
productNewPrice.text = product.price
/*var temp = (product.price as Double ) * 1.5
var oldPrice = temp as String
productOldPrice.text = oldPrice
productOldPrice.paintFlags = productOldPrice.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG*/
//Context of the view
Glide.with(imageView.context) // Context
.load(product.image) // Data
.into(imageView) // View
}
}
interface onItemClickListener{
fun onItemClick(product : Product)
}
class Comparator : DiffUtil.ItemCallback<Product>() {
override fun areItemsTheSame(oldItem: Product, newItem: Product) =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Product, newItem: Product) =
oldItem == newItem
}
}
View Model:
import android.content.ContentValues.TAG
import android.util.Log
import androidx.lifecycle.*
import com.example.ecommapp.main_activity.data.Product
import com.example.ecommapp.main_activity.retrofit.ProductsApi
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.scopes.ViewModelScoped
import kotlinx.coroutines.launch
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import javax.inject.Inject
#HiltViewModel
class HomeViewModel #Inject constructor(
private val savedStateHandle: SavedStateHandle,
val api : ProductsApi
) : ViewModel() {
var new_collection_list : MutableLiveData<List<Product>>
var best_sellling_list : MutableLiveData<List<Product>>
init {
new_collection_list = MutableLiveData()
best_sellling_list = MutableLiveData()
get_best_selling_data()
get_new_collection_data()
}
fun get_new_collection_data(){
var call = api.get_products_desc()
call.enqueue(object : Callback<List<Product>> {
override fun onResponse(call: Call<List<Product>>, response: Response<List<Product>>) {
if (response.body() != null){
new_collection_list.postValue(response.body())
Log.d(TAG, "onResponse: Success Response")
}
else{
new_collection_list.postValue(null)
Log.d(TAG, "onResponse: Null Response")
}
}
override fun onFailure(call: Call<List<Product>>, t: Throwable) {
Log.d(TAG, "onFailure: Failure Response")
}
})
}
fun get_best_selling_data() {
var call = api.get_products_asc()
call.enqueue(object : Callback<List<Product>> {
override fun onResponse(call: Call<List<Product>>, response: Response<List<Product>>) {
if (response.body() != null){
best_sellling_list.postValue(response.body())
Log.d(TAG, "onResponse: Success Response")
}
else{
best_sellling_list.postValue(null)
Log.d(TAG, "onResponse: Null Response")
}
}
override fun onFailure(call: Call<List<Product>>, t: Throwable) {
Log.d(TAG, "onFailure: Failure Response")
}
})
}
fun on_swipe_refresh(){
get_new_collection_data()
get_best_selling_data()
}
}
Fragment :
package com.example.ecommapp.main_activity.fragments.home_fragment
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.view.View
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.example.ecommapp.R
import com.example.ecommapp.main_activity.data.Product
import com.example.ecommapp.main_activity.shared_files.recycler_view_adapters.ProductItemAdapter
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.android.synthetic.main.home_fragment_layout.*
#AndroidEntryPoint
class HomeFragment: Fragment(R.layout.home_fragment_layout), ProductItemAdapter.onItemClickListener {
lateinit var new_collection_list : LiveData<List<Product>>
lateinit var best_selling_list : LiveData<List<Product>>
val new_collections_adapter = ProductItemAdapter(this)
val best_selling_adapter = ProductItemAdapter(this)
val viewModel : HomeViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// New Collection Recycler View Setup
rv_new_collections.layoutManager = LinearLayoutManager(
activity?.applicationContext,
LinearLayoutManager.HORIZONTAL,
false
)
rv_new_collections.adapter = new_collections_adapter
// Best Selling Recycler View Setup
rv_best_selling.layoutManager = LinearLayoutManager(
activity?.applicationContext,
LinearLayoutManager.HORIZONTAL,
false
)
rv_best_selling.adapter = best_selling_adapter
//
set_data()
Handler().postDelayed({
if ( isConnected(activity?.applicationContext) ){
layout_new_collection_shimmer.visibility = View.INVISIBLE
rv_new_collections.visibility = View.VISIBLE
layout_best_selling_shimmer.visibility = View.INVISIBLE
rv_best_selling.visibility = View.VISIBLE
set_data()
}
else{
Toast.makeText(activity?.applicationContext, "No Internet Connection, Swipe to reload.", Toast.LENGTH_LONG )
.show()
}
}, 2000)
container_swipe.setOnRefreshListener(object : SwipeRefreshLayout.OnRefreshListener {
override fun onRefresh() {
if ( isConnected(activity?.applicationContext) ){
if( layout_new_collection_shimmer.visibility == View.INVISIBLE){
layout_new_collection_shimmer.visibility = View.INVISIBLE
rv_new_collections.visibility = View.VISIBLE
layout_best_selling_shimmer.visibility = View.INVISIBLE
rv_best_selling.visibility = View.VISIBLE
}
viewModel.on_swipe_refresh()
set_data()
}
else{
Toast.makeText(activity?.applicationContext, "No Internet Connection, Swipe to reload.", Toast.LENGTH_LONG )
.show()
}
// Must be added
container_swipe.isRefreshing = false
}
})
}
fun set_data(){
new_collection_list = viewModel.new_collection_list
best_selling_list = viewModel.best_sellling_list
new_collection_list.observe(viewLifecycleOwner, Observer {
new_collections_adapter.submitList(it)
new_collections_adapter.notifyDataSetChanged()
})
best_selling_list.observe(viewLifecycleOwner, Observer {
best_selling_adapter.submitList(it)
best_selling_adapter.notifyDataSetChanged()
})
}
fun isConnected(ctx: Context?): Boolean {
val hasInternet: Boolean
val connectivityManager =
ctx?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val networkCapabilities = connectivityManager.activeNetwork ?: return false
val actNw =
connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false
hasInternet = when {
actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
else -> false
}
} else {
hasInternet = try {
if (connectivityManager.activeNetworkInfo == null) {
false
} else {
connectivityManager.activeNetworkInfo?.isConnected!!
}
} catch (e: Exception) {
false
}
}
return hasInternet}
override fun onItemClick(product : Product) {
val action = HomeFragmentDirections.actionHomeFragmentToProductFragment(product, product.title, R.id.homeFragment)
findNavController().navigate(action)
}
}
Layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="#+id/container_swipe"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".main_activity.fragments.home_fragment.HomeFragment"
>
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.SearchView
android:id="#+id/search_view"
android:layout_width="0dp"
android:layout_height="50dp"
app:layout_constraintWidth_percent="0.9"
android:elevation="2dp"
android:outlineProvider="bounds"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="20dp"/>
<androidx.cardview.widget.CardView
android:id="#+id/img_advertisment"
android:layout_width="0dp"
android:layout_height="180dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/search_view"
app:layout_constraintWidth_percent="0.9"
android:background="#00000000"
app:cardElevation="0dp"
app:cardCornerRadius="10dp"
>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/advertisment"
/>
</androidx.cardview.widget.CardView>
<RelativeLayout
android:id="#+id/container_new_collection"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="#id/img_advertisment"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintWidth_percent="0.9"
android:layout_marginTop="20dp"
>
<TextView
android:id="#+id/tv_new_collection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Collections"
android:textColor="#color/tomato"
android:textSize="25dp"
android:fontFamily="sans-serif-black"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show All"
android:textSize="20dp"
android:textColor="#c6c4ce"
android:fontFamily="sans-serif-black"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginTop="5dp"/>
<com.facebook.shimmer.ShimmerFrameLayout
android:id="#+id/layout_new_collection_shimmer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:shimmer_repeat_mode="restart"
app:shimmer_shape="linear"
android:layout_below="#id/tv_new_collection"
android:layout_marginTop="10dp">
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
</LinearLayout>
</HorizontalScrollView>
</com.facebook.shimmer.ShimmerFrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:layout_marginTop="10dp"
android:id="#+id/rv_new_collections"
android:layout_width="wrap_content"
android:layout_height="350dp"
android:layout_below="#id/tv_new_collection"
tools:listitem="#layout/product_item_layout"
android:visibility="invisible"
/>
</RelativeLayout>
<RelativeLayout
android:id="#+id/container_best_selling"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="#id/container_new_collection"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintWidth_percent="0.9"
android:layout_marginTop="20dp"
>
<TextView
android:id="#+id/tv_best_selling"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Best Selling"
android:textColor="#color/tomato"
android:textSize="25dp"
android:fontFamily="sans-serif-black"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show All"
android:textSize="20dp"
android:textColor="#c6c4ce"
android:fontFamily="sans-serif-black"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginTop="5dp"
/>
<com.facebook.shimmer.ShimmerFrameLayout
android:id="#+id/layout_best_selling_shimmer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:shimmer_repeat_mode="restart"
app:shimmer_shape="linear"
android:layout_marginTop="10dp"
android:layout_below="#id/tv_best_selling">
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
</LinearLayout>
</HorizontalScrollView>
</com.facebook.shimmer.ShimmerFrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_best_selling"
android:layout_width="wrap_content"
android:layout_height="350dp"
android:layout_below="#id/tv_best_selling"
tools:listitem="#layout/product_item_layout"
android:layout_marginTop="10dp"
android:visibility="invisible"
/>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
I Figured out what is the problem. The API takes much time to send the data. So, the recycler view renders the view with null values.

RecyclerView doesn't scroll smoothly initially

I'm using Tmdb API and displaying it in a RecyclerView. The RecyclerView is not scrolling smoothly initially but then is working fine. I've tried to change Recyclerview height to 0dp or match_parent but it's still the same. I've tried also android:nestedScrollingEnabled="true". Please help me to fix this problem in the RecyclerView. Thank you in advance.
fragment_add.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.AddFragment">
<androidx.appcompat.widget.AppCompatAutoCompleteTextView
android:id="#+id/fragment_add_movieSeriesACT"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:background="#drawable/edit_text_background"
android:hint="#string/movie_or_series"
android:imeOptions="actionDone"
android:inputType="textEmailAddress"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:textColor="#color/colorPrimary"
android:textColorHint="#color/colorEight"
android:textCursorDrawable="#null"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/fragment_add_mainRV"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/fragment_add_movieSeriesACT" />
<ProgressBar
android:id="#+id/fragment_add_mainPB"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:indeterminateTint="#color/colorOne"
android:indeterminateTintMode="src_atop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
AddFragment.kt
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import com.martiandeveloper.muuvi.R
import com.martiandeveloper.muuvi.adapter.RecyclerViewMovieAdapter
import com.martiandeveloper.muuvi.viewmodel.AddViewModel
import kotlinx.android.synthetic.main.fragment_add.*
class AddFragment : Fragment() {
private lateinit var viewModel: AddViewModel
private val adapter = RecyclerViewMovieAdapter(arrayListOf())
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_add, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUI()
}
private fun initUI() {
viewModel = ViewModelProviders.of(this).get(AddViewModel::class.java)
setRecyclerView()
observe()
viewModel.refreshData()
}
private fun setRecyclerView() {
fragment_add_mainRV.layoutManager = LinearLayoutManager(context)
fragment_add_mainRV.adapter = adapter
}
private fun observe() {
viewModel.movieList.observe(viewLifecycleOwner, Observer { movieList ->
movieList?.let {
adapter.updateMovieList(it)
}
})
viewModel.isError.observe(viewLifecycleOwner, Observer { isError ->
isError?.let {
setProgress(1F, true, View.GONE)
if (it) {
setToast(resources.getString(R.string.something_went_wrong_please_try_again_later))
}
}
})
viewModel.isLoading.observe(viewLifecycleOwner, Observer { isLoading ->
isLoading?.let {
if (it) {
setProgress(.5F, false, View.VISIBLE)
} else {
setProgress(1F, true, View.GONE)
}
}
})
}
private fun setProgress(alpha: Float, enable: Boolean, visible: Int) {
fragment_add_movieSeriesACT.alpha = alpha
fragment_add_mainRV.alpha = alpha
fragment_add_movieSeriesACT.isEnabled = enable
fragment_add_mainPB.visibility = visible
}
private fun setToast(text: String) {
Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
}
}
AddViewModel.kt
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.martiandeveloper.muuvi.model.MovieResult
import com.martiandeveloper.muuvi.model.Movie
import com.martiandeveloper.muuvi.service.MovieService
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.observers.DisposableSingleObserver
import io.reactivex.schedulers.Schedulers
class AddViewModel : ViewModel() {
private val movieService = MovieService()
private val disposable = CompositeDisposable()
val movieList = MutableLiveData<List<Movie>>()
val isError = MutableLiveData<Boolean>()
val isLoading = MutableLiveData<Boolean>()
fun refreshData() {
isLoading.value = true
disposable.add(
movieService.getData().subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableSingleObserver<MovieResult>() {
override fun onSuccess(t: MovieResult) {
movieList.value = t.results
isError.value = false
isLoading.value = false
}
override fun onError(e: Throwable) {
isError.value = true
isLoading.value = false
}
})
)
}
}
RecyclerViewMovieAdapter.kt
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.martiandeveloper.muuvi.R
import com.martiandeveloper.muuvi.model.Movie
import kotlinx.android.synthetic.main.recyclerview_movie_item.view.*
class RecyclerViewMovieAdapter(private val movieList: ArrayList<Movie>) :
RecyclerView.Adapter<RecyclerViewMovieAdapter.RecyclerViewMovieViewHolder>() {
lateinit var context: Context
class RecyclerViewMovieViewHolder(var view: View) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): RecyclerViewMovieViewHolder {
context = parent.context
val view = LayoutInflater.from(context)
.inflate(R.layout.recyclerview_movie_item, parent, false)
return RecyclerViewMovieViewHolder(view)
}
override fun getItemCount(): Int {
return movieList.size
}
override fun onBindViewHolder(holder: RecyclerViewMovieViewHolder, position: Int) {
holder.view.recyclerview_movie_item_movieTitleMTV.text =
movieList[position].movieTitle
holder.view.recyclerview_movie_item_movieVoteAverageMTV.text =
movieList[position].movieVoteAverage.toString()
Glide.with(context)
.load("https://image.tmdb.org/t/p/w300${movieList[position].moviePosterPath}")
.into(holder.view.recyclerview_movie_item_moviePosterIV)
}
fun updateMovieList(newMovieList: List<Movie>) {
movieList.clear()
movieList.addAll(newMovieList)
notifyDataSetChanged()
}
}
recyclerview_movie_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<androidx.cardview.widget.CardView
android:layout_width="80dp"
android:layout_height="80dp"
app:cardBackgroundColor="#android:color/transparent"
app:cardCornerRadius="10dp"
app:cardElevation="0dp">
<ImageView
android:id="#+id/recyclerview_movie_item_moviePosterIV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="#string/app_name"
android:scaleType="centerCrop"
app:srcCompat="#drawable/default_user_image" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="#+id/recyclerview_movie_item_movieTitleMTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/colorPrimary"
android:textSize="18sp"
android:textStyle="bold" />
<com.google.android.material.textview.MaterialTextView
android:id="#+id/recyclerview_movie_item_movieVoteAverageMTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/colorPrimaryDark" />
</LinearLayout>
</LinearLayout>
I believe this is due to size of the image , I had similar issue try to fit the image you obtain using glide into imageview, I have added centerCrop():
Glide.with(context).load("https://image.tmdb.org/t/p/w300${movieList[position].moviePosterPath}") .centerCrop().into(holder.view.recyclerview_movie_item_moviePosterIV);
If this does not work try to change imageview scaletype or centerCrop() to fitCenter()

Old view data appears in activity briefly, before new data is put into activity using databinding and MVVM

I have two activities-
A. Product List
B. Product Detail
Whenever user clicks on product item, he goes to the product detail activity where an API is called which shows detail of that product.
Now the problem is, that for the first time, the views in Product Detail initially have no data and i am inflating the data in views using livedata in viewmodel, with the help of data-binding. Now when the user navigates back to Product List , finish is called on Product Detail Activity.
Mow when user again clicks on another product, this time when he is taken to the product detail screen, old product detail briefly appears before new product detail is fetched from API and binded in the views. I don't know what is causing this behavior. Can somebody point me in the right direction ?
I have already tried calling finish and System.gc, but somehow the view holds on to the old data before new data is inflated in it.
Product List Activity
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import androidx.core.view.GravityCompat
import androidx.appcompat.app.ActionBarDrawerToggle
import android.view.MenuItem
import android.view.View
import androidx.drawerlayout.widget.DrawerLayout
import com.google.android.material.navigation.NavigationView
import android.widget.TextView
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.app.thingle.R
import com.app.thingle.databinding.ActivitySideMenuBinding
import com.app.thingle.data.model.dashBoardDataPackage.DashBoardStatus
import com.app.thingle.data.model.dashBoardDataPackage.Product
import com.app.thingle.utility.Constants
import com.app.thingle.viewModel.DashboardViewModel
import kotlinx.android.synthetic.main.activity_side_menu.view.*
import kotlinx.android.synthetic.main.app_bar_side_menu.view.*
import java.util.ArrayList
class SideMenuActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
lateinit var sideMenuBinding: ActivitySideMenuBinding
lateinit var dashboardViewModel: DashboardViewModel
private lateinit var cartTextView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sideMenuBinding = DataBindingUtil.setContentView(this, R.layout.activity_side_menu)
dashboardViewModel = ViewModelProviders.of(this).get(DashboardViewModel::class.java)
sideMenuBinding.viewModel = dashboardViewModel
dashboardViewModel.getProductsFromServer()
sideMenuBinding.navView.setNavigationItemSelectedListener(this)
val toggle = ActionBarDrawerToggle(
this,
sideMenuBinding.drawerLayout,
sideMenuBinding.appBarSideMenu.mainToolbar,
R.string.navigation_drawer_open,
R.string.navigation_drawer_close
)
toggle.isDrawerIndicatorEnabled = false
sideMenuBinding.drawerLayout.addDrawerListener(toggle)
toggle.syncState()
if (!dashboardViewModel.isUser()) {
sideMenuBinding.drawerLayout.nav_view.menu.findItem(R.id.nav_log_out).isVisible = false
}
sideMenuBinding.appBarSideMenu.mainToolbar.toolbar_action_holder.menu_icon_id.setOnClickListener {
sideMenuBinding.drawerLayout.openDrawer(GravityCompat.START)
}
cartTextView =
sideMenuBinding.appBarSideMenu.mainToolbar.toolbar_action_holder.cart_holder.cart_amount
setUpBindings()
}
private fun setUpBindings() {
handleActivityStatus()
handleLoaderState()
handleApiResponse()
setUpListUpdate()
setUpCartStatus()
setUpListClicks()
}
private fun setUpListUpdate() {
dashboardViewModel.getProductsByType().observe(this, Observer {
dashboardViewModel.setItemsInAdapter(it)
})
}
private fun setUpCartStatus() {
dashboardViewModel.getCartCount().observe(this, Observer {
if (it > 0) {
cartTextView.visibility = View.VISIBLE
cartTextView.text = it.toString()
}
})
}
private fun handleApiResponse() {
dashboardViewModel.getApiResponse().observe(this, Observer { handleResponse(it) })
}
private fun handleLoaderState() {
dashboardViewModel.getUiState().observe(this, Observer { handleState(it) })
}
private fun handleActivityStatus() {
dashboardViewModel.getDashboardStatus().observe(this, Observer {
when (it) {
DashBoardStatus.WRONG_PRODUCT_TYPE -> {
sideMenuBinding.appBarSideMenu.mainContent.noResultFoundLayout.visibility =
View.VISIBLE
sideMenuBinding.appBarSideMenu.mainContent.recyclerText.visibility = View.GONE
}
DashBoardStatus.GO_TO_CART_SCREEN -> {
showToast("Go to Cart")
}
DashBoardStatus.GO_TO_SEARCH_SCREEN -> {
this.startActivity(
Intent(this, SearchScreen::class.java)
)
}
DashBoardStatus.EMPTY_LIST -> {
sideMenuBinding.appBarSideMenu.mainContent.noResultFoundLayout.visibility =
View.VISIBLE
sideMenuBinding.appBarSideMenu.mainContent.recyclerText.visibility = View.GONE
}
DashBoardStatus.FETCH_API_SUCCESS -> {
sideMenuBinding.appBarSideMenu.mainContent.recyclerText.visibility =
View.VISIBLE
}
DashBoardStatus.FETCH_API_ERROR -> {
sideMenuBinding.appBarSideMenu.mainContent.noResultFoundLayout.visibility =
View.VISIBLE
sideMenuBinding.appBarSideMenu.mainContent.recyclerText.visibility = View.GONE
}
DashBoardStatus.NO_CONNECTION -> {
showToast(getString(R.string.no_network))
}
else -> showToast(getString(R.string.something_went_wrong))
}
})
}
private fun setUpListClicks() {
dashboardViewModel.getSelectedProductType().observe(this, Observer {
this.startActivity(
Intent(this, BooksCategoryActivity::class.java)
.putParcelableArrayListExtra(
Constants.PRODUCT_LIST,
it.Products as ArrayList<Product>
)
.putExtra(Constants.CATEGORY_NAME, it.name)
.putExtra(Constants.ID, it.id)
)
})
dashboardViewModel.getSelectedProduct().observe(this, Observer {
this.startActivity(
Intent(this, BookDetailActivity::class.java)
.putExtra(Constants.PRODUCT_ID, it.id)
)
})
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.nav_borrows -> {
if (dashboardViewModel.isUser()) {
this.startActivity(Intent(this, BorrowHistoryActivity::class.java))
} else {
goToLogin()
}
}
R.id.nav_contributions -> {
if (dashboardViewModel.isUser()) {
this.startActivity(Intent(this, ContributionHistoryActivity::class.java))
} else {
goToLogin()
}
}
R.id.nav_profile -> {
if (dashboardViewModel.isUser()) {
this.startActivity(Intent(this, EditProfileActivity::class.java))
} else {
goToLogin()
}
}
R.id.nav_help_contact -> {
if (dashboardViewModel.isUser()) {
this.startActivity(Intent(this, ContactUsActivity::class.java))
} else {
goToLogin()
}
}
R.id.nav_log_out -> {
logOut()
}
}
val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
drawerLayout.closeDrawer(GravityCompat.START)
return true
}
private fun goToLogin() {
this.startActivity(Intent(this, LoginActivity::class.java))
finishAffinity()
}
private fun logOut() {
dashboardViewModel.clearUserData()
goToLogin()
}
override fun onBackPressed() {
val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START)
} else {
this.startActivity(Intent(this, ChooseTypeActivity::class.java))
killActivity()
}
}
private fun killActivity() {
this.finishAffinity()
}
}
layout- Product Detail
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.app.thingle.viewModel.ProductDetailViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/main_layout_holder"
tools:context=".ui.activities.BookDetailActivity">
<include
android:id="#+id/about_book_toolbar"
layout="#layout/thingle_search_and_cart_toolbar" />
<FrameLayout
android:id="#+id/product_not_available_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="3dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/about_book_toolbar">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/lightPink"
android:fontFamily="#font/avenirltstd_book"
android:gravity="center"
android:padding="5dp"
android:visibility="gone"
android:text="#string/this_product_is_currently_unavailable"
android:textColor="#color/white"
android:textSize="14sp" />
</FrameLayout>
<ScrollView
android:id="#+id/about_product_scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/product_not_available_holder">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/book_image_id"
android:layout_width="100dp"
android:layout_height="140dp"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/book_name_id"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintVertical_bias="0.1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:fontFamily="#font/avenirltstd_black"
android:textColor="#color/black"
android:textSize="22sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/book_image_id"
app:layout_constraintTop_toTopOf="#id/book_image_id"
app:layout_constraintBottom_toTopOf="#id/book_intro"
/>
<TextView
android:id="#+id/book_intro"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="20dp"
android:fontFamily="#font/avenirltstd_book"
android:textColor="#color/black"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/book_image_id"
app:layout_constraintTop_toBottomOf="#id/book_name_id"
app:layout_constraintBottom_toBottomOf="#id/book_image_id"
/>
<Button
android:id="#+id/add_cart_id"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:background="#drawable/pink_button_border"
android:fontFamily="#font/avenirltstd_book"
android:onClick="#{viewModel::onAddCartButtonClicked}"
android:text="#string/add_to_cart"
android:textAllCaps="false"
android:textColor="#color/white"
android:textSize="18sp"
app:layout_constraintEnd_toStartOf="#id/share_id"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/book_image_id" />
<Button
android:id="#+id/share_id"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="20dp"
android:background="#drawable/pink_button_border"
android:fontFamily="#font/avenirltstd_book"
android:onClick="#{viewModel::onShareButtonClicked}"
android:text="#string/share"
android:textAllCaps="false"
android:textColor="#color/white"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="#id/add_cart_id"
app:layout_constraintTop_toBottomOf="#+id/book_image_id" />
<View
android:id="#+id/line_id"
android:layout_width="0dp"
android:layout_height="0.1dp"
android:layout_marginTop="20dp"
android:background="#color/grey"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/add_cart_id" />
<TextView
android:id="#+id/about_book_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="15dp"
android:fontFamily="#font/avenirltstd_book"
android:text="#string/about"
android:textColor="#color/black"
android:textSize="22sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/line_id" />
<TextView
android:id="#+id/text_info_book"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="20dp"
android:fontFamily="#font/avenirltstd_book"
android:lineSpacingExtra="#dimen/line_spacing"
android:textColor="#color/lightBlack"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/about_book_info" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Product Detail Activity
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.TextView
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.app.thingle.R
import com.app.thingle.data.model.bookDetailDataPackage.ProductDetailStatus
import com.app.thingle.databinding.ActivityBookDetailBinding
import com.app.thingle.utility.Constants
import com.app.thingle.viewModel.ProductDetailViewModel
import kotlinx.android.synthetic.main.thingle_search_and_cart_toolbar.view.*
import android.view.KeyEvent.KEYCODE_BACK
import android.view.KeyEvent
import androidx.lifecycle.ViewModel
class BookDetailActivity : BaseActivity() {
private lateinit var bookDetailBinding: ActivityBookDetailBinding
private lateinit var productDetailViewModel: ProductDetailViewModel
private lateinit var cartTextView: TextView
private lateinit var toolBarTitle: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bookDetailBinding = DataBindingUtil.setContentView(this, R.layout.activity_book_detail)
productDetailViewModel = ViewModelProviders.of(this).get(ProductDetailViewModel::class.java)
bookDetailBinding.viewModel = productDetailViewModel
productDetailViewModel.getProductById(intent.getIntExtra(Constants.PRODUCT_ID, -1))
setUpBindings()
setUpToolbar()
}
private fun setUpToolbar() {
toolBarTitle = bookDetailBinding.aboutBookToolbar.toolbar_action_holder.thingle_toolbar_text
toolBarTitle.text = getString(R.string.about_Book)
bookDetailBinding.aboutBookToolbar.toolbar_action_holder.thingle_logo_toolbar_back.setOnClickListener { onBackPressed() }
bookDetailBinding.aboutBookToolbar.toolbar_action_holder.toolbar_search_id.setOnClickListener {
this.startActivity(
Intent(this, SearchScreen::class.java)
)
finish()
}
bookDetailBinding.aboutBookToolbar.toolbar_action_holder.cart_id.setOnClickListener {
showToast(
"Go To Cart"
)
}
cartTextView =
bookDetailBinding.aboutBookToolbar.toolbar_action_holder.cart_holder.cart_amount
}
private fun setUpBindings() {
setUpValues()
handleLoaderState()
handleApiResponse()
setUpCartStatus()
handleActivityStatus()
setUpButtonClick()
}
private fun setUpValues() {
productDetailViewModel.getProduct().observe(this, Observer {
if (!it.isAvailable) {
bookDetailBinding.productNotAvailableHolder.visibility = View.VISIBLE
bookDetailBinding.addCartId.visibility = View.GONE
}
bookDetailBinding.bookNameId.text = it.title
bookDetailBinding.bookIntro.text = it.shortDescription
bookDetailBinding.textInfoBook.text = it.description
if (it.ProductImages!!.isNotEmpty()) {
if (it.ProductImages[0].imageUrl != null) {
setImageUrlOnImageView(
bookDetailBinding.bookImageId,
it.ProductImages[0].imageUrl
)
}
}
})
}
private fun setUpButtonClick() {
productDetailViewModel.getProductId().observe(this, Observer {
showToast(it.toString())
})
}
private fun handleActivityStatus() {
productDetailViewModel.getActivityStatus().observe(this, Observer {
when (it) {
ProductDetailStatus.FETCH_API_SUCCESS -> {
}
ProductDetailStatus.FETCH_API_ERROR -> {
showToast(getString(R.string.something_went_wrong))
onBackPressed()
}
ProductDetailStatus.NO_CONNECTION -> {
showToast(getString(R.string.no_network))
}
ProductDetailStatus.EMPTY_DATA -> {
showToast(getString(R.string.no_data))
onBackPressed()
}
ProductDetailStatus.SHARE_BUTTON_CLICKED -> {
showToast("Share")
}
//ProductDetailStatus.ADD_CART_BUTTON_CLICKED->{showToast("Add To Cart")}
else -> {
showToast(getString(R.string.something_went_wrong))
}
}
})
}
private fun setUpCartStatus() {
productDetailViewModel.getCartCount().observe(this, Observer {
if (it > 0) {
cartTextView.visibility = View.VISIBLE
cartTextView.text = it.toString()
}
})
}
private fun handleApiResponse() {
productDetailViewModel.getApiResponse().observe(this, Observer { handleResponse(it) })
}
private fun handleLoaderState() {
productDetailViewModel.getUiState().observe(this, Observer { handleState(it) })
}
override fun onBackPressed() {
super.onBackPressed()
killActivity()
}
private fun killActivity() {
finish()
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KEYCODE_BACK) {
killActivity()
}
return super.onKeyDown(keyCode, event)
}
}
Product Detail Viewmodel
import android.app.Application
import android.view.View
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.app.thingle.data.model.bookDetailDataPackage.ProductDetailStatus
import com.app.thingle.data.model.bookDetailDataPackage.ResponseObject
import com.app.thingle.data.repository.CartRepository
import com.app.thingle.data.repository.ProductDetailRepository
import com.app.thingle.utility.ApiResponseCallback
import com.app.thingle.utility.ConnectionDetector
import com.app.thingle.utility.SingleLiveEvent
class ProductDetailViewModel(application: Application) : BaseViewModel(application) {
private var product: MutableLiveData<ResponseObject> = MutableLiveData()
private var productId: MutableLiveData<Int> = MutableLiveData()
private var userCartItems =
MutableLiveData<List<com.app.thingle.data.model.cartDataPackage.ResponseObject>>()
var cartCount: MutableLiveData<Int> = MutableLiveData()
private val productDetailStatus = SingleLiveEvent<ProductDetailStatus>()
fun getProductById(productId: Int) {
if (product.value == null && productId != -1) {
fetchProductsByType(productId)
} else {
productDetailStatus.value = ProductDetailStatus.EMPTY_DATA
}
}
private fun fetchProductsByType(id: Int) {
if (ConnectionDetector.getInstance(context).isConnectionAvailable()) {
setLoaderState(true)
product = ProductDetailRepository.instance.fetchProductById(
id,
context,
object : ApiResponseCallback {
override fun provideResponse(
isApiError: Boolean,
isResponseError: Boolean,
response: String
) {
if (isApiError || isResponseError) {
setLoaderState(false)
productDetailStatus.value = ProductDetailStatus.FETCH_API_ERROR
} else {
productDetailStatus.value = ProductDetailStatus.FETCH_API_SUCCESS
if (product.value == null) {
productDetailStatus.value = ProductDetailStatus.EMPTY_DATA
}
if (isUser()) {
fetchCartForUser()
} else {
setLoaderState(false)
cartCount.value = 0
}
}
}
})
} else {
productDetailStatus.value = ProductDetailStatus.NO_CONNECTION
}
}
private fun fetchCartForUser() {
userCartItems =
CartRepository.instance.fetchCartForUser(context, object : ApiResponseCallback {
override fun provideResponse(
isApiError: Boolean,
isResponseError: Boolean,
response: String
) {
setLoaderState(false)
if (isApiError || isResponseError) {
cartCount.value = 0
} else {
if (userCartItems.value!!.isNotEmpty()) {
cartCount.value = userCartItems.value!!.size
}
}
}
})
}
fun getProductId(): LiveData<Int> {
return productId
}
fun getProduct(): LiveData<ResponseObject> {
return product
}
fun getCartCount(): LiveData<Int> {
return cartCount
}
fun getActivityStatus(): SingleLiveEvent<ProductDetailStatus> {
return productDetailStatus
}
fun onAddCartButtonClicked(v: View) {
productDetailStatus.value = ProductDetailStatus.ADD_CART_BUTTON_CLICKED
productId.value = product.value!!.id
}
fun onShareButtonClicked(v: View) {
productDetailStatus.value = ProductDetailStatus.SHARE_BUTTON_CLICKED
}
}
I found out the solution by implementing onCleared() inside my viewmodel and clearing my viewmodel objects.
super.onCreate(null) instead of super.onCreate(savedInstanceState) should do the trick.
You might want to put in some code to check if you want this to happen (ie. last instance was finished) or not (ie. you navigated away and system cleaned up your activity which it has to restart)

Categories

Resources