Home > front end >  NewsApp crashes when clicking on past Articles
NewsApp crashes when clicking on past Articles

Time:11-29

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([email protected])
    }
}
}

enter code here

CodePudding user response:

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
    }
}
  • Related