Can a Kotlin entity with a constructor that has default non null values become null at runtime? - android

I have spent the last couple of weeks trying to rewrite an android app from a java to kotlin, from custom fragment navigation to navigation component and all other Jetpack bells and whistles.
Now I've encountered several bugs through this process but there's this specific one. I have a kotlin class with a default constructor as shown below
#Entity(tableName = Globals.FIREBASE_ITEM_NODE)
#Parcelize
class Item(
#PrimaryKey(autoGenerate = true)
var id: Int = 0 ,
var imageUri: String = "",
var isRead: Boolean = false,
var expanded: Boolean = false,
var favourite: Boolean = false,
var isSaved: Boolean = false,
var englishWord: String = "",
var topic: String = "",
var audioUri: String = "",
var rutooroWord: String = "",
var firebaseImageNode: String = "",
) : Parcelable
This is because I fetch data from firebase rtdb and cache it in room. I then collect a flow of this data submit it to a List Adapter and use databinding to bind it to my views.
the item Viewholder
inner class ItemViewHolder(private val b: RvLangItemBinding) : RecyclerView.ViewHolder(b.root) {
fun bind(position: Int) {
if (getItem(position) !is Item) return
b.item = getItem(position) as Item
if ((getItem(position) as Item).expanded) createPalette(
(getItem(position) as Item).imageUri,
b.parent,
b.tvEnglish,
b.tvRutooro
)
else b.parent.setBackgroundColor(
itemView.context.resources.getColor(
R.color.transparent,
itemView.context.theme
)
)
b.root.setOnClickListener {
(getItem(position) as Item).expanded = !(getItem(position) as Item).expanded
notifyItemChanged(position)
if (prevPosition != INITIAL_POSITION && prevPosition != position) {
(getItem(prevPosition) as Item).expanded = false
notifyItemChanged(prevPosition)
}
prevPosition = position
if ((getItem(position) as Item).expanded && (getItem(position) as Item).audioUri.isNotEmpty())
AudioUtil.playAudioFile(
itemView.context,
Uri.parse((getItem(position) as Item).audioUri)
)
}
b.favourite.setOnLikeListener(object : OnLikeListener {
override fun liked(likeButton: LikeButton) {
(getItem(position) as Item).favourite = true
onItem.update(getItem(position) as Item)
}
override fun unLiked(likeButton: LikeButton) {
(getItem(position) as Item).favourite = false
onItem.update(getItem(position) as Item)
}
})
b.audioButton.setOnClickListener {
if ((getItem(position) as Item).audioUri.isNotEmpty())
AudioUtil.playAudioFile(
itemView.context,
Uri.parse((getItem(position) as Item).audioUri)
)
}
}
}
and this is the xml for the item
<?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>
<import type="android.view.View" />
<variable
name="item"
type="com.allez.san.learnrutooro.models.Item" />
</data>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="18dp"
android:elevation="4dp"
android:padding="8dp"
app:cardCornerRadius="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="#+id/tv_english"
style="#style/TextAppearance.Material3.TitleMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="#{item.englishWord}"
android:textSize="16sp"
app:layout_constraintEnd_toStartOf="#id/favourite"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Good Morning" />
<com.like.LikeButton
android:id="#+id/favourite"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="4dp"
app:icon_size="25dp"
app:icon_type="star"
app:liked="#{item.favourite}"
app:layout_constraintBottom_toBottomOf="#+id/tv_english"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="#+id/tv_english"
app:like_drawable="#drawable/ic_star_green"
app:unlike_drawable="#drawable/ic_star_white" />
<RelativeLayout
android:id="#+id/relativeLayout"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="4dp"
android:background="#android:color/darker_gray"
app:layout_constraintBottom_toBottomOf="#id/downArrow"
app:layout_constraintEnd_toStartOf="#id/downArrow"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#id/downArrow" />
<com.google.android.material.imageview.ShapeableImageView
android:id="#+id/downArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:src="#drawable/ic_arrow_drop_down"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/favourite" />
<com.google.android.material.textview.MaterialTextView
android:id="#+id/tv_rutooro"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:text="#{item.rutooroWord}"
android:textSize="16sp"
android:visibility="#{item.expanded? View.VISIBLE:View.GONE}"
app:layout_constraintBottom_toTopOf="#id/item_image"
app:layout_goneMarginBottom="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/downArrow"
tools:text="oraire ota" />
<com.google.android.material.imageview.ShapeableImageView
android:id="#+id/item_image"
setImage="#{item.imageUri}"
setImageItemVisibility="#{item}"
android:layout_width="270dp"
android:layout_height="200dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/tv_rutooro"
app:layout_goneMarginBottom="16dp"
tools:src="#drawable/lr_logo_light" />
<com.google.android.material.imageview.ShapeableImageView
android:id="#+id/audio_button"
android:layout_width="wrap_content"
android:layout_height="32dp"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
android:src="#drawable/ic_audio"
android:visibility="#{item.expanded? View.VISIBLE:View.GONE}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/downArrow"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</layout>
and these are my binding adapters
#BindingAdapter("setImage")
fun setImage(view:ImageView, uri: String)=
Glide.with(view).load(uri).into(view)
#BindingAdapter("setImageItemVisibility")
fun setItemImageVisibility(view:ImageView, item: Item){
view.visibility = if(item.expanded && item.imageUri.isNotEmpty()) View.VISIBLE else View.GONE
}
and this is the error I've been getting.
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.allez.san.myapplication, PID: 26721
java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter item
at com.allez.san.learnrutooro.utils.BindingUtilsKt.setItemImageVisibility(Unknown Source:7)
at com.allez.san.learnrutooro.databinding.RvLangItemBindingImpl.executeBindings(RvLangItemBindingImpl.java:152)
at androidx.databinding.ViewDataBinding.executeBindingsInternal(ViewDataBinding.java:512)
at androidx.databinding.ViewDataBinding.executePendingBindings(ViewDataBinding.java:484)
at androidx.databinding.ViewDataBinding$7.run(ViewDataBinding.java:218)
at androidx.databinding.ViewDataBinding$8.doFrame(ViewDataBinding.java:320)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1106)
at android.view.Choreographer.doCallbacks(Choreographer.java:866)
at android.view.Choreographer.doFrame(Choreographer.java:792)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1092)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
I/Process: Sending signal. PID: 26721 SIG: 9
How is this possible and why??? I've been at it for a while now. reading as much as I could about the subject but no luck yet.
Any help will be appreciated. thanx in advance.

try to change the fields type to nullable and run it again, if you did not have the same error then the problem is that you defined entity all its fields are non-nullable, so when you are calling #Query("SELECT * FROM items") fun getAllItems(): Flow<List<Item>> you are trying to give a null value from database to one of the fields.

I think the problem was with my binding adapters
#BindingAdapter("setImage")
fun setImage(view: ImageView, uri: String) =
Glide.with(view).load(uri).into(view)
#BindingAdapter("setImageItemVisibility")
fun setItemImageVisibility(view: ImageView, item: Item) {
view.visibility = if (item.expanded && item.imageUri.isNotEmpty()) View.VISIBLE else View.GONE
}
Because when i switched to directly binding the image and setting the image visibility in the item viewholder everything is working just fine.
Glide.with(itemView).load((getItem(position) as Item).imageUri).into(b.itemImage)
b.itemImage.visibility = if ((getItem(position) as Item).expanded && (getItem(position) as Item).imageUri.isNotEmpty()) View.VISIBLE else View.GONE
I don't think this is a solution to that error coz I still don't know why I was getting it and I'd like to still use databinding to set the image and its visibility. If anyone has an explanation as to why this was happening I'd really appreciate it.

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

Data binding + LiveData is not working with complex nested objects

I have encountered some unexpected behaviour with LiveData and data binding libraries.
I had implemented CustomLiveData as in this answer https://stackoverflow.com/a/48194074/13321296, so I just can call notifyChange() inside parent class to update UI.
I have parent object(some methods omitted for brevity):
class Day(val tasks: MutableList<RunningTask>,
state: DayState = DayState.WAITING,
var dayStartTime: Long = 0L,
currentTaskPos: Int = 0): BaseObservable() {
var state: DayState = state
set(value) {
field = value
notifyChange()
}
var currentTaskPos: Int = currentTaskPos
set(value) {
field = value
notifyChange()
}
fun start() {
dayStartTime = System.currentTimeMillis()
state = DayState.ACTIVE
resetTasks()
tasks[currentTaskPos].start()
notifyChange()
}
}
Child object:
class RunningTask(
startTime: Long,
var name: String = "",
private val originalDuration: Long = 0L,
val sound: String
): BaseObservable() {
var startTime: Long = startTime
set(value) {
field = value
uiStartTime = convertMillisToStringFormat(value)
}
#Bindable
var uiStartTime: String = convertMillisToStringFormat(startTime)
set(value) {
field = value
notifyPropertyChanged(BR.uiStartTime)
}
var duration: Long = originalDuration
set(value) {
field = value
}
var state: State = State.WAITING
var progress: Long = 0L
set(value) {
field = value
}
var timePaused: Long = 0L
var timeRemain: String = convertMillisToStringFormat(duration)
enum class State {
WAITING, ACTIVE, COMPLETED, DISABLED
}
fun start() {
state = State.ACTIVE
}
}
The problem is what data binding from item_main_screen_task.xml is not updated when I change items inside of Day's tasks field, e.g. calling method start(), but other fields, such as state, do update correctly, so I guess the problem is with list inside of it.
fragment_main_screen.xml, recyclerview is populated with Day class field tasks:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View"/>
<variable
name="viewmodel"
type="com.sillyapps.meantime.ui.mainscreen.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorPrimary">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/tasks"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:visibility="#{viewmodel.noTemplate ? View.GONE : View.VISIBLE}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toTopOf="#+id/play_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/constraintLayout"
tools:listitem="#layout/item_main_screen_task"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
item_main_screen_task, taskState attribute is just basically BindingAdapter what sets background drawable according to Day's state enum:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="task"
type="com.sillyapps.meantime.data.RunningTask" />
<variable
name="taskAdapterPosition"
type="Integer" />
<variable
name="clickListener"
type="com.sillyapps.meantime.ui.ItemClickListener" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:taskState="#{task.state}"
android:onClick="#{() -> clickListener.onClickItem(taskAdapterPosition)}">
<TextView
android:id="#+id/time"
style="#style/TimeItemStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:text="#{task.uiStartTime}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/enter_name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="17:00" />
<TextView
android:id="#+id/enter_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:text="#{task.name}"
app:layout_constraintBottom_toBottomOf="#+id/time"
app:layout_constraintEnd_toStartOf="#+id/progress"
app:layout_constraintStart_toEndOf="#+id/time"
app:layout_constraintTop_toTopOf="#+id/time"
tools:text="Свободное время" />
<TextView
android:id="#+id/progress"
style="#style/TimeItemStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="#{task.timeRemain}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/enter_name"
app:layout_constraintTop_toTopOf="#+id/time"
tools:text="01:00" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Thanks in advance.
Turns out that solution was very simple, but somewhat unexpected
The child class should extend BaseObservable, and call notifyChange() on setters of every data-binded fields, something like that:
class RunningTask(
startTime: Long,
var name: String = "",
private val originalDuration: Long = 0L,
val sound: String
): BaseObservable() {
var startTime: Long = startTime
set(value) {
field = value
uiStartTime = convertMillisToStringFormat(value)
}
#Bindable
var uiStartTime: String = convertMillisToStringFormat(startTime)
set(value) {
field = value
notifyPropertyChanged(BR.uiStartTime)
}
var state: State = State.WAITING
set(value) {
field = value
notifyChange()
}
...
}
Appears that I'd already implemented this in uiStartTime before coming up with question, but I just didn't know exact reason why it's worked

How to disable a button in Kotlin?

I have created 2 activities, well the first one I use it as splash screen, now in the second one I have difficulties when deactivating a button, I leave the code for your understanding
activity_main2.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".TemaCalcActivity">
<Spinner
android:layout_width="144dp"
android:layout_height="39dp"
android:id="#+id/spnDiferencia" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="8dp" app:layout_constraintStart_toEndOf="#+id/spnMP"
android:layout_marginEnd="8dp" android:layout_marginTop="24dp"
app:layout_constraintTop_toBottomOf="#+id/textView5"/>
<Button
android:text="OK"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="#+id/button" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="120dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="120dp" android:layout_marginTop="32dp"
app:layout_constraintTop_toBottomOf="#+id/spnMP" android:background="#drawable/button_effect"
android:textSize="18sp" android:textStyle="bold" android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintVertical_bias="0.026" android:onClick="onBotonFinalizar" android:enabled="false"/>
</android.support.constraint.ConstraintLayout>
Main2Activity.kt
class Main2Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
spnDiferencia.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
when (position) {
0 -> { button.isEnabled = true}
1 -> { button.isEnabled = false}
}
}
The error I have is that the entire application is stopped and then restarted, making this a repetition.
button.isEnabled = true
// or
button.isClickable = true
caution ---> button == btnCalcular
--- error log ---
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.mrl.fr.tuto, PID: 22066
java.lang.IllegalStateException: btnCalcular must not be null
at com.mrl.fr.tuto.Main2Activity$onCreate$5.onItemSelected(Main2cActivity.kt:128)
at android.widget.AdapterView.fireOnSelected(AdapterView.java:919)
at android.widget.AdapterView.dispatchOnItemSelected(AdapterView.java:908)
at android.widget.AdapterView.access$300(AdapterView.java:53)
at android.widget.AdapterView$SelectionNotifier.run(AdapterView.java:878)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:194)
at android.app.ActivityThread.main(ActivityThread.java:5631)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:959)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:754)
Just to disable a button .
button.isEnabled = false
button.isClickable = false
and If you want to disable a button , and want to grey out its color & background ,you can do something like this -
fun markButtonDisable(button: Button) {
button?.isEnabled = false
button?.setTextColor(ContextCompat.getColor(textView.context, R.color.white))
button?.setBackgroundColor(ContextCompat.getColor(textView.context, R.color.greyish))
}
Did you try this
myButton.setEnabled(false);

A Kotlin property is in looping when getting the value ($field)

When I bind the value in the 'name' property in xml, the getter seems to be in a loop and the value inside it, is concatenating in the screen until I stop the app.
1 - I don't know with sure yet if I need to use notifyPropertyChanged() or the anotations #set and #get;
2 - If I set the get without the concatenating string, it's works nicelly: get() = field;
3 - If I try to return the get value inside braces, the problem keeps to occour: get(){return "Field: $field"};
This is the model:
class ContactModel : BaseObservable(){
#set:Bindable
#get:Bindable
var name: String = ""
get() = "Field: $field"
set(value) {
field = value
notifyPropertyChanged(BR.name)
}
#set:Bindable
#get:Bindable
var email: String = ""
set(value) {
field = value
notifyPropertyChanged(BR.email)
}
#set:Bindable
#get:Bindable
var phone: String = ""
set(value) {
field = value
notifyPropertyChanged(BR.phone)
}
}
Here's the activity:
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
var contactModel: ContactModel = ContactModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
contactModel = ContactModel(/*"Rômulo", "romulocoviel#gmail.com", "(19):98421-0821"*/)
contactModel.name = "Rômulo"
contactModel.email = "romulocoviel#gmail.com"
contactModel.phone = "(19):98421-0821"
binding.contactModel = contactModel
binding.setLifecycleOwner(this)
}
fun changeSignatures(view: View) {
Log.e("TESTING", "Testando!" + contactModel.name)
val nameList: ArrayList<ContactModel> = ArrayList()
contactModel.name = "asdasd"
contactModel.email = "asdasda"
contactModel.phone = "asdasd"
}
}
And here's the XML that I have a button that changes the values when tapped and the binding views:
<data>
<variable
name="contactModel"
type="com.example.romulo.bindingmetricsconversor.ContactModel"/>
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:text="#={contactModel.name}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/tvName" android:layout_marginTop="8dp"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"
android:layout_marginLeft="8dp" android:layout_marginStart="8dp"/>
<TextView
android:text="#={contactModel.email}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/tvEmail" android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="#+id/tvName" app:layout_constraintStart_toStartOf="parent"
android:layout_marginLeft="8dp" android:layout_marginStart="8dp"/>
<TextView
android:text="#={contactModel.phone}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/tvPhone" android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="#+id/tvEmail" app:layout_constraintStart_toStartOf="parent"
android:layout_marginLeft="8dp" android:layout_marginStart="8dp"/>
<Button
android:text="Change"
android:layout_width="wrap_content"
android:layout_height="49dp"
android:id="#+id/btChange" android:layout_marginTop="8dp"
app:layout_constraintTop_toTopOf="parent" android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="8dp" android:layout_marginRight="8dp"
app:layout_constraintStart_toStartOf="parent" android:layout_marginLeft="8dp"
android:layout_marginStart="8dp" android:onClick="changeSignatures"/>
</android.support.constraint.ConstraintLayout>
The result in the screen always is:
Field: asdasd
Field:Field: asdasd
Field:Field: asdasd
Field:Field:Field: asdasd
Field:Field:Field:Field: asdasd
... to the infinite
Just for the sake of completeness:
it seems like whenever the text view is updated by the property change listener it detects a change in its own content and thus tries to save back to the observable, triggering a loop, since you're using two-way binding.
The problem can be solved by using one-way binding instead (#{}), as upon changing its text the text view would trigger its own listeners and attempt to modify the observable, sending it into an infinite recursion.

DSL-like OnClick-listener for a custom view using kotlin in 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)
}
}
}

Categories

Resources