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.
CodePudding user response:
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.
CodePudding user response:
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.