I am using Recycler view with multiple views types in my project. Now when i scroll up and down the recycler view, my app crashes with a Null Pointer Exception. Here is my code of Main Adapter class.
class MainFragmentAdapter(private val myContext: MainFragment, myCallBack: MainFragment): RecyclerView.Adapter<RecyclerView.ViewHolder>(){
var gold = mutableListOf<Item>()
var silver = mutableListOf<Item>()
var advertiseBanner = mutableListOf<Window>()
var category = mutableListOf<Window>()
var mRate = mutableListOf<Rate>()
var womenSection = mutableListOf<Window>()
var menSection = mutableListOf<Window>()
var myDiscountBanner : Window ?= null
var myTrendingBanner : Window ?= null
var myFestivalBanner : Window ?= null
private val viewPool = RecyclerView.RecycledViewPool()
private val methodCallBack : MethodCallBack
init {
methodCallBack = myCallBack
}
override fun getItemViewType(position: Int): Int {
return position
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when(viewType){
11-> {
val binding = NewRecyclerviewBinding.inflate(LayoutInflater.from(parent.context),parent,false)
NewRecyclerViewBindingHolder(binding)
}
10-> {
val binding = AdvertiseBinding.inflate(LayoutInflater.from(parent.context),parent,false)
AdvertiseHolder(binding)
}
9-> {
val binding = DiamondBinding.inflate(LayoutInflater.from(parent.context),parent,false)
DiamondHolder(binding)
}
8 -> {
val binding = DiamondBinding.inflate(LayoutInflater.from(parent.context),parent,false)
DiamondHolder(binding)
}
7 -> {
val binding = DiamondBinding.inflate(LayoutInflater.from(parent.context),parent,false)
DiamondHolder(binding)
}
6 -> {
val binding = DiamondBinding.inflate(LayoutInflater.from(parent.context),parent,false)
DiamondHolder(binding)
}
5->{
val binding = DiamondBinding.inflate(LayoutInflater.from(parent.context),parent,false)
DiamondHolder(binding)
}
4->{
val binding = DiamondBinding.inflate(LayoutInflater.from(parent.context),parent,false)
DiamondHolder(binding)
}
3 ->{
val binding = DiamondBinding.inflate(LayoutInflater.from(parent.context),parent,false)
DiamondHolder(binding)
}
2 ->{
val binding = JewelleryPromisesBinding.inflate(LayoutInflater.from(parent.context),parent,false)
JewelleryPromisesHolder(binding)
}
1 ->{
val binding = JewelleryPromisesBinding.inflate(LayoutInflater.from(parent.context),parent,false)
JewelleryPromisesHolder(binding)
}
else ->{
val binding = JewelleryPromisesBinding.inflate(LayoutInflater.from(parent.context),parent,false)
JewelleryPromisesHolder(binding)
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (position) {
11 ->{
with(holder as NewRecyclerViewBindingHolder){
val categoryAdapter = CategoryAdapter()
binding.newRecyclerview.apply {
adapter = categoryAdapter
setHasFixedSize(true)
setRecycledViewPool(viewPool)
}
categoryAdapter.submitList(category)
categoryAdapter.setListener(myContext)
}
}
10 -> {
with(holder as AdvertiseHolder){
val monthly = advertiseBanner
val monthlyAdapter = MonthlyAdapter(myContext.requireContext())
monthlyAdapter.monthlyList = monthly
binding.myPager.adapter = monthlyAdapter
methodCallBack.addDotsIndicator(0,binding.linearDot,monthly.size)
binding.myPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener{
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
}
override fun onPageSelected(position: Int) {
methodCallBack.addDotsIndicator(
position,
binding.linearDot,
monthly.size
)
}
override fun onPageScrollStateChanged(state: Int) {
}
})
}
}
9-> {
with(holder as DiamondHolder){
if (gold.isNotEmpty()){
val goldAdapter = ProductAdapter(myContext)
goldAdapter.setListener(myContext)
if (mRate.isNotEmpty())
goldAdapter.rate = mRate[0].value!!
binding.diamondText.text = myContext.getText(R.string.gold)
binding.diamondRecycler.apply {
setHasFixedSize(true)
adapter = goldAdapter
setRecycledViewPool(viewPool)
}
goldAdapter.submitList(gold)
}
binding.viewMoreButton.setOnClickListener {
methodCallBack.click(gold)
}
}
}
8 -> {
with(holder as DiamondHolder){
binding.diamondText.visibility = View.GONE
binding.containerName.visibility = View.GONE
binding.diamondRecycler.visibility = View.GONE
binding.viewMoreButton.visibility = View.GONE
binding.showAll.visibility = View.GONE
binding.jewelleryBanner.visibility = View.VISIBLE
binding.bannerImage.loadImage(myDiscountBanner?.url)
}
}
7->{
with(holder as DiamondHolder){
if (womenSection.isNotEmpty()){
val genderAdapter = SectionAdapter()
genderAdapter.genderType = "women"
binding.viewMoreButton.visibility = View.GONE
binding.containerName.text = myContext.getText(R.string.women_section)
binding.containerName.visibility = View.VISIBLE
binding.showAll.visibility = View.VISIBLE
binding.diamondRecycler.apply {
layoutManager = GridLayoutManager(myContext.requireContext(), 3)
adapter = genderAdapter
setHasFixedSize(true)
setRecycledViewPool(viewPool)
}
genderAdapter.submitList(womenSection)
genderAdapter.setListener(myContext)
}
}
}
6 -> {
with(holder as DiamondHolder){
binding.diamondText.visibility = View.GONE
binding.containerName.visibility = View.GONE
binding.diamondRecycler.visibility = View.GONE
binding.viewMoreButton.visibility = View.GONE
binding.showAll.visibility = View.GONE
binding.jewelleryBanner.visibility = View.VISIBLE
binding.bannerImage.loadImage(myTrendingBanner?.url)
}
}
5->{
with(holder as DiamondHolder){
if (menSection.isNotEmpty()){
val genderAdapter = SectionAdapter()
genderAdapter.genderType = "men"
binding.viewMoreButton.visibility = View.GONE
binding.containerName.text = myContext.getText(R.string.men_section)
binding.containerName.visibility = View.VISIBLE
binding.showAll.visibility = View.VISIBLE
binding.diamondRecycler.apply {
layoutManager = GridLayoutManager(myContext.requireContext(), 3)
adapter = genderAdapter
setHasFixedSize(true)
setRecycledViewPool(viewPool)
}
genderAdapter.submitList(menSection)
genderAdapter.setListener(myContext)
}
}
}
4 -> {
with(holder as DiamondHolder){
binding.diamondText.visibility = View.GONE
binding.containerName.visibility = View.GONE
binding.diamondRecycler.visibility = View.GONE
binding.viewMoreButton.visibility = View.GONE
binding.showAll.visibility = View.GONE
binding.jewelleryBanner.visibility = View.VISIBLE
binding.bannerImage.loadImage(myFestivalBanner?.url)
}
}
3->{
with(holder as DiamondHolder){
if (silver.isNotEmpty()){
val goldAdapter = ProductAdapter(myContext)
goldAdapter.setListener(myContext)
if (mRate.isNotEmpty())
goldAdapter.rate = mRate[1].value!!
binding.diamondText.text = myContext.getText(R.string.silver)
binding.diamondRecycler.apply {
setHasFixedSize(true)
adapter = goldAdapter
setRecycledViewPool(viewPool)
}
goldAdapter.submitList(silver)
}
binding.viewMoreButton.setOnClickListener {
methodCallBack.click(silver)
}
}
}
2->{
with(holder as JewelleryPromisesHolder){
binding.headerTitle.visibility = View.VISIBLE
binding.table.visibility = View.VISIBLE
binding.socialFeed.visibility = View.GONE
binding.instagramIcon.visibility = View.GONE
binding.whatsappIcon.visibility = View.GONE
binding.youtubeIcon.visibility = View.GONE
binding.facebookIcon.visibility = View.GONE
}
}
1 ->{
with(holder as JewelleryPromisesHolder){
binding.headerTitle.visibility = View.GONE
binding.table.visibility = View.GONE
binding.socialFeed.visibility = View.VISIBLE
binding.instagramIcon.visibility = View.VISIBLE
binding.whatsappIcon.visibility = View.VISIBLE
binding.youtubeIcon.visibility = View.VISIBLE
binding.facebookIcon.visibility = View.VISIBLE
}
}
else -> {
with(holder as JewelleryPromisesHolder){
binding.headerTitle.visibility = View.GONE
binding.table.visibility = View.GONE
binding.socialFeed.visibility = View.GONE
binding.instagramIcon.visibility = View.GONE
binding.whatsappIcon.visibility = View.GONE
binding.youtubeIcon.visibility = View.GONE
binding.facebookIcon.visibility = View.GONE
binding.whatsappBanner.visibility = View.VISIBLE
}
}
}
}
override fun getItemCount() = 12
inner class DiamondHolder (val binding : DiamondBinding) : RecyclerView.ViewHolder(binding.root)
inner class NewRecyclerViewBindingHolder (val binding : NewRecyclerviewBinding): RecyclerView.ViewHolder(binding.root)
inner class AdvertiseHolder(val binding : AdvertiseBinding) : RecyclerView.ViewHolder(binding.root)
inner class JewelleryPromisesHolder(val binding : JewelleryPromisesBinding) : RecyclerView.ViewHolder(binding.root)
interface MethodCallBack{
fun click(productList : List<Item>)
fun click()
fun addDotsIndicator(position: Int, linearDot: LinearLayout, size: Int)
}
}
Here are my Adapter Classes codes :-
Category Adapter
class CategoryAdapter : RecyclerView.Adapter<CategoryAdapter.HorizontalHolder>() {
private var oldList = mutableListOf<Window>()
private var childListener : CategoryInterface ?= null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HorizontalHolder {
val binding = HorizontalCategoryBinding.inflate(LayoutInflater.from(parent.context),parent,false)
return HorizontalHolder(binding)
}
override fun onBindViewHolder(holder: HorizontalHolder, position: Int) {
with(holder){
if(oldList.isNotEmpty()) {
with(oldList[position]) {
binding.categoryImage.loadImage(this.url)
binding.categoryName.text = this.sub_type
binding.categoryImage.setOnClickListener {
childListener?.onClick(this.sub_type!!, "all")
}
}
}
}
}
fun setListener(listener : MainFragment){
childListener = listener
}
fun submitList(newList : MutableList<Window>){
val diffUtil = WindowDiffUtil(oldList ,newList)
val diffResult = DiffUtil.calculateDiff(diffUtil)
oldList = newList
diffResult.dispatchUpdatesTo(this)
}
override fun getItemCount() = oldList.size
inner class HorizontalHolder(val binding : HorizontalCategoryBinding) : RecyclerView.ViewHolder(binding.root)
}
Product Adapter
class ProductAdapter(private val context : MainFragment) : RecyclerView.Adapter<ProductAdapter.ProductHolder>(){
var itemList = mutableListOf<Item>()
var rate = 0
private var childListener : ChildInterface ?= null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductHolder {
val binding = SilverCardsBinding.inflate(LayoutInflater.from(parent.context),parent,false)
return ProductHolder(binding)
}
override fun onBindViewHolder(holder: ProductHolder, position: Int) {
with(holder){
if (itemList.isNotEmpty()) {
with(itemList[position]) {
if (rate != 0) {
binding.productImage.loadImage(this.images?.get(0))
binding.itemName.text = this.name
var weight = this.net_weight
if (weight != null) {
weight *= (rate)
}
val newWeight = weight?.toInt()?.plus(this.labour!!)
binding.price.text =
context.resources.getString(R.string.Rs, newWeight?.toString())
holder.itemView.setOnClickListener {
childListener?.onClick(this, "SelectedProduct")
}
}
}
}
}
}
fun submitList(newList : MutableList<Item>){
val diffUtil = ItemDiffUtil(itemList ,newList)
val diffResult = DiffUtil.calculateDiff(diffUtil)
itemList = newList
diffResult.dispatchUpdatesTo(this)
}
fun setListener(listener : MainFragment){
childListener = listener
}
override fun getItemCount() : Int {
return itemList.size
}
inner class ProductHolder(val binding : SilverCardsBinding): RecyclerView.ViewHolder(binding.root)
}
SectionAdapter
class SectionAdapter : RecyclerView.Adapter<SectionAdapter.SectionHolder>(){
private var oldList = mutableListOf<Window>()
var genderType : String ?= null
private var childListener : CategoryInterface?= null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SectionHolder {
val binding = SectionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return SectionHolder(binding)
}
override fun onBindViewHolder(holder: SectionHolder, position: Int) {
if (oldList.isNotEmpty()) {
with(holder){
with(oldList[position]) {
binding.sectionImage.loadImage(this.url)
binding.sectionName.text = this.sub_type
holder.itemView.setOnClickListener {
childListener?.onClick(this.sub_type!!, genderType!!)
}
}
}
}
}
override fun getItemCount(): Int {
return oldList.size
}
fun setListener(listener : MainFragment){
childListener = listener
}
fun submitList(newList : MutableList<Window>){
val diffUtil = WindowDiffUtil(oldList ,newList)
val diffResult = DiffUtil.calculateDiff(diffUtil)
oldList = newList
diffResult.dispatchUpdatesTo(this)
}
inner class SectionHolder (val binding : SectionBinding) : RecyclerView.ViewHolder(binding.root){
}
}
Now when I scroll fast, I get Null Pointer Exception. These are the exceptions.
java.lang.ClassCastException: com.example.groceryapp.mainscreen.CategoryAdapter$HorizontalHolder cannot be cast to com.example.groceryapp.mainscreen.SectionAdapter$SectionHolder
at com.example.groceryapp.mainscreen.SectionAdapter.onBindViewHolder(SectionAdapter.kt:14)
at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7254)
at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7337)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6194)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6460)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6300)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6296)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2330)
at androidx.recyclerview.widget.GridLayoutManager.layoutChunk(GridLayoutManager.java:572)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1591)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:668)
at androidx.recyclerview.widget.GridLayoutManager.onLayoutChildren(GridLayoutManager.java:170)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4309)
at androidx.recyclerview.widget.RecyclerView.onMeasure(RecyclerView.java:3686)
at android.view.View.measure(View.java:24530)
at androidx.constraintlayout.widget.ConstraintLayout$Measurer.measure(ConstraintLayout.java:811)
at androidx.constraintlayout.core.widgets.ConstraintWidgetContainer.measure(ConstraintWidgetContainer.java:632)
at androidx.constraintlayout.core.widgets.analyzer.Direct.horizontalSolvingPass(Direct.java:323)
at androidx.constraintlayout.core.widgets.analyzer.Direct.solveHorizontalMatchConstraint(Direct.java:709)
at androidx.constraintlayout.core.widgets.analyzer.Direct.horizontalSolvingPass(Direct.java:374)
at androidx.constraintlayout.core.widgets.analyzer.Direct.solvingPass(Direct.java:144)
at androidx.constraintlayout.core.widgets.ConstraintWidgetContainer.layout(ConstraintWidgetContainer.java:693)
at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.solveLinearSystem(BasicMeasure.java:160)
at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.solverMeasure(BasicMeasure.java:291)
at androidx.constraintlayout.core.widgets.ConstraintWidgetContainer.measure(ConstraintWidgetContainer.java:120)
at androidx.constraintlayout.widget.ConstraintLayout.resolveSystem(ConstraintLayout.java:1594)
at androidx.constraintlayout.widget.ConstraintLayout.onMeasure(ConstraintLayout.java:1708)
at android.view.View.measure(View.java:24530)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.measureChildWithMargins(RecyclerView.java:9681)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1657)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1591)
at androidx.recyclerview.widget.LinearLayoutManager.scrollBy(LinearLayoutManager.java:1395)
at androidx.recyclerview.widget.LinearLayoutManager.scrollVerticallyBy(LinearLayoutManager.java:1136)
at androidx.recyclerview.widget.RecyclerView.scrollStep(RecyclerView.java:1972)
at androidx.recyclerview.widget.RecyclerView.scrollByInternal(RecyclerView.java:2071)
at androidx.recyclerview.widget.RecyclerView.onTouchEvent(RecyclerView.java:3531)
at android.view.View.dispatchTouchEvent(View.java:13415)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3054)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2741)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3060)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2755)
2022-10-25 22:09:51.133 26193-26193 AndroidRuntime com.example.groceryapp E at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3060)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2755)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3060)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2755)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3060)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2755)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3060)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2755)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3060)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2755)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3060)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2755)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3060)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2755)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:465)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1849)
.....
This is why it's crashing:
java.lang.NullPointerException: holder.itemView.category_image must not be null
at com.example.groceryapp.mainscreen.SectionAdapter.onBindViewHolder(SectionAdapter.kt:27)
At line 27 in onBindViewHolder
in your SectionAdapter
class, you're referencing holder.itemView.category_image
, which is null. It's crashing because you're trying to do something with it, probably where you try to call loadImage()
on it (and null doesn't have a loadImage
function).
Since you're just accessing IDs directly on the View
(i.e. itemView.someId
) instead of using findViewById
or View Binding, I'm assuming you're using the (deprecated) Android Extensions (you have something like kotlinx.android.synthetic.
in your imports).
That lets you "magically" access views by their ID without having to actually look them up first. It also means that if there is no view with that ID in the layout, you won't get warned about it until it fails at runtime. So check that R.layout.section
actually has a... whatever widget you're using that has a loadImage
function, with an ID of category_image
.
If you do have something with that ID in there, and it really does work fine if you scroll slowly, maybe it's a flaw with how the "magical" synthetic binding works - I'm not sure if it would be an issue, but it is a flawed system and that's why they moved away from it. And consider moving to View Binding instead. There's a migration guide here