В настоящее время я пишу приложение для 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)}
}
Вы имеете в виду, как получить этот объект фильма, который был проверен, установив флажок в элементе recyclerview?
Я не знаю, что передать методу, который я вызову в onclicklistener в адаптере, чтобы поместить фильм в избранное.
какой метод вы собираетесь назвать? Пожалуйста, укажите его с его параметрами.
Добавить в избранное и удалить в избранное в репозитории
у вас есть список фильмов из сети, и вы хотите поставить галочку и добавить этот фильм в другой список? которые вы сделали для любимых фильмов
Да это именно тот случай
хорошо, а что это за ссылка на репозиторий в адаптере?
Наверное, забыл, но когда я использую метод в репо, что я ему передаю? Как получить ссылку на фильм, в котором установлен флажок?