Hello everyone,
I’ve been stuck for several days now, trying to set up a favorites system on my app.
I’m using the API: NewsAPI, and I have 2 fragments, a “home” which displays the latest articles and a “dashboard” which will display the articles put in favorites thanks to my bookmark image, which when I click on it copies the article in the Dashboard tab.
But I can’t do this, so I need your help.
I have a second problem, when I’m on the first tab / fragment, and I change then come back to the main fragment my app crashes, I don’t know if anyone would have an idea why it does that.
Here is the code of my ArticleAdapter.kt:
class ArticleAdapter(private val context: Context, private var articleList: List<Article>) : RecyclerView.Adapter<ArticleAdapter.ArticleViewHolder>() {
private var isBookmarked = false
private val favoriteArticleList = ArrayList<Article>()
private var onItemClickListener: OnItemClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item_layout, parent, false)
return ArticleViewHolder(view)
}
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
val article = articleList[position]
holder.bookmarkImageView.setImageResource(if (favoriteArticleList.contains(article)) R.drawable.baseline_bookmark_24 else R.drawable.baseline_bookmark_border_24)
holder.bind(article)
holder.shareImageView.setOnClickListener {
showSharePopupMenu(holder.bookmarkImageView, article)
}
holder.bookmarkImageView.setOnClickListener {
holder.bookmarkImageView.setImageResource(if (favoriteArticleList.contains(article)) R.drawable.baseline_bookmark_24 else R.drawable.baseline_bookmark_border_24)
if (favoriteArticleList.contains(article))
{
Toast.makeText(context, "Removed from bookmark", Toast.LENGTH_SHORT).show()
favoriteArticleList.remove(article)
}
else
{
Toast.makeText(context, "Added to bookmark", Toast.LENGTH_SHORT).show()
favoriteArticleList.add(article)
}
notifyDataSetChanged()
}
holder.itemView.setOnClickListener {
onItemClickListener?.onItemClick(article)
}
}
override fun getItemCount(): Int {
return articleList.size
}
fun updateData(newArticleList: List<Article>) {
articleList = newArticleList
notifyDataSetChanged()
}
inner class ArticleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val bookmarkImageView: ImageView = itemView.findViewById(R.id.imageView_bookmark)
val shareImageView: ImageView = itemView.findViewById(R.id.imageView_share)
private val titleTextView: TextView = itemView.findViewById(R.id.textView_title)
private val dateTextView: TextView = itemView.findViewById(R.id.textView_date)
private val descriptionTextView: TextView = itemView.findViewById(R.id.textView_description)
private val articleImageView: ImageView = itemView.findViewById(R.id.imageView)
fun bind(article: Article) {
titleTextView.text = article.title
dateTextView.text = article.publishedAt.replace("T", " ").replace("Z", "")
descriptionTextView.text = article.description
Glide.with(itemView.context)
.load(article.urlToImage)
.into(articleImageView)
}
}
private fun showSharePopupMenu(anchorView: View, article: Article) {
shareContent(article.url)
}
private fun shareContent(content: String) {
val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.type = "text/plain"
shareIntent.putExtra(Intent.EXTRA_TEXT, content)
context.startActivity(Intent.createChooser(shareIntent, "Partager via"))
}
interface OnItemClickListener {
fun onItemClick(article: Article)
}
fun setOnItemClickListener(listener: OnItemClickListener) {
onItemClickListener = listener
}
}
And the one from my DashboardFragment.kt:
class DashboardFragment : Fragment() {
private var _binding: FragmentDashboardBinding? = null
private lateinit var recyclerView: RecyclerView
private var favoriteArticlesList = mutableListOf<Article>()
private lateinit var articleAdapter: ArticleAdapter
private lateinit var favoriteArticleAdapter: ArticleAdapter
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val dashboardViewModel =
ViewModelProvider(this).get(DashboardViewModel::class.java)
_binding = FragmentDashboardBinding.inflate(inflater, container, false)
val root: View = binding.root
recyclerView = root.findViewById(R.id.recyclerViewDashboard)
recyclerView.layoutManager = LinearLayoutManager(requireContext())
articleAdapter = ArticleAdapter(requireContext(), emptyList())
favoriteArticleAdapter = ArticleAdapter(requireContext(), emptyList())
recyclerView.adapter = favoriteArticleAdapter
favoriteArticlesList = mutableListOf()
articleAdapter.setOnItemClickListener(object : ArticleAdapter.OnItemClickListener {
override fun onItemClick(article: Article) {
if (favoriteArticlesList.contains(article)) {
favoriteArticlesList.remove(article)
} else {
favoriteArticlesList.add(article)
}
favoriteArticleAdapter.updateData(favoriteArticlesList)
}
})
return root
}
override fun onResume() {
super.onResume()
articleAdapter.notifyDataSetChanged()
favoriteArticleAdapter.notifyDataSetChanged()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
As for the xml, the 2 fragments have the same xml with a recyclerView and a search bar (just the ids change):
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.dashboard.DashboardFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewDashboard"
android:layout_width="match_parent"
android:layout_height="666dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/search"
app:layout_constraintVertical_bias="0.647" />
<androidx.appcompat.widget.SearchView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:id="@+id/search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:iconifiedByDefault="false"
app:searchHintIcon="@null"
app:queryHint="Search..."
android:focusable="false"
app:closeIcon="@drawable/baseline_clear_24"
app:searchIcon="@drawable/baseline_search_24" />
</androidx.constraintlayout.widget.ConstraintLayout>
And I have a list_item_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_marginHorizontal="10dp"
android:layout_marginVertical="10dp"
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="20dp"
app:cardElevation="8dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/titleLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0">
<TextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Titre"
android:textColor="@android:color/black"
android:textSize="20sp"
android:layout_gravity="start"
android:gravity="start"
android:paddingStart="10dp"
android:paddingEnd="40dp" />
<ImageView
android:id="@+id/imageView_bookmark"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:background="@android:color/transparent"
android:clickable="true"
android:src="@drawable/baseline_bookmark_border_24"
app:layout_constraintBottom_toTopOf="@+id/imageView"
app:layout_constraintEnd_toEndOf="@+id/textView_description"
app:layout_constraintTop_toBottomOf="@+id/textView_description"
app:layout_constraintVertical_bias="0.0" />
</LinearLayout>
<TextView
android:id="@+id/textView_date"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Date"
android:textColor="@android:color/holo_red_dark"
android:textSize="12sp"
android:layout_marginStart="10dp"
app:layout_constraintStart_toStartOf="@+id/titleLayout"
app:layout_constraintTop_toBottomOf="@+id/titleLayout"
app:layout_constraintEnd_toEndOf="@+id/titleLayout"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintVertical_bias="0.0" />
<TextView
android:id="@+id/textView_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."
android:textColor="@android:color/holo_blue_dark"
android:textSize="16sp"
android:layout_marginStart="10dp"
app:layout_constraintStart_toStartOf="@+id/titleLayout"
app:layout_constraintTop_toBottomOf="@+id/textView_date"
app:layout_constraintEnd_toEndOf="@+id/titleLayout"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintVertical_bias="0.0"
android:layout_marginBottom="8dp" />
<ImageView
android:id="@+id/imageView"
android:layout_width="350dp"
android:layout_height="250dp"
app:layout_constraintStart_toStartOf="@+id/titleLayout"
app:layout_constraintTop_toBottomOf="@+id/imageView_share"
app:layout_constraintEnd_toEndOf="@+id/titleLayout"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintVertical_bias="0.0" />
<ImageView
android:id="@+id/imageView_share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:background="@android:color/transparent"
android:clickable="true"
android:src="@drawable/baseline_share_24"
app:layout_constraintBottom_toTopOf="@+id/imageView"
app:layout_constraintEnd_toEndOf="@+id/textView_description"
app:layout_constraintTop_toBottomOf="@+id/textView_description"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
Do not hesitate to tell me if you need other parts of my code.
Thanks in advance.
Zapsalis