Search code examples
androidkotlinmvvmandroid-databindingondestroy

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
    }


}

Solution

  • I found out the solution by implementing onCleared() inside my viewmodel and clearing my viewmodel objects.