DSL-like OnClick-listener for a custom view using kotlin in Android - android

I'm trying to create an DSL-like OnClick-listener (in kotlin) for a custom view that I'm using several places in the Android project I'm currently working on. The view has an ImageView, a primary textview and a secondary textview. I'm trying to create a listener-helper that allows you to only override specific methods of an interface instead of all of them (inspired by this article). But I can't get it working. Actually it doesn't even work using regular OnClick listeners.
Here's my ErrorMessageView:
class ErrorMessageView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyle, defStyleRes) {
private var mOnClickListener: OnErrorMessageViewClickListener? = null
init {
LayoutInflater.from(context).inflate(
R.layout.custom_errorview,
this,
true)
ButterKnife.bind(this)
}
interface OnErrorMessageViewClickListener {
fun onImageClick()
fun onPrimaryTextClick()
fun onSecondaryTextClick()
}
... left out for brevity ...
fun setOnErrorMessageViewClickListener(
onViewClickListener: OnErrorMessageViewClickListener?) {
this.mOnClickListener = onViewClickListener
}
fun setOnErrorMessageViewClickListener(init: ErrorMessageViewClickListenerHelper.() -> Unit) {
val listener = ErrorMessageViewClickListenerHelper()
listener.init()
mOnClickListener = listener
}
#OnClick(R.id.image_container)
internal fun onImageViewClick() {
Timber.d("Clicked image view")
mOnClickListener?.onImageClick()
}
#OnClick(R.id.primary_text_container)
internal fun onPrimaryTextViewClick() {
Timber.d("Clicked primary textview")
mOnClickListener?.onPrimaryTextClick()
}
#OnClick(R.id.secondary_text_container)
internal fun onSecondaryTextViewClick() {
Timber.d("Clicked secondary textview")
mOnClickListener?.onSecondaryTextClick()
}
}
And here's my helper class:
private typealias ErrorViewClickListener = () -> Unit
open class ErrorMessageViewClickListenerHelper : ErrorMessageView.OnErrorMessageViewClickListener {
private var mImageClick: ErrorViewClickListener? = null
fun onImageClick(onImageClick: ErrorViewClickListener?) {
mImageClick = onImageClick
}
override fun onImageClick() {
mImageClick?..invoke()
}
private var mPrimaryTextClick: ErrorViewClickListener? = null
fun onPrimaryTextClick(onPrimaryTextClick: ErrorViewClickListener?) {
mPrimaryTextClick = onPrimaryTextClick
}
override fun onPrimaryTextClick() {
mPrimaryTextClick?.invoke()
}
private var mSecondaryTextClick: ErrorViewClickListener? = null
fun onSecondaryTextClick(onSecondaryTextClick: ErrorViewClickListener?) {
mSecondaryTextClick = onSecondaryTextClick
}
override fun onSecondaryTextClick() {
mSecondaryTextClick?.invoke()
}
}
My layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp">
<android.support.v7.widget.AppCompatImageView
android:id="#+id/image_container"
android:layout_width="#dimen/dialog_worklist_image_size"
android:layout_height="#dimen/dialog_worklist_image_size"
android:layout_centerVertical="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:ignore="ContentDescription" />
<TextView
android:id="#+id/primary_text_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginEnd="24dp"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:textSize="16sp"
android:clickable="true"
android:focusable="true"
app:layout_constraintEnd_toEndOf="#+id/image_container"
app:layout_constraintStart_toStartOf="#+id/image_container"
app:layout_constraintTop_toBottomOf="#id/image_container" />
<TextView
android:id="#+id/secondary_text_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginEnd="24dp"
android:layout_marginStart="24dp"
android:textSize="16sp"
android:clickable="true"
android:focusable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/primary_text_container" />
</android.support.constraint.ConstraintLayout>
I'm using the helper in my code like this, but nothing gets logged (It's almost as if no clicklistener can be attached):
override fun setWorklistNotEnabledMessage(showMessage: Boolean) {
if (showMessage) {
mView?.dialog_worklist_recyclerview?.visibility = View.GONE
mView?.dialog_worklist_errorview?.apply {
visibility = View.VISIBLE
setSecondaryTextClickListener(View.OnClickListener { Timber.d("Test secondary click") })
setErrorDrawable(R.drawable.ic_worklist_disabled_black_24dp)
setPrimaryText(R.string.global_worklist_disabled_error)
setSecondaryText(R.string.dialog_worklist_worklist_disabled_error_secondary_text)
setOnErrorMessageViewClickListener {
onSecondaryTextClick { Timber.d("Test secondary click") }
onPrimaryTextClick { Timber.d("Test primary click") }
onImageClick { Timber.d("Test image click") }
// . this#WorklistDialog
}
}
} else {
mView?.dialog_worklist_errorview?.apply {
visibility = View.GONE
setErrorDrawable(null)
setPrimaryText("")
setSecondaryText("")
setOnErrorMessageViewClickListener(null)
}
}
}
Does anyone have a suggestion to what is wrong with my code?
Btw. the above code is used in a DialogFragment - that is why you might notice the mView?. as a bit awkward. But that is the way to get a handle on the views in a dialog.

Was just about to start crying when I finally found the solution - OMG.
The problem was the SwipeRefreshLayout in the layout file for the dialogfragment, which can be seen here:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="#+id/dialog_worklist_toolbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:background="#color/colorPrimary"
android:elevation="4dp"
android:minHeight="?android:attr/actionBarSize"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar" />
<com.conhea.smartgfr.ui.examination.layouts.ErrorMessageView
android:id="#+id/dialog_worklist_errorview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/dialog_worklist_toolbar" />
<ProgressBar
android:id="#+id/dialog_worklist_progressbar"
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_toBottomOf="#id/dialog_worklist_toolbar" />
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/dialog_worklist_swiperefreshlayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
app:layout_constraintTop_toBottomOf="#id/dialog_worklist_toolbar">
<android.support.v7.widget.RecyclerView
android:id="#+id/dialog_worklist_recyclerview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/transparent" />
</android.support.v4.widget.SwipeRefreshLayout>
</android.support.constraint.ConstraintLayout>
The SwipeRefreshLayout was blocking the custom ErrorMessageView from being clickable.
After adding mView?.dialog_worklist_swiperefreshlayout?.isEnabled = false to the below in my DialogFragment everything started working:
override fun setWorklistNotEnabledMessage(showMessage: Boolean) {
if (showMessage) {
mView?.dialog_worklist_recyclerview?.visibility = View.GONE
mView?.dialog_worklist_swiperefreshlayout?.isEnabled = false
mView?.dialog_worklist_errorview?.apply {
visibility = View.VISIBLE
setErrorDrawable(R.drawable.ic_worklist_disabled_black_24dp)
setPrimaryText(R.string.global_worklist_disabled_error)
setSecondaryText(R.string.dialog_worklist_worklist_disabled_error_secondary_text)
setOnErrorMessageViewClickListener {
onSecondaryTextClick { Timber.d("Test secondary click") }
onPrimaryTextClick { Timber.d("Test primary click") }
onImageClick { Timber.d("Test image click") }
}
}
} else {
mView?.dialog_worklist_errorview?.apply {
visibility = View.GONE
setErrorDrawable(null)
setPrimaryText("")
setSecondaryText("")
setOnErrorMessageViewClickListener(null)
}
}
}

Related

How can I update the UI without skipping frames

So I am developing and android app in Kotlin with coroutines and no matter what change I make, I still keep getting the message:
I/Choreographer: Skipped 59 frames! The application may be doing too much work on its main thread.
How can I get rid of it. I mean like I am only displaying nine photos... Below is my code
Model:
data class Food (
val id: String,
val name: String,
val price: String,
#Json(name = "img_url") val imgSrcUrl: String,
val type: String,
val description: String,
val average_rating: String,
val number_of_raters: String,
val special_price: String
)
data class FoodCategory(
val id: String,
val title: String,
val foods: List<Food>
)
ViewModel:
enum class NetworkStatus {LOADING, DONE, FAILED}
enum class FontFamily (#FontRes val fontRes: Int) {
POPPINS_BOLD(R.font.poppins_bold),
POPPINS(R.font.poppins)
}
class FoodOverviewViewModel(private val foodRepository: FoodRepository): ViewModel() {
private lateinit var foodProducts: List<Food>
//This is the data that is gonna be exposed to the viewmodel
//It will be submitted to a ListAdapter
private val _foodCategory = MutableLiveData<List<FoodCategory>>()
val foodCategory: LiveData<List<FoodCategory>>
get() = _foodCategory
//Used to display a progress bar for network status
private val _status = MutableLiveData<NetworkStatus>()
val status: LiveData<NetworkStatus>
get() = _status
init {
getOverviewProducts()
}
private fun getOverviewProducts() {
viewModelScope.launch(Dispatchers.Default) {
_status.postValue(NetworkStatus.LOADING)
try {
getUpdatedFood()
Log.i("getOverviewProducts","I am running on tread: $coroutineContext")
_status.postValue(NetworkStatus.DONE)
}catch (e: Exception) {
_status.postValue(NetworkStatus.FAILED)
}
}
}
private suspend fun getUpdatedFood() {
//withContext(Dispatchers.Default) {
val limiter = 6 //Number of items I want to get from the server
val foodCategory = arrayListOf<FoodCategory>()
Log.i("getUpdatedFood","I am running on tread: $coroutineContext")
val getRecommended = foodRepository.getRecommendedFood(limiter.toString())
foodCategory += FoodCategory(id = 0.toString(), title = "Recommended for you", foods = getRecommended)
val getSpecials = foodRepository.getSpecials(limiter.toString())
foodCategory += FoodCategory(id = 1.toString(), title = "Specials", foods = getSpecials)
_foodCategory.postValue(foodCategory)
//}
}
}
Repository:
class FoodRepository {
suspend fun getRecommendedFood(limiter: String) = withContext(Dispatchers.IO) {
Log.i("Resp-getRecommended","I am running on tread: $coroutineContext")
return#withContext ProductApi.retrofitService.getRecommended(limiter)
}
suspend fun getSpecials(limiter: String) = withContext(Dispatchers.IO) {
Log.i("Resp-getSpecials","I am running on tread: $coroutineContext")
return#withContext ProductApi.retrofitService.getSpecials(limiter)
}
}
BindingAdapters:
//Load image using Glide (in Food item recycleview)
#BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView , imgUrl: String?) {
imgUrl?.let {
val imgUri = imgUrl.toUri().buildUpon().scheme("http").build()
Glide.with(imgView.context)
.load(imgUri)
.apply(
RequestOptions()
.placeholder(R.drawable.loading_animation)
.error(R.drawable.ic_broken_image))
.into(imgView)
}
}
//set raters count (in Food item recycleview)
#BindingAdapter("ratersCount")
fun bindText(txtView: TextView, number_of_raters: String?) {
number_of_raters?.let {
val ratersCount = "(${number_of_raters})"
txtView.text = ratersCount
}
}
//update the progressbar visibilty (in outer-parent recycleview)
#BindingAdapter("updateStatus")
fun ProgressBar.updateStatus(status: NetworkStatus?) {
visibility = when (status) {
NetworkStatus.LOADING -> View.VISIBLE
NetworkStatus.DONE -> View.GONE
else -> View.GONE
}
}
//Hide or view an imageview based in the network Status. When network Error, an error image
//will show (in outer-parent recycleview)
#BindingAdapter("setNoInternet")
fun ImageView.setNoInternet(status: NetworkStatus?) {
when(status) {
NetworkStatus.LOADING -> {
visibility = View.GONE
}
NetworkStatus.DONE -> {
visibility = View.GONE
}
NetworkStatus.FAILED -> {
visibility = View.VISIBLE
setImageResource(R.drawable.ic_connection_error)
}
}
}
//Submit the list of FoodCatergory item to the outer-parent recycleview
#BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, data: List<FoodCategory>?) {
(recyclerView.adapter as FoodCategoryAdapter).submitList(data)
}
//Submit list the the Food item recyclew view (child recycleView)
#BindingAdapter("setProducts")
fun RecyclerView.setProducts(foods: List<Food>?) {
if (foods != null) {
val foodAdapter = FoodItemAdapter()
foodAdapter.submitList(foods)
adapter = foodAdapter
}
}
I have a Recycleview of Food Item and a Recycleview Pool of FoodCategory. If I comment out
_foodCategory.postValue(foodCategory)
in ViewModel: getUpdatedFood() than I do not get the message. However, when I submit the list to the outer recycleview (The one the hold the viewpool), than I get this answer. Please help. I been stuck on it for a while tryna get rid of that message.
Thank you..
Updated
Below is the adapeters and its view holders
FoodItem layout
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View"/>
<variable
name="foodItem"
type="com.example.e_commerceapp.models.Food"/>
<variable
name="font"
type="com.example.e_commerceapp.products.overview.FontFamily"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/child_item_main_layout"
android:background="#drawable/search_background"
android:layout_marginTop="10dp"
android:layout_marginStart="10dp"
android:layout_marginBottom="10dp"
android:layout_width="150dp"
android:layout_height="250dp">
<ImageView
android:layout_width="120dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:id="#+id/burger_image"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_height="160dp"
/>
<!-- app:imageUrl="#{foodItem.imgSrcUrl}"-->
<TextView
android:layout_width="match_parent"
android:layout_height="34dp"
android:layout_marginStart="5dp"
android:id="#+id/burger_title"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginEnd="5dp"
android:singleLine="true"
android:textColor="#B4000000"
app:layout_constraintTop_toBottomOf="#id/burger_image"
android:text="#{foodItem.name}"
android:textSize="12sp"
android:fontFamily="#font/poppins"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="35dp"
app:layout_constraintTop_toBottomOf="#id/burger_title"
android:layout_marginEnd="5dp"
android:gravity="center"
android:id="#+id/burger_price"
android:layout_marginStart="5dp"
app:layout_constraintStart_toEndOf="#id/special_price"
android:textColor="#D0000000"/>
<!-- app:price="#{foodItem.price}"-->
<!-- app:specialPrice="#{foodItem.special_price}"-->
<!-- app:fontRes="#{foodItem.special ? font.POPPINS : font.POPPINS_BOLD}"-->
<TextView
android:layout_width="wrap_content"
android:layout_height="35dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/burger_title"
android:layout_marginEnd="5dp"
android:gravity="center_vertical"
android:layout_marginStart="5dp"
android:id="#+id/special_price"
android:textColor="#D0000000"
android:visibility="gone"
android:textStyle="normal"
android:fontFamily="#font/poppins_bold"/>
<!-- app:setSpecialPrice="#{foodItem.special_price}"-->
<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
app:layout_constraintTop_toBottomOf="#id/burger_price"
android:src="#drawable/ic_baseline_star_24"
android:visibility="#{foodItem.hasRating ? View.GONE : View.VISIBLE}"
android:id="#+id/rating_star"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="5dp"
android:layout_marginBottom="10dp"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="15dp"
android:layout_marginStart="5dp"
android:gravity="center"
android:textSize="12sp"
android:visibility="#{foodItem.hasRating ? View.GONE : View.VISIBLE}"
android:id="#+id/rating_count"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#id/burger_price"
android:text="#{foodItem.average_rating}"
android:layout_marginBottom="10dp"
app:layout_constraintStart_toEndOf="#id/rating_star"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="15dp"
android:id="#+id/number_of_raters"
android:textSize="12sp"
android:visibility="#{foodItem.hasRating ? View.GONE : View.VISIBLE}"
android:layout_marginStart="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="#id/rating_count"
app:ratersCount="#{foodItem.number_of_raters}"
android:layout_marginBottom="10dp"
app:layout_constraintTop_toBottomOf="#id/burger_price"/>
<ImageView android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="10dp"
android:layout_marginTop="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
FoodItem Adapter
class FoodItemAdapter: ListAdapter<Food ,
FoodItemAdapter.ItemFoodViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup , viewType: Int): ItemFoodViewHolder {
return ItemFoodViewHolder(
FoodItemBinding.inflate(LayoutInflater.from(parent.context),
parent, false))
}
override fun onBindViewHolder(holder: ItemFoodViewHolder , position: Int) {
val currentFood = getItem(position)
holder.bind(currentFood)
}
class ItemFoodViewHolder(private var binding: FoodItemBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(food: Food) {
binding.foodItem = food
binding.executePendingBindings()
}
}
object DiffCallback: DiffUtil.ItemCallback<Food>() {
override fun areItemsTheSame(oldItem: Food , newItem: Food): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: Food , newItem: Food): Boolean {
return oldItem.id == newItem.id
}
}
}
FoodCategory layout
<?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="foodCategory"
type="com.example.e_commerceapp.models.FoodCategory"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:background="#fff"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:id="#+id/category_title"
android:layout_marginTop="16dp"
android:text="#{foodCategory.title}"
android:textColor="#2B2A2A"
android:fontFamily="#font/poppins_bold"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/nestedRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
app:setProducts="#{foodCategory.foods}"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/category_title"
tools:itemCount="4"
tools:listitem="#layout/food_item"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
FoodCategory Adapter
class FoodCategoryAdapter: ListAdapter<FoodCategory,
FoodCategoryAdapter.CategoryFoodViewHolder>(Companion) {
private val viewPool = RecyclerView.RecycledViewPool()
override fun onCreateViewHolder(parent: ViewGroup , viewType: Int): CategoryFoodViewHolder {
return CategoryFoodViewHolder(FoodCategoryBinding.inflate(LayoutInflater.from(parent.context),
parent, false))
}
override fun onBindViewHolder(holder: CategoryFoodViewHolder , position: Int) {
val currentFoodCategory = getItem(position)
holder.bind(currentFoodCategory)
}
inner class CategoryFoodViewHolder(private var binding: FoodCategoryBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(currentFoodCategory: FoodCategory?) {
binding.foodCategory = currentFoodCategory
binding.nestedRecyclerView.setRecycledViewPool(viewPool)
binding.executePendingBindings()
}
}
companion object: DiffUtil.ItemCallback<FoodCategory>() {
override fun areItemsTheSame(oldItem: FoodCategory , newItem: FoodCategory): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: FoodCategory, newItem: FoodCategory): Boolean {
return oldItem.id == newItem.id
}
}
}
The parent recycleView
<?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"
tools:context=".products.overview.FoodOverviewFragment">
<data>
<variable
name="foodOverview"
type="com.example.e_commerceapp.products.overview.FoodOverviewViewModel"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:background="#color/grey"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:id="#+id/relative_layout"
android:layout_height="105dp"
android:elevation="8dp"
android:layout_marginBottom="5dp"
android:background="#fff"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:layout_width="200dp"
android:layout_marginTop="10dp"
android:layout_height="35dp"
android:id="#+id/logo_and_name"
android:src="#drawable/compony_logo_and_name"
android:layout_alignParentStart="true"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_marginTop="10dp"
android:id="#+id/notifications"
android:src="#drawable/ic_baseline_notifications_24"
android:layout_alignParentEnd="true"
android:paddingEnd="20dp"
android:paddingStart="20dp"/>
<TextView
android:layout_width="match_parent"
android:id="#+id/search"
android:layout_marginTop="10dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="#drawable/search_background"
android:layout_below="#id/logo_and_name"
android:gravity="center_vertical"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"
android:paddingEnd="10dp"
android:paddingStart="10dp"
android:text="#string/search_text"
tools:ignore="RtlSymmetry"
app:drawableEndCompat="#drawable/ic_baseline_search_24"/>
</RelativeLayout>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:updateStatus="#{foodOverview.status}"
app:layout_constraintTop_toBottomOf="#id/relative_layout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:id="#+id/progressbar"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="#id/relative_layout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:id="#+id/noInternetImage"/>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="0dp"
android:id="#+id/foodCategory"
android:clipToPadding="false"
tools:itemCount="4"
tools:listitem="#layout/food_category"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_height="0dp"
app:listData="#{foodOverview.foodCategory}"
app:layout_constraintTop_toBottomOf="#id/relative_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
The skipping frames likely has nothing to do with the code you posted: it sounds like a misconfiguration of RecyclerViews / Adapters to me. You'll need to post that code for it be more clear though.
However, even though what you posted likely isn't the culprit you can still optimize the coroutines code you have:
class FoodOverviewViewModel(private val foodRepository: FoodRepository): ViewModel() {
private lateinit var foodProducts: List<Food>
private val _foodCategory = MutableLiveData<List<FoodCategory>>()
val foodCategory: LiveData<List<FoodCategory>>
get() = _foodCategory
private val _status = MutableLiveData<NetworkStatus>()
val status: LiveData<NetworkStatus>
get() = _status
init {
getOverviewProducts()
}
private fun getOverviewProducts() {
viewModelScope.launch { // <------- Don't apply a custom scope here
_status.value = NetworkStatus.LOADING // <--- Don't call "postValue" here
try {
val food = getUpdatedFood() // <------ This is already using a background dispatcher
_foodCategory.value = food // <------- Emit this value here
_status.value = NetworkStatus.DONE
} catch (e: Exception) {
_status.value = NetworkStatus.FAILED
}
}
}
private suspend fun getUpdatedFood(): List<FoodCategory> { // <---- Return a value here
val limiter = 6 //Number of items I want to get from the server
val foodCategory = arrayListOf<FoodCategory>()
Log.i("getUpdatedFood","I am running on tread: $coroutineContext")
val getRecommended = foodRepository.getRecommendedFood(limiter.toString())
foodCategory += FoodCategory(id = 0.toString(), title = "Recommended for you", foods = getRecommended)
val getSpecials = foodRepository.getSpecials(limiter.toString())
foodCategory += FoodCategory(id = 1.toString(), title = "Specials", foods = getSpecials)
return foodCategories
}
}
The key ideas here:
Use viewModelScope.launch { ... } rather than applying a scope. You want everything in there that is not a coroutine to run on the main thread. See, for example, https://developer.android.com/kotlin/coroutines/coroutines-best-practices#main-safe
Return a value from getUpdatedFood so you can just update the _foodCategory value on the main thread in the viewModelScope

ImageView with Lottie animation inside layout - problem with ClickListener

I've created my custom View class:
class CustomCheckBox constructor(
context: Context,
attrs: AttributeSet? = null
) : ConstraintLayout(context, attrs), Checkable {
private var checked: Boolean = false
init {
LayoutInflater.from(context).inflate(R.layout.custom_view, this)
}
fun setCheckedAnim(check: Boolean) {
isChecked = check
animation.playAnimation()
}
fun toggleAnim(check: Boolean) {
toggle(check)
animation.playAnimation()
}
override fun setChecked(value: Boolean) {
checked = value
checkbox.isChecked = value
}
override fun isChecked(): Boolean = checked
override fun toggle() {
checked = !checked
checkbox.isChecked = checked
}
}
Layout is pretty simple:
<?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"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:background="#android:color/transparent">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="#+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
android:button="#drawable/toggle_drawable"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.airbnb.lottie.LottieAnimationView
android:id="#+id/animation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:lottie_rawRes="#raw/animation_json"
app:lottie_speed="2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:lottie_autoPlay="false"
app:lottie_loop="false"/>
</androidx.constraintlayout.widget.ConstraintLayout>
The problem I am facing is that when I set ClickListener for this View in code, the Listener doesn't work. It happens because CheckBox captures every click. If I create custom ClickListener and set is as listener for CheckBox then it works fine but the true problem is that I need this listener to be set for my main View using setOnClickListener() so the View has it, not it's component. It's because later in code I am checking if (listener != null) that's why it has to have it's own listener which will work.

BottomSheet is jumping on button clicks

I have a BottomSheet which houses a product detail card. The problem is, when I click on the + or - button on the product detail while the bottom sheet is in it's Expanded state, it jumps down.
When it is down and I click on the buttons it doesn't jump, it only happens when it is in it's Expanded (completely up) state
I have attached a GIF to show what is exactly happening
Here is the code
scan_sheet.xml
<?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:id="#+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:animateLayoutChanges="false"
android:background="#drawable/bottom_sheet_dialog_fragment"
android:orientation="vertical"
app:behavior_hideable="true"
app:behavior_peekHeight="100dp"
app:layout_behavior="studio.monotype.storedemo.BottomSheetBehavior">
<include
layout="#layout/hero_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/divider_view"
android:layout_width="match_parent"
android:layout_height="4dp"
android:layout_marginStart="24dp"
android:layout_marginTop="44dp"
android:layout_marginEnd="24dp"
android:background="#color/colorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/hero_item" />
<include
layout="#layout/related_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#+id/divider_view"
tools:layout_editor_absoluteX="0dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
ScanActivity.kt (simplified to show only what is essential)
class ScanActivity : AppCompatActivity() {
private lateinit var bottomSheet: BottomSheetBehavior<*>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scan)
setupBottomSheet()
showSheet()
}
private fun setupBottomSheet() {
bottomSheet = BottomSheetBehavior.from(bottom_sheet)
bottomSheet.isHideable = true
bottomSheet.skipCollapsed= true
bottomSheet.isDraggable = true
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
bottomSheet.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
}
#SuppressLint("SwitchIntDef")
override fun onStateChanged(sheet: View, newState: Int) {
when (newState) {
BottomSheetBehavior.STATE_HIDDEN -> {
codeScanner.startPreview()
}
}
}
})
plus_btn.setOnClickListener {
var qty= qty_tv.text.toString().toInt()
qty++
qty_tv.text =qty.toString()
}
minus_btn.setOnClickListener {
var qty= qty_tv.text.toString().toInt()
if(qty!=0)
{
qty--
}
qty_tv.text =qty.toString()
}
}
private fun showSheet() {
bottomSheet.state = BottomSheetBehavior.STATE_EXPANDED
}
}
it seems that google engineer gave correct answer
Seems like something is going on because you are setting
android:layout_gravity="bottom" on the view with the
BottomSheetBehavior. You should remove that line.
It helped on my case
Looks to me like that could be a bug in the BottomSheetBehavior? Seems like the height of the sheet isn't getting saved or restored correctly. After the button is pressed, a layout happens again which changes the height. Could you file a bug at https://issuetracker.google.com/issues/new?component=439535

livedata recyclerview with checkbox

I am implementing android architecture component to view buyers list and select one.
Here is my Buyer entity
#Entity
data class Buyer(#PrimaryKey var id: Long = 0, var name: String = "", var photo: String = "", var address: String = "",
#Ignore var isSelected: Boolean = false,
#SerializedName("last_update_time") var lastUpdateTime: Long = 0L) {
}
I have inserted and displayed it in recyclerview.
What I want is to know how can I display particular buyer is selected, when click on one buyer.
If I click on one buyer previous selected buyer must deselect.
Please help me to implement this.
EDIT
class BuyerAdapter(private var buyers: ArrayList<Buyer>, private val listener: View.OnClickListener) : RecyclerView.Adapter<BuyerViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BuyerViewHolder {
val v = LayoutInflater.from(parent.context).inflate(
R.layout.item_buyer, parent, false)
return BuyerViewHolder(v)
}
override fun onBindViewHolder(holder: BuyerViewHolder, position: Int) {
bindView(holder, position)
}
private fun bindView(holder: BuyerViewHolder, position: Int) {
val buyer = buyers[position]
holder.setName(buyer.name)
holder.setAddress(buyer.address)
holder.loadImage(ServiceHandler.BASE_URL + buyer.photo)
if (buyer.isSelected) {
holder.setCardColor(R.color.waveBlue)
holder.setNameColor(R.color.white)
holder.setAddressColor(R.color.white)
} else {
holder.setCardColor(R.color.white)
holder.setNameColor(R.color.contentGrey)
holder.setAddressColor(R.color.contentGreyDesc)
}
holder.itemView.tag = buyer
holder.itemView.setOnClickListener(listener)
}
override fun getItemCount(): Int = buyers.size
fun refresh(newBuyers: ArrayList<Buyer>) {
this.buyers = newBuyers
notifyDataSetChanged()
}
}
And here is my adapter xml item
<?xml version="1.0" encoding="utf-8"?><!--<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"--><!--xmlns:card_view="http://schemas.android.com/apk/res-auto"--><!--android:layout_width="match_parent"--><!--android:layout_height="191dp"--><!--android:paddingTop="13dp"--><!-->-->
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/buyer_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="4dp"
android:clickable="true"
card_view:cardCornerRadius="2dp"
card_view:cardElevation="4dp"
card_view:cardPreventCornerOverlap="true">
<android.support.constraint.ConstraintLayout
android:id="#+id/rlBuyerBack"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="4dp">
​
<de.hdodenhof.circleimageview.CircleImageView
android:id="#+id/ivLogo"
android:layout_width="40dp"
android:layout_height="40dp"
android:scaleType="centerCrop"
android:src="#drawable/placeholder_profile_photo"
card_view:layout_constraintBottom_toBottomOf="parent"
card_view:layout_constraintLeft_toLeftOf="parent"
card_view:layout_constraintTop_toTopOf="parent" />
<in.motiontech.wave.helper.WaveTextView
android:id="#+id/tvName"
style="#style/semiBoldFont"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="Name"
android:textColor="#color/contentGrey"
android:textSize="#dimen/tSizeHeader"
card_view:layout_constraintLeft_toRightOf="#+id/ivLogo"
card_view:layout_constraintTop_toTopOf="#+id/ivLogo" />
<in.motiontech.wave.helper.WaveTextView
android:id="#+id/tvAddress"
style="#style/regularFont"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignStart="#id/tvName"
android:layout_below="#+id/tvName"
android:layout_marginLeft="8dp"
android:layout_marginTop="2dp"
android:text="Address"
android:textColor="#color/contentGrey"
android:textSize="#dimen/tSizeDesc"
card_view:layout_constraintLeft_toRightOf="#+id/ivLogo"
card_view:layout_constraintTop_toBottomOf="#+id/tvName" />
</android.support.constraint.ConstraintLayout>
</android.support.v7.widget.CardView>
Edit 2
Here how I added data in recyclerview
viewModel.getBuyers().observe(this, Observer<List<Buyer>> {
if (it != null) {
if (it.isEmpty()) {
showProgress()
if (CommonUtils.isInNetwork(this)) {
viewModel.getBuyerList()
} else {
CommonUtils.showNoInternetDialog(this)
}
} else {
hideProgress()
buyerAdapter?.refresh(ArrayList(it))
}
}
})
What I have done is I have updated value of livedata. You can see below code:
fun selectBuyer(buyer: Buyer?) {
if (buyer == null)
return
buyers.value?.filter { it != buyer }?.forEach { it.isSelected = false }
buyers.value?.get(buyers.value!!.indexOf(buyer))?.isSelected = true
newBuyer = buyer
}
I notice there is no need to notifyupdate, as I am using observer pattern

RecyclerView Items doesn't appear until i scroll it

I'm using Recyclerview inside a Fragment Following Google's sample of MVP Android architecture and I tried to make the View part passive as possible following this article , which makes the whole Recyclerview Adapter passive of the Data Models and the presenter handles it.
Here is my code of the Fragment:
class OrderHistoryFragment : Fragment(), OrderHistoryContract.View {
lateinit var mPresenter: OrderHistoryContract.Presenter
lateinit var rvOrderHistory: RecyclerView
lateinit var orderHistoryAdapter : OrderHistoryAdapter
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val root = inflater!!.inflate(R.layout.order_history_fragment, container, false)
rvOrderHistory = root.findViewById<RecyclerView>(R.id.rvOrderHistory)
rvOrderHistory.layoutManager = LinearLayoutManager(context, LinearLayout.VERTICAL, false)
orderHistoryAdapter = OrderHistoryAdapter(mPresenter, object : HistoryItemListener {
override fun onReorder(orderHistory: OrderHistory) {
}
override fun onOpenOrder(orderHistory: OrderHistory) {
val orderIntent = Intent(activity, OrderDetailActivity::class.java)
orderIntent.putExtra("orderId", orderHistory.id)
startActivity(orderIntent)
}
})
rvOrderHistory.adapter = orderHistoryAdapter
return root
}
override fun onResume() {
super.onResume()
mPresenter.start()
}
override fun setPresenter(presenter: OrderHistoryContract.Presenter) {
mPresenter = checkNotNull<OrderHistoryContract.Presenter>(presenter)
}
override fun showLoadingIndicator(load: Boolean?) {
}
override fun updateOrdersAdapter() {
orderHistoryAdapter.notifyDataSetChanged()
}
override fun showSnackBar(Message: String) {
val parentLayout = activity.findViewById<View>(android.R.id.content)
val snackBar = Snackbar
.make(parentLayout, Message, Snackbar.LENGTH_INDEFINITE)
snackBar.setAction("Dismiss") { snackBar.dismiss() }
snackBar.setActionTextColor(Color.RED)
snackBar.show()
}
interface HistoryItemListener {
fun onReorder(orderHistory: OrderHistory)
fun onOpenOrder(orderHistory: OrderHistory)
}
companion object {
fun newInstance(): OrderHistoryFragment {
return OrderHistoryFragment()
}
}
fun OrderHistoryFragment() {
}
}
And this is my RecyclerView Adapters code
class OrderHistoryAdapter(internal var orderHistoryPresenter: OrderHistoryContract.Presenter, private val listener: OrderHistoryFragment.HistoryItemListener) : RecyclerView.Adapter<OrderHistoryAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.order_history_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
orderHistoryPresenter.onBindOrdersRow(position, holder)
holder.bReOrder!!.setOnClickListener { v -> listener.onReorder(orderHistoryPresenter.getOrderHistoryItem(position)) }
holder.cvOrderItem!!.setOnClickListener { v -> listener.onOpenOrder(orderHistoryPresenter.getOrderHistoryItem(position)) }
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemCount(): Int {
return orderHistoryPresenter.getOrdersCount()
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), OrderHistoryContract.orderRowView {
internal var ivOrderVendor: ImageView? = null
internal var tvOrderId: TextView? = null
internal var tvOrderItems: TextView? = null
internal var tvOrderDate: TextView? = null
internal var tvOrderPrice: TextView? = null
internal var bReOrder: Button? = null
internal var cvOrderItem: CardView? = null
init {
ivOrderVendor = itemView.findViewById<ImageView>(R.id.ivOrderVendor)
tvOrderId = itemView.findViewById<TextView>(R.id.tvOrderId)
tvOrderItems = itemView.findViewById<TextView>(R.id.tvOrderItems)
tvOrderDate = itemView.findViewById<TextView>(R.id.tvOrderDate)
tvOrderPrice = itemView.findViewById<TextView>(R.id.tvOrderPrice)
bReOrder = itemView.findViewById<Button>(R.id.bReOrder)
cvOrderItem = itemView.findViewById<CardView>(R.id.cvOrderItem)
}
override fun setOrderImage(url: String) {
Glide.with(itemView.context).load(url).into(ivOrderVendor!!)
}
override fun setOrderDate(orderDate: String) {
tvOrderDate!!.text = orderDate
}
override fun setOrderId(orderId: String) {
tvOrderId!!.text = orderId
}
override fun setOrderItems(orderItems: ArrayList<String>) {
val stringBuilder = StringBuilder()
for (item in orderItems) {
stringBuilder.append(item)
}
tvOrderItems!!.text = stringBuilder.toString()
}
override fun setOrderPrice(orderPrice: String) {
tvOrderPrice!!.text = R.string.price.toString() + " " + orderPrice + " " + R.string.egp
}
}
}
And Here is code of the presenter which handles the Adapter data and it's binding to the ViewHolder
class OrderHistoryPresenter internal constructor(mDataRepository: DataRepository, mOrdeHistoryView: OrderHistoryContract.View) : OrderHistoryContract.Presenter {
private val mDataRepository: DataRepository
//refrence of the View to trigger the functions after proccessing the task
private val mOrdeHistoryView: OrderHistoryContract.View
private var orderHistoryItems = ArrayList<OrderHistory>()
init {
this.mDataRepository = checkNotNull(mDataRepository, "tasksRepository cannot be null")
this.mOrdeHistoryView = checkNotNull<OrderHistoryContract.View>(mOrdeHistoryView, "tasksView cannot be null!")
mOrdeHistoryView.setPresenter(this)
}
override fun start() {
mOrdeHistoryView.showLoadingIndicator(true)
mDataRepository.getCurrentUser(object : LocalDataSource.userRequestCallback {
override fun onUserRequestSuccess(botitUser: BotitUser) {
val urlParams = HashMap<String, String>()
urlParams.put(Endpoints.USER_ID_KEY, botitUser.userId!!)
val url = Endpoints.getUrl(Endpoints.urls.ORDER_HISTORY, urlParams)
mDataRepository.buildEndPointRequest(url, " ", Endpoints.requestsType.GET, object : EndpointDataSource.RequestCallback {
override fun onRequestSuccess(Body: String) {
try {
mOrdeHistoryView.showLoadingIndicator(false)
orderHistoryItems = JSONParser.parseData(JSONParser.parsers.ORDER_HISTORY, JSONObject(Body)) as ArrayList<OrderHistory>
mOrdeHistoryView.updateOrdersAdapter()
} catch (e: JSONException) {
e.printStackTrace()
}
}
override fun onRequestError(Body: String) {
mOrdeHistoryView.showLoadingIndicator(false)
mOrdeHistoryView.showSnackBar("Cannot load data")
}
})
}
override fun onUserRequestError(Body: String) {
}
})
}
override fun refreshData() {
}
override fun getOrdersCount(): Int {
return orderHistoryItems.size
}
override fun onBindOrdersRow(position: Int, orderViewHolder: OrderHistoryContract.orderRowView) {
if (orderHistoryItems.isNotEmpty()) {
val orderHistory = orderHistoryItems[position]
// orderViewHolder.setOrderDate(orderHistory.orderDate!!)
orderViewHolder.setOrderId(orderHistory.orderId!!)
orderViewHolder.setOrderImage(orderHistory.orderImage!!)
orderViewHolder.setOrderItems(orderHistory.orderItems)
orderViewHolder.setOrderPrice(orderHistory.orderPrice!!)
}
}
override fun getOrderHistoryItem(position: Int): OrderHistory {
return orderHistoryItems[position]
}
override fun actionReOrder(ordreId: String) {
}
}
Here is the Fragment XML
<android.support.v7.widget.RecyclerView android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/rvOrderHistory"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
</android.support.v7.widget.RecyclerView>
and Here is the RecyclerView Item XML order_history_item.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.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:id="#+id/cvOrderItem"
android:layout_margin="4dp"
android:orientation="vertical">
<android.support.constraint.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:orientation="vertical"
android:padding="8dp">
<ImageView
android:id="#+id/ivOrderVendor"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/mac" />
<TextView
android:id="#+id/tvOrderId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="Order #2123"
android:textAppearance="#style/TextAppearance.AppCompat.Body2"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toRightOf="#+id/ivOrderVendor"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/tvOrderItems"
android:layout_width="242dp"
android:layout_height="35dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:text="MacDonald’s: Big Mac Beef, Big Tasty Beef. El Ezaby: Signal 2, Pantene Shampoo"
android:textAppearance="#style/TextAppearance.AppCompat.Small"
android:layout_marginRight="8dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="#+id/tvOrderId"
app:layout_constraintLeft_toRightOf="#+id/ivOrderVendor"
android:layout_marginLeft="8dp"
app:layout_constraintHorizontal_bias="0.0" />
<TextView
android:id="#+id/tvOrderDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="8dp"
android:text="03:22 PM 23/2/2017"
app:layout_constraintBottom_toTopOf="#+id/tvOrderItems"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
android:layout_marginLeft="8dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintHorizontal_bias="1.0" />
<TextView
android:id="#+id/tvOrderPrice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:text="Price: 225.50 LE"
android:textAppearance="#style/TextAppearance.AppCompat.Body2"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvOrderItems" />
<Button
android:id="#+id/bReOrder"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_margin="4dp"
android:background="#drawable/chip_accent"
android:foreground="?attr/selectableItemBackground"
android:orientation="vertical"
android:padding="8dp"
android:text="Order Again"
android:textAllCaps="false"
android:textColor="#color/colorAccent"
android:textSize="15sp"
app:layout_constraintHorizontal_bias="0.937"
app:layout_constraintLeft_toRightOf="#+id/tvOrderPrice"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvOrderItems"
tools:layout_editor_absoluteY="74dp" />
</android.support.constraint.ConstraintLayout>
</android.support.v7.widget.CardView>
The issue is that when I start the Activity showing the Fragment, the RecyclerView doesn't show the Item. It only appears if I scrolled the empty RecyclerView or by leaving the App on the foreground and go back to it again.
At the initialization the data of the Adapter is empty but onResume() I make a request which updates the data at the Presenter, Then I notifyDataChange() on the Adapter but nothing updates.
When I debugged I found that onBindViewHolder() isn't called after notifyDataChange() on the Adapter so i don't know why the notifyDataChange() doesn't notify the Adapter that the data is changed.
Anyone has an idea or any solution that might fix this issue?
You need to use runOnUiThread.
if(activity != null) {
activity!!.runOnUiThread {
root.Recycleview.adapter = Adapter(Array)
Adapter(Array).notifyDataSetChanged()
}
}
have a look at this answer
As stupid as it might sound, calling this line of code after setting data for recyclerView, helped me for this issue:
recyclerView.smoothScrollToPosition(0)
PS: technologies that I was using that may have something to do with this were: RJava, Retrofit2, NavigationUI, Fragments, LiveData, and Databinding.
EDIT:
I followed #SudoPlz comment and answer on another question and it also worked too, you have to extend RecyclerView and override requestLayout:
private boolean mRequestedLayout = false;
#SuppressLint("WrongCall")
#Override
public void requestLayout() {
super.requestLayout();
// We need to intercept this method because if we don't our children will never update
// Check https://stackoverflow.com/questions/49371866/recyclerview-wont-update-child-until-i-scroll
if (!mRequestedLayout) {
mRequestedLayout = true;
this.post(() -> {
mRequestedLayout = false;
layout(getLeft(), getTop(), getRight(), getBottom());
onLayout(false, getLeft(), getTop(), getRight(), getBottom());
});
}
}
while still, I would have preferred this to fixed after 4, 5 years, however, this was a good workaround, and you won't forget about them in your view.
I noticed in your item xml your constraintLayout height is match_parent right?
I recommend you to use it as wrap_content

Categories

Resources