3

My app crashes when I attempt to click on articles published days ago, however it works fine when I tried to do it on a more recent article, here is an image for reference.

enter image description here

The app crashes when I scroll down and click on past news, the app also seems to be quite laggy and unresponsive, would appreciate any advice.

The error seems to lie on the Onclicklistener portion of the code, I uploaded all the codes which I think are relevant.

Runtime error

From the errors below, the classes highlighted were the BreakingNews and the NewsAdapter.

java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.Object.hashCode()' on a null object reference
    at com.example.thenewsapplication.ROOM.Source.hashCode(Unknown Source:2)
    at com.example.thenewsapplication.ROOM.Article.hashCode(Unknown Source:71)
    at androidx.navigation.NavBackStackEntry.hashCode(NavBackStackEntry.kt:256)
    at java.util.HashMap.hash(HashMap.java:338)
    at java.util.HashMap.put(HashMap.java:611)
    at androidx.navigation.NavController.linkChildToParent(NavController.kt:143)
    at androidx.navigation.NavController.addEntryToBackStack(NavController.kt:1918)
    at androidx.navigation.NavController.addEntryToBackStack$default(NavController.kt:1813)
    at androidx.navigation.NavController$navigate$4.invoke(NavController.kt:1721)
    at androidx.navigation.NavController$navigate$4.invoke(NavController.kt:1719)
    at androidx.navigation.NavController$NavControllerNavigatorState.push(NavController.kt:287)
    at androidx.navigation.fragment.FragmentNavigator.navigate(FragmentNavigator.kt:198)
    at androidx.navigation.fragment.FragmentNavigator.navigate(FragmentNavigator.kt:164)
    at androidx.navigation.NavController.navigateInternal(NavController.kt:260)
    at androidx.navigation.NavController.navigate(NavController.kt:1719)
    at androidx.navigation.NavController.navigate(NavController.kt:1545)
    at androidx.navigation.NavController.navigate(NavController.kt:1472)
    at androidx.navigation.NavController.navigate(NavController.kt:1454)
    at com.example.thenewsapplication.Fragments.BreakingNews$onViewCreated$1.invoke(BreakingNews.kt:57)
    at com.example.thenewsapplication.Fragments.BreakingNews$onViewCreated$1.invoke(BreakingNews.kt:53)
    at com.example.thenewsapplication.Adapter.NewsAdapter.onBindViewHolder$lambda-2$lambda-1(NewsAdapter.kt:82)
    at com.example.thenewsapplication.Adapter.NewsAdapter.$r8$lambda$xRXjhIuiNyf8fdAGPo8jTchti_k(Unknown Source:0)
    at com.example.thenewsapplication.Adapter.NewsAdapter$$ExternalSyntheticLambda0.onClick(Unknown Source:4)
    at android.view.View.performClick(View.java:7448)
    at android.view.View.performClickInternal(View.java:7425)
    at android.view.View.access$3600(View.java:810)
    at android.view.View$PerformClick.run(View.java:28305)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:223)
    at android.app.ActivityThread.main(ActivityThread.java:7656)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

Article class (The article itself) I extended the serialization class so I can pass info between fragments via Bundle

data class Article(
@PrimaryKey(autoGenerate = true)
var id: Int? =null,
val author: String?,
val content: String?,
val description: String?,
val publishedAt: String?,
val source: Source?,
val title: String?,
val url: String,
val urlToImage: String?
) : Serializable

Source (for the source datatype in the Article data class)

data class Source(
val id: Any,
val name: String
)

TypeConverters (Convert the Source datatype to a primative type)

class Converters {

@TypeConverter
fun fromSource(source: Source): String{
    return source.name
}

@TypeConverter
fun toSource(name: String): Source {
    return Source(name, name)
}

}

Adapter (Adapter for the BreakingNews class RecyclerView)

class NewsAdapter: RecyclerView.Adapter<NewsAdapter.ArticleViewHolder>() {

class ArticleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
    val ivArticleImage = itemView.findViewById<ImageView>(R.id.ivArticleImage)
    val tvSource = itemView.findViewById<TextView>(R.id.tvSource)
    val tvTitle = itemView.findViewById<TextView>(R.id.tvTitle)
    val tvDescription = itemView.findViewById<TextView>(R.id.tvDescription)
    val tvPublishedAt = itemView.findViewById<TextView>(R.id.tvPublishedAt)




}

private val diffcallback = object : DiffUtil.ItemCallback<Article>(){
    override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {
        return oldItem.url == newItem.url
    }

    override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {
        return oldItem == newItem
}
}




val differ = AsyncListDiffer(this, diffcallback)


override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
    return ArticleViewHolder(
        LayoutInflater.from(parent.context).inflate(
            R.layout.item_article_preview,
            parent,
            false
        )
    )


}

fun setOnItemClickListener(listen: (Article) -> Unit){
    Log.d("NewsAdapter", "setOnItem")
    onItemClickListener = listen
}

private var onItemClickListener: ((Article) -> Unit)? = null


override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
    val article = differ.currentList[position]

    holder.itemView.apply {

        Glide.with(this).load(article.urlToImage).into(holder.ivArticleImage)
        holder.tvSource.text = article.source?.name
        holder.tvTitle.text = article.title
        holder.tvDescription.text = article.description
        holder.tvPublishedAt.text = article.publishedAt
        setOnClickListener{
            Log.d("NewsAdapter", "onBindViewHolder")
            onItemClickListener?.let {
                it(article)
            }
        }
    }
}

override fun getItemCount(): Int {
    return differ.currentList.size
}

}

BreakingNews (The Fragment/UI)

class BreakingNews: Fragment(R.layout.breakingnews) {
lateinit var viewModel: NewsViewModel
lateinit var newsAdapter: NewsAdapter
lateinit var binding: BreakingnewsBinding

val TAG = "BreakingNewsFragment"

var isLoading = false
var isLastPage = false
var isScrolling = false

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {

    binding = BreakingnewsBinding.inflate(inflater,container,false);
    val view = binding.root;
    return view;
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    viewModel = (activity as MainActivity).viewModel
    setupRecyclerView()

    newsAdapter.setOnItemClickListener {
        val bundle = Bundle().apply {
            putSerializable("article",it)  //Passing data between activity or fragments, work based on key/value pairs. Since we have denoted our Article Class to be of type Serializable, we can use this to pass data.
        }
        findNavController().navigate(
            R.id.action_breakingNews_to_newsDetailsFragment,
            bundle
        )
    }

    viewModel.breakingNews.observe(viewLifecycleOwner,Observer{response ->
        when (response){
            is Resource.Success -> {
                hideProgressBar()
                response.data?.let {newsResponse ->
                        newsAdapter.differ.submitList(newsResponse.articles.toList())
                    val totalPages = newsResponse.totalResults / QUERY_PAGE_SIZE + 2

                    isLastPage = viewModel.breakingNewsPage == totalPages
                }
            }
            is Resource.Error -> {
                showProgressBar()
                response.message?.let {
                    Log.e(TAG,"An error occured $it")
                }
            }
            is Resource.Loading -> {
                hideProgressBar()
            }
        }
    })


}

private fun hideProgressBar() {
    binding.paginationProgressBar.visibility = View.INVISIBLE
    isLoading = false
}

private fun showProgressBar() {
    binding.paginationProgressBar.visibility = View.VISIBLE
    isLoading = true
}

var scrollListener = object : RecyclerView.OnScrollListener() {
    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        super.onScrollStateChanged(recyclerView, newState)

        if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
            isScrolling = true
        }
    }

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)

        val layoutManager = recyclerView.layoutManager as LinearLayoutManager
        val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
        val visibleItemCount = layoutManager.childCount
        val totalItemCount = layoutManager.itemCount

        val isNotLoadingAndNotLastPage = !isLoading && !isLastPage
        val isAtLastItem = firstVisibleItemPosition + visibleItemCount >= totalItemCount
        val isNotAtBeginning = firstVisibleItemPosition >= 0
        val isTotalMoreThanVisible = totalItemCount >= QUERY_PAGE_SIZE
        val shouldPaginate =
            isNotLoadingAndNotLastPage && isAtLastItem && isNotAtBeginning && isTotalMoreThanVisible && isScrolling
        if (shouldPaginate) {
            viewModel.getBreakingNews("us")
            isScrolling = false
        }
    }
}

private fun setupRecyclerView() {
    newsAdapter = NewsAdapter()
    binding.breakingNews.apply {
        adapter = newsAdapter
        layoutManager = LinearLayoutManager(activity)
        addOnScrollListener(this@BreakingNews.scrollListener)
    }
}
}

enter code here

1 Answers1

2

I think it's not about being old article and new article, it's about that articles have id of null. To prevent that organize your data classes like below:

@Entity(tableName = "articles")
data class Article(
    @PrimaryKey(autoGenerate = true)
    var id: Int? = null,
    val author: String?,
    val content: String?,
    val description: String?,
    val publishedAt: String?,
    val source: Source?,
    val title: String?,
    val url: String?,
    val urlToImage: String?
): Serializable {
    override fun hashCode(): Int {
        var result = id.hashCode()
        if(url.isNullOrEmpty()){
            result = 31 * result + url.hashCode()
        }
        return result
    }
}
data class Source(
    val id: String,
    val name: String
): Serializable {
    override fun hashCode(): Int {
        var result = id.hashCode()
        if(name.isNullOrEmpty()){
            result = 31 * result + name.hashCode()
        }
        return result
    }
}
Mert
  • 904
  • 4
  • 9
  • 21
  • That solves the problem thanks, however I am curious, what is the point of multiplying the `id.hashcode` and `name.hashcode` by 31? Why 31 and not any number? What does this actually do? – Theprogrammingnoob Nov 27 '22 at 17:20
  • I use it in my projects when i face this type of situations. I thought that it was just a prime number, but with you asking i just found [this](https://stackoverflow.com/q/299304/4058604). We are using `id` because we want it to be not null. When it is null, then it will add `url.hashCode` to it, and we know that `url` values has something in it, because we check it, and most likely are unique. – Mert Nov 27 '22 at 17:38
  • Do you know the reason why we need to declare the `id` value as null? – Theprogrammingnoob Nov 27 '22 at 17:43
  • Because the API you are using can have an article with `null` id. And you need `id` because you use `room` and your primary key is `id` which can not be null. So with this code i shared, you prevent it to be `null`. The API on the other hand use `url` for that distinction. – Mert Nov 27 '22 at 17:48