【问题标题】:Implementing a favorites features on MoviesApp在 MoviesApp 上实现收藏夹功能
【发布时间】:2021-08-08 19:38:31
【问题描述】:

我目前正在用 Kotlin 编写一个显示电影列表的 android 应用程序,并且我想添加一个收藏夹功能。我在每部电影附加的 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
        }

    }



}




电影.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 中调用的方法以将电影放入收藏夹
  • 你要调用的方法是什么?请提及它的参数。
  • 添加到收藏夹并删除存储库中的收藏夹
  • 您有来自网络的电影列表,并且您想选中该框并将该电影添加到其他列表中?你为最喜欢的电影制作的

标签: android kotlin mvvm android-room


【解决方案1】:
  1. 您可以使用像 hereexample 这样记录您的点击的Listner

  2. 在您的数据类Movies.kt 中,您可以添加一个变量来显示电影是否受欢迎,即

var isFavourite : Boolean = false 

您可以在需要时根据该列查询。

【讨论】:

  • 但是如果我使用布尔变量如何使用适配器中的方法仍然存在问题
  • 最简单的方法是使用选择电影一部电影(例如:通过长按,通过实现您的fun onItemClick(movie: MoviesResults.Movies) 覆盖侦听器中的功能),然后使用警告对话框询问您是否要为这部电影加星,如果是,则将布尔值设置为 true 并更新数据库。
  • 不能通过checkbox做吗?
  • 这是可能的,但是你需要在你的活动中有两个状态 1) 查看状态,2) 可以通过操作切换的编辑状态,然后在编辑状态下你必须实现复选框功能及其操作。这一切都需要大规模实施。
【解决方案2】:

利用 kotlin 的特性,您可以在适配器中使用 lambda 进行回调:

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

【讨论】:

  • 你所说的view是viewholder?
  • 更新了答案。不,查看平均活动/片段。
  • viewmodel中的函数是一个挂起函数,虽然它不起作用
  • 在您的viewmodel 中使用 viewmodelScope 并更新您如何使用 lambda 的问题。
  • 但是我必须更新每个片段中的适配器。有没有办法从视图模型或存储库调用适配器中的函数?
猜你喜欢
  • 2012-04-08
  • 2023-01-22
  • 1970-01-01
  • 2021-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-19
  • 1970-01-01
相关资源
最近更新 更多