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.
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
}
}