News API - Add to bookmark

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