Внедрение избранных функций в MoviesApp

avatar
Ahmadddd4
8 августа 2021 в 19:38
341
2
0

В настоящее время я пишу приложение для Android на Kotlin, которое отображает список фильмов, и я хочу добавить функцию избранного. Я добавил флажок в recyclerview, прикрепленный к каждому фильму, где при нажатии на флажок фильм добавляется в избранное.

Однако у меня возникли проблемы с реализацией правильных функций в адаптере recyclerview. Я не знаю, как получить ссылку на фильм с установленным флажком, чтобы передать его в метод, добавляющий его в представление переработчика.

Спасибо за любую помощь, которую я могу получить.

Ниже вы найдете соответствующий код.

MoviesListViewModel.kt

package com.example.moviesapp

import androidx.lifecycle.*
import androidx.paging.cachedIn
import com.example.moviesapp.network.MovieDao
import com.example.moviesapp.network.MoviesRepository
import com.example.moviesapp.network.MoviesResults
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

const val DEFAULT_QUERY = " "


@HiltViewModel
class MoviesListViewModel @Inject constructor(
    private val repository: MoviesRepository,
    private val movieDao: MovieDao,
): ViewModel() {


    private var currentQuery = MutableLiveData(DEFAULT_QUERY)

    val moviesTrending = repository.getTrendingMovies().cachedIn(viewModelScope)

    val moviesAction = repository.getActionMovies().cachedIn(viewModelScope)

    val moviesComedy = repository.getComedyMovies().cachedIn(viewModelScope)

    val moviesHorror = repository.getHorrorMovies().cachedIn(viewModelScope)

    val moviesRomance = repository.getRomanceMovies().cachedIn(viewModelScope)

    val moviesScifi = repository.getScifiMovies().cachedIn(viewModelScope)




    suspend fun favoriteMovies(movie: MoviesResults.Movies) {
        movieDao.favorite(movie)
    }
    suspend fun deleteMovies(movie: MoviesResults.Movies) {
        movieDao.delete(movie)
    }



    val movies = currentQuery.switchMap {queryString ->
        repository.getSearchResults(queryString).cachedIn(viewModelScope)

    }




    fun searchMovies(query: String) {

    currentQuery.value = query

    }

    class MoviesListViewModelFactory(private val repository: MoviesRepository, private val movieDao: MovieDao): ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            if (modelClass.isAssignableFrom(MoviesListViewModel::class.java)) {
                @Suppress("UNCHECKED_CAST")
                return MoviesListViewModel(repository, movieDao) as T
            }
            throw IllegalArgumentException("Unknown ViewModel class")

        }


    }



}


MoviesListAdapter.kt

package com.example.moviesapp

import android.util.SparseBooleanArray
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.moviesapp.databinding.MovieLayoutBinding
import com.example.moviesapp.network.MoviesRepository
import com.example.moviesapp.network.MoviesResults

val IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w500"

class MoviesListAdapter constructor(private val listener: OnItemClickListener, private val repository: MoviesRepository) :
    PagingDataAdapter<MoviesResults.Movies, MoviesListAdapter.MoviesListViewHolder>(
        MOVIE_COMPARATOR
    ) {








    var checkBoxStateArray = SparseBooleanArray()



    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MoviesListViewHolder {
        val binding = MovieLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)


        return MoviesListViewHolder(binding)
    }


    override fun onBindViewHolder(holder: MoviesListViewHolder, position: Int) {


        if(!checkBoxStateArray.get(position,false))
        {//checkbox unchecked.
            holder.checkbox.isChecked = false


        }
        else
        {//checkbox checked
            holder.checkbox.isChecked = true
        }

        val currentItem = getItem(position)
        if (currentItem != null) {
            holder.bind(currentItem)
        }

    }


    inner class MoviesListViewHolder(private val binding: MovieLayoutBinding) :
        RecyclerView.ViewHolder(binding.root) {




        private fun showToast(string: String) {
            Toast.makeText(itemView.context, string, Toast.LENGTH_SHORT).show()

        }

        var checkbox = binding.favoritesCheckbox

        init {
            binding.root.setOnClickListener {
                val position = bindingAdapterPosition
                if (position != RecyclerView.NO_POSITION) {
                    val item = getItem(position)
                    if (item != null)
                        listener.onItemClick(item)
                }
            }
        }


        init {
            checkbox.setOnClickListener {

                if (checkbox.isChecked) {
                    showToast("Movie added to favorites")




                } else {
                    showToast("Movie removed from favorites")

                }

            }


        }


        fun bind(movie: MoviesResults.Movies) {
            binding.apply {
                movieTitle.text = movie.title
                movieRating.text = movie.vote_average
                movieYear.text = movie.release_date
                Glide.with(itemView)
                    .load(IMAGE_BASE_URL + movie.poster_path)
                    .centerCrop()
                    .error(R.drawable.ic_baseline_error_outline_24)
                    .into(movieImage)

            }
        }



    }


    interface OnItemClickListener {
        fun onItemClick(movie: MoviesResults.Movies)
    }


    companion object {
        private val MOVIE_COMPARATOR = object : DiffUtil.ItemCallback<MoviesResults.Movies>() {
            override fun areItemsTheSame(
                oldItem: MoviesResults.Movies,
                newItem: MoviesResults.Movies
            ) =
                oldItem.id == newItem.id


            override fun areContentsTheSame(
                oldItem: MoviesResults.Movies,
                newItem: MoviesResults.Movies
            ) =
                oldItem == newItem


        }

    }


}



MoviesRepository.kt

package com.example.moviesapp.network

import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.liveData
import javax.inject.Inject
import javax.inject.Singleton


@Singleton
//We use Inject because I own this class, unlike the Retrofit and MoviesApi class
class MoviesRepository @Inject constructor(private val moviesApi: MoviesApi, private val movieDao: MovieDao) {
    //This function will be called later on in the ViewModel
fun getSearchResults(query: String) =
    Pager(
        config = PagingConfig(
            pageSize = 20,
            //Value at which we want to start dropping items
            maxSize = 100,
            //Disabling placeholders for objects that haven't been loaded yet
            enablePlaceholders = false
        ),
        pagingSourceFactory = {MoviesPagingSource(moviesApi, query)}
    //Turn this pager into a stream of paging data to get live updates
    ).liveData

    fun getTrendingMovies() =
        Pager(
            config = PagingConfig(
                pageSize = 20,
                //Value at which we want to start dropping items
                maxSize = 100,
                //Disabling placeholders for objects that haven't been loaded yet
                enablePlaceholders = false
            ),
            pagingSourceFactory = {MoviesTrendingPagingSource(moviesApi)}
            //Turn this pager into a stream of paging data to get live updates
        ).liveData

    fun getActionMovies() =
        Pager(
            config = PagingConfig(
                pageSize = 20,
                //Value at which we want to start dropping items
                maxSize = 100,
                //Disabling placeholders for objects that haven't been loaded yet
                enablePlaceholders = false
            ),
            pagingSourceFactory = {MoviesActionPagingSource(moviesApi)}
            //Turn this pager into a stream of paging data to get live updates
        ).liveData

    fun getComedyMovies() =
        Pager(
            config = PagingConfig(
                pageSize = 20,
                //Value at which we want to start dropping items
                maxSize = 100,
                //Disabling placeholders for objects that haven't been loaded yet
                enablePlaceholders = false
            ),
            pagingSourceFactory = {MoviesComedyPagingSource(moviesApi)}
            //Turn this pager into a stream of paging data to get live updates
        ).liveData

    fun getHorrorMovies() =
        Pager(
            config = PagingConfig(
                pageSize = 20,
                //Value at which we want to start dropping items
                maxSize = 100,
                //Disabling placeholders for objects that haven't been loaded yet
                enablePlaceholders = false
            ),
            pagingSourceFactory = {MoviesHorrorPagingSource(moviesApi)}
            //Turn this pager into a stream of paging data to get live updates
        ).liveData

    fun getRomanceMovies() =
        Pager(
            config = PagingConfig(
                pageSize = 20,
                //Value at which we want to start dropping items
                maxSize = 100,
                //Disabling placeholders for objects that haven't been loaded yet
                enablePlaceholders = false
            ),
            pagingSourceFactory = {MoviesRomancePagingSource(moviesApi)}
            //Turn this pager into a stream of paging data to get live updates
        ).liveData

    fun getScifiMovies() =
        Pager(
            config = PagingConfig(
                pageSize = 20,
                //Value at which we want to start dropping items
                maxSize = 100,
                //Disabling placeholders for objects that haven't been loaded yet
                enablePlaceholders = false
            ),
            pagingSourceFactory = {MoviesScifiPagingSource(moviesApi)}
            //Turn this pager into a stream of paging data to get live updates
        ).liveData


      suspend fun addToFavorites(movies: MoviesResults.Movies) {
          movieDao.favorite(movies)

      }

    suspend fun removeFromFavorites(movies: MoviesResults.Movies) {
        movieDao.delete(movies)

    }








}

MovieDao.kt


package com.example.moviesapp.network

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy


@Dao
interface MovieDao {


    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun favorite(movie: MoviesResults.Movies)


    @Delete
    suspend fun delete(movie: MoviesResults.Movies)



}

MoviesRoomDatabase.kt


package com.example.moviesapp.network

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase


@Database(entities = [MoviesResults.Movies::class], version = 1, exportSchema=false)
abstract class MoviesRoomDatabase : RoomDatabase() {

abstract fun movieDao(): MovieDao


//INSTANCE will keep a reference to the database. This helps in mainting one instance of the database opened since it is an expensive resource to create and maintain
companion object {


    //Volatile variable will never be cached
    //Makes sure INSTANCE is always up-to-date and same for all execution threads
    //Changes made by one thread to INSTANCE are visible to all other threads imemdiately

    @Volatile
private var INSTANCE: MoviesRoomDatabase? = null
    fun getDatabase(context: Context): MoviesRoomDatabase {

        //Wrapping code to get database inside synchronized block means that only one thread of execution can enter this block of code, making sure database is initialized only once
        return INSTANCE ?: synchronized(this) {
         val instance = Room.databaseBuilder(
             context.applicationContext,
             MoviesRoomDatabase::class.java,
         "Movies_Database"
         )
             .fallbackToDestructiveMigration()
             .build()
            INSTANCE = instance

            return instance
        }

    }



}




Movies.kt


package com.example.moviesapp.network

import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.squareup.moshi.Json
import kotlinx.parcelize.Parcelize

@Parcelize
@Entity
data class MoviesResults(
    @Json(name="results") val results: Movies,
): Parcelable {
    @Parcelize
    @Entity
    data class Movies(
    @Json(name= "title") val title: String,
    @PrimaryKey(autoGenerate = true)
    @Json(name="id") val id: Int,
    @Json(name="release_date") val release_date: String ,
    @Json(name="overview") val overview: String ,
    @Json(name="vote_average") val vote_average: String,
    @Json(name="poster_path") val poster_path: String,
    @Json(name="original_language") val original_language: String,
    ): Parcelable {

}
}

MoviesApplication.kt




package com.example.moviesapp

import android.app.Application
import com.example.moviesapp.network.MoviesRoomDatabase
import dagger.hilt.android.HiltAndroidApp


@HiltAndroidApp
class MoviesApplication: Application() {
    val database : MoviesRoomDatabase by lazy {MoviesRoomDatabase.getDatabase(this)}

}

Источник
Kamal Nayan
8 августа 2021 в 20:04
0

Вы имеете в виду, как получить этот объект фильма, который был проверен, установив флажок в элементе recyclerview?

Ahmadddd4
8 августа 2021 в 20:16
0

Я не знаю, что передать методу, который я вызову в onclicklistener в адаптере, чтобы поместить фильм в избранное.

Kamal Nayan
8 августа 2021 в 20:27
0

какой метод вы собираетесь назвать? Пожалуйста, укажите его с его параметрами.

Ahmadddd4
8 августа 2021 в 20:37
0

Добавить в избранное и удалить в избранное в репозитории

USMAN osman
9 августа 2021 в 07:13
0

у вас есть список фильмов из сети, и вы хотите поставить галочку и добавить этот фильм в другой список? которые вы сделали для любимых фильмов

Ahmadddd4
9 августа 2021 в 07:14
0

Да это именно тот случай

USMAN osman
9 августа 2021 в 07:27
0

хорошо, а что это за ссылка на репозиторий в адаптере?

Ahmadddd4
9 августа 2021 в 07:37
0

Наверное, забыл, но когда я использую метод в репо, что я ему передаю? Как получить ссылку на фильм, в котором установлен флажок?

Ответы (2)

avatar
USMAN osman
9 августа 2021 в 07:38
0

Используйте функцию kotlin, вы можете использовать лямбда-выражение в адаптере для обратных вызовов:

class Adapter(
    private val onAddToFavorite: (Movie) -> Unit
) {
    
    inner class MovieViewHolder : RecyclerView.ViewHolder(binding.root){

        fun bind(movie: Movie) {

            if (checkBox.isChecked) {
                onAddToFavorite(movie)
            }

        }
    }
}

Теперь в вашем представлении (активность/фрагмент) у вас будет обратный вызов для определенного фильма, а затем вы добавите его в избранное, например:

 Adapter{
          movie ->
        viewmodel.addFavorite(movie)
    }
Ahmadddd4
9 августа 2021 в 07:40
0

Под представлением вы подразумеваете зрителя?

USMAN osman
9 августа 2021 в 07:42
0

Обновил ответ. Нет, просмотреть среднюю активность/фрагмент.

Ahmadddd4
9 августа 2021 в 08:00
0

Функция в модели представления является функцией приостановки, хотя она не работает.

USMAN osman
9 августа 2021 в 08:02
0

используйте viewmodelScope в своем viewmodel и обновите вопрос, как у вас дела с лямбдой.

Ahmadddd4
10 августа 2021 в 09:06
0

Но тогда мне придется обновлять адаптер в каждом фрагменте. Нет ли способа вызвать функцию в адаптере из модели представления или репозитория?

avatar
Anshul
9 августа 2021 в 04:57
0
  1. Вы можете использовать Listner, который регистрирует ваши клики, например здесь , пример

  2. В вашем классе данных Movies.kt вы можете добавить переменную, которая показывает, является ли фильм любимым, т.е.

var isFavourite : Boolean = false 

и при необходимости вы можете выполнять запросы на основе этого столбца.

Ahmadddd4
9 августа 2021 в 06:52
0

Но все еще остается проблема, как использовать метод в адаптере, если я использую логическую переменную

Anshul
9 августа 2021 в 07:05
0

Самый простой способ - использовать выбор фильма в фильме (например, долгим нажатием переопределить функцию в листнере, внедрив свой fun onItemClick(movie: MoviesResults.Movies)), а затем использовать диалоговое окно предупреждения, спрашивающее, хотите ли вы включить этот фильм в звезду, если да, то установите логическое значение в true и обновить базу данных.

Ahmadddd4
9 августа 2021 в 07:07
0

Через чекбокс нельзя?

Anshul
9 августа 2021 в 07:19
0

Это возможно, но тогда вам нужно иметь два состояния в вашей деятельности: 1) состояние просмотра, 2) состояние редактирования, которое можно переключать с помощью действия, затем в состоянии редактирования вы должны реализовать функцию флажка и его работу. Все это потребует масштабной реализации.