在 switchmap 中启动协程
Launching a coroutine within a switchmap
所以我正在编写一个显示电影列表的应用程序。我想实现一个搜索功能,其中显示搜索结果的函数将从 api.
调用
但是,我在协同程序中实现 switchmap 时遇到了问题。我在 return 类型方面遇到了特别的麻烦,因为 viewmodelscope return 是我需要实时数据的工作。下面是相关代码。
谢谢!
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 {
}
}
MoviesApi.kt
package com.example.moviesapp.network
import retrofit2.http.GET
import retrofit2.http.Query
const val API_KEY = "[mykey]"
const val MEDIA_TYPE = "movie"
const val TIME_WINDOW = "week"
interface MoviesApi {
companion object {
const val BASE_URL = "https://api.themoviedb.org/3/"
}
@GET("search/movie")
suspend fun getMovies(
@Query("query") query: String,
@Query("api_key") key: String = API_KEY,
): List<MoviesResults.Movies>
@GET("trending/${MEDIA_TYPE}/${TIME_WINDOW}")
suspend fun getTrendingMovies(
@Query("api_key") api_key: String = API_KEY,
@Query("media_type") media_type: String = MEDIA_TYPE,
@Query("time_window") time_window: String = TIME_WINDOW,
): List<MoviesResults.Movies>
@GET("discover/movie")
suspend fun getActionMovies(
@Query("api_key") api_key: String = API_KEY,
@Query("with_genres") with_genres: String = "28"
): List<MoviesResults.Movies>
@GET("discover/movie")
suspend fun getComedyMovies(
@Query("api_key") api_key: String = API_KEY,
@Query("with_genres") with_genres: String = "35"
): List<MoviesResults.Movies>
@GET("discover/movie")
suspend fun getHorrorMovies(
@Query("api_key") api_key: String = API_KEY,
@Query("with_genres") with_genres: String = "27"
): List<MoviesResults.Movies>
@GET("discover/movie")
suspend fun getRomanceMovies(
@Query("api_key") api_key: String = API_KEY,
@Query("with_genres") with_genres: String = "10749"
): List<MoviesResults.Movies>
@GET("discover/movie")
suspend fun getScifiMovies(
@Query("api_key") api_key: String = API_KEY,
@Query("with_genres") with_genres: String = "878"
): List<MoviesResults.Movies>
}
MoviesRepository.kt
package com.example.moviesapp.network
import androidx.lifecycle.MutableLiveData
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) {
//This function will be called later on in the ViewModel
suspend fun getSearchResults(query:String): MutableLiveData<List<MoviesResults.Movies>> {
return moviesApi.getMovies(query, API_KEY,).
}
suspend fun getTrendingMovies(): List<MoviesResults.Movies> {
return moviesApi.getTrendingMovies(API_KEY, MEDIA_TYPE, TIME_WINDOW)
}
suspend fun getActionMovies(): List<MoviesResults.Movies> {
return moviesApi.getActionMovies(API_KEY,"28")
}
suspend fun getComedyMovies(): List<MoviesResults.Movies> {
return moviesApi.getComedyMovies(API_KEY,"35")
}
suspend fun getHorrorMovies(): List<MoviesResults.Movies> {
return moviesApi.getHorrorMovies(API_KEY,"27")
}
suspend fun getRomanceMovies(): List<MoviesResults.Movies> {
return moviesApi.getRomanceMovies(API_KEY,"10749")
}
suspend fun getScifiMovies(): List<MoviesResults.Movies> {
return moviesApi.getScifiMovies(API_KEY,"878")
}
}
MoviesListViewModel.kt
package com.example.moviesapp.ui
import androidx.lifecycle.*
import com.example.moviesapp.network.MoviesRepository
import com.example.moviesapp.network.MoviesResults
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
const val DEFAULT_QUERY = " "
@HiltViewModel
class MoviesListViewModel @Inject constructor(
private val repository: MoviesRepository,
): ViewModel() {
private val _moviesAction = MutableLiveData<List<MoviesResults.Movies>>()
val moviesAction: LiveData<List<MoviesResults.Movies>> = _moviesAction
private val _moviesComedy = MutableLiveData<List<MoviesResults.Movies>>()
val moviesComedy: LiveData<List<MoviesResults.Movies>> = _moviesComedy
private val _moviesHorror = MutableLiveData<List<MoviesResults.Movies>>()
val moviesHorror: LiveData<List<MoviesResults.Movies>> = _moviesHorror
private val _moviesRomance = MutableLiveData<List<MoviesResults.Movies>>()
val moviesRomance: LiveData<List<MoviesResults.Movies>> = _moviesRomance
private val _moviesScifi = MutableLiveData<List<MoviesResults.Movies>>()
val moviesScifi: LiveData<List<MoviesResults.Movies>> = _moviesScifi
private val _moviesTrending= MutableLiveData<List<MoviesResults.Movies>>()
val moviesTrending: LiveData<List<MoviesResults.Movies>> = _moviesTrending
fun getAction() {
viewModelScope.launch {
_moviesAction.value = repository.getActionMovies()
}
}
fun getComedy() {
viewModelScope.launch {
_moviesComedy.value = repository.getComedyMovies()
}
}
fun getHorror() {
viewModelScope.launch {
_moviesHorror.value = repository.getHorrorMovies()
}
}
fun getRomance() {
viewModelScope.launch {
_moviesRomance.value = repository.getRomanceMovies()
}
}
fun getScifi() {
viewModelScope.launch {
_moviesScifi.value = repository.getScifiMovies()
}
}
fun getTrending() {
viewModelScope.launch {
_moviesTrending.value = repository.getTrendingMovies()
}
}
private var currentQuery = MutableLiveData(DEFAULT_QUERY)
val movies = currentQuery.switchMap {
queryString ->
viewModelScope.launch {
repository.getSearchResults(queryString)
}
}
fun searchMovies(query: String) {
currentQuery.value = query
}
class MoviesListViewModelFactory @Inject constructor(private val repository: MoviesRepository, private val movie: MoviesResults.Movies): ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MoviesListViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return MoviesListViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
}
MoviesListAdapter.kt
package com.example.moviesapp.ui
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.moviesapp.R
import com.example.moviesapp.databinding.MovieLayoutBinding
import com.example.moviesapp.network.MoviesResults
import java.util.*
val IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w500"
class MoviesListAdapter constructor(private val listener: OnItemClickListener) :
ListAdapter<MoviesResults.Movies, MoviesListAdapter.MoviesListViewHolder>(DiffCallback) {
private var movies: List<MoviesResults.Movies> = Collections.emptyList()
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) {
val currentItem = movies[position]
holder.bind(currentItem)
}
inner class MoviesListViewHolder(val binding: MovieLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
init {
binding.root.setOnClickListener {
val position = absoluteAdapterPosition
if (position != RecyclerView.NO_POSITION) {
val item = movies[position]
listener.onItemClick(item)
}
}
}
init {
binding.root.setOnClickListener {
val position = absoluteAdapterPosition
if (position != RecyclerView.NO_POSITION) {
val item = movies[position]
listener.onFavoriteClick(item)
}
}
}
init {
if (binding.favoritesCheckbox.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)
}
}
private fun showToast(string: String) {
Toast.makeText(itemView.context, string, Toast.LENGTH_SHORT).show()
}
}
interface OnItemClickListener {
fun onItemClick(movie: MoviesResults.Movies)
fun onFavoriteClick(movie: MoviesResults.Movies)
}
override fun getItemCount(): Int {
return movies.size
}
companion object DiffCallback : DiffUtil.ItemCallback<MoviesResults.Movies>() {
override fun areItemsTheSame(
oldItem: MoviesResults.Movies,
newItem: MoviesResults.Movies
): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(
oldItem: MoviesResults.Movies,
newItem: MoviesResults.Movies
): Boolean {
return oldItem == newItem
}
}
}
MoviesListFragment.kt
package com.example.moviesapp.ui.Fragments
import android.os.Bundle
import android.view.*
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.moviesapp.R
import com.example.moviesapp.databinding.FragmentMoviesListBinding
import com.example.moviesapp.network.MoviesResults
import com.example.moviesapp.ui.DaoViewModel
import com.example.moviesapp.ui.MoviesListAdapter
import com.example.moviesapp.ui.MoviesListViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MoviesListFragment : Fragment(), MoviesListAdapter.OnItemClickListener {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_movies_list, container, false)
}
private val daoViewModel by viewModels<DaoViewModel>()
private val viewModel by viewModels<MoviesListViewModel>()
private var _binding: FragmentMoviesListBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//View is inflated layout
_binding = FragmentMoviesListBinding.bind(view)
val adapter = MoviesListAdapter(this)
binding.apply {
recyclerView.layoutManager = LinearLayoutManager(requireContext())
//Disable animations
recyclerView.setHasFixedSize(true)
recyclerView.adapter = adapter
}
//Observe the movies livedata
//Use viewLifecycleOwner instead of this because the UI should stop being updated when the fragment view is destroyed
viewModel.getTrending()
viewModel.moviesTrending.observe(viewLifecycleOwner) {
adapter.submitList(it)
}
//Display trending movies
//loadstate is of type combined loadstates, which combines the loadstate of different scenarios(when we refresh dataset or when we append new data to it) into this one object
//We can use it to check for these scenarios and make our views visible or unvisible according to it
setHasOptionsMenu(true)
}
override fun onItemClick(movie: MoviesResults.Movies) {
val action = MoviesListFragmentDirections.actionMoviesListFragmentToMoviesDetailsFragment(movie)
findNavController().navigate(action)
}
override fun onFavoriteClick(movie: MoviesResults.Movies) {
daoViewModel.addMovieToFavs(movie)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
// Inflate the gallery menu
inflater.inflate(R.menu.menu_gallery, menu)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
如前所述,here 在将 Coroutine
与 LiveData
一起使用时,您应该使用 liveData
生成器函数。像这样
val movies = currentQuery.switchMap { queryString ->
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
val result = repository.getSearchResults(queryString)
emit(result)
}
}
对于我来说,我通常使用builder函数liveData {}
。
它是一个构建器函数,允许您使用协程触发实时数据。与常规 livedata 的不同之处在于,使用此构建器函数,您可以使用 emit()
函数触发 LiveData,而不是使用 setValue()
或 postValue()
来触发 LiveData。
@HiltViewModel
class MoviesListViewModel @Inject constructor(private val repository: MoviesRepository): ViewModel() {
val switchMapLiveData = _yourLiveData.switchMap { yourLiveDataValue ->
liveData {
emit(repository.yourFunction(yourLiveDataValue))
}
}
}
所以我正在编写一个显示电影列表的应用程序。我想实现一个搜索功能,其中显示搜索结果的函数将从 api.
调用但是,我在协同程序中实现 switchmap 时遇到了问题。我在 return 类型方面遇到了特别的麻烦,因为 viewmodelscope return 是我需要实时数据的工作。下面是相关代码。
谢谢!
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 {
}
}
MoviesApi.kt
package com.example.moviesapp.network
import retrofit2.http.GET
import retrofit2.http.Query
const val API_KEY = "[mykey]"
const val MEDIA_TYPE = "movie"
const val TIME_WINDOW = "week"
interface MoviesApi {
companion object {
const val BASE_URL = "https://api.themoviedb.org/3/"
}
@GET("search/movie")
suspend fun getMovies(
@Query("query") query: String,
@Query("api_key") key: String = API_KEY,
): List<MoviesResults.Movies>
@GET("trending/${MEDIA_TYPE}/${TIME_WINDOW}")
suspend fun getTrendingMovies(
@Query("api_key") api_key: String = API_KEY,
@Query("media_type") media_type: String = MEDIA_TYPE,
@Query("time_window") time_window: String = TIME_WINDOW,
): List<MoviesResults.Movies>
@GET("discover/movie")
suspend fun getActionMovies(
@Query("api_key") api_key: String = API_KEY,
@Query("with_genres") with_genres: String = "28"
): List<MoviesResults.Movies>
@GET("discover/movie")
suspend fun getComedyMovies(
@Query("api_key") api_key: String = API_KEY,
@Query("with_genres") with_genres: String = "35"
): List<MoviesResults.Movies>
@GET("discover/movie")
suspend fun getHorrorMovies(
@Query("api_key") api_key: String = API_KEY,
@Query("with_genres") with_genres: String = "27"
): List<MoviesResults.Movies>
@GET("discover/movie")
suspend fun getRomanceMovies(
@Query("api_key") api_key: String = API_KEY,
@Query("with_genres") with_genres: String = "10749"
): List<MoviesResults.Movies>
@GET("discover/movie")
suspend fun getScifiMovies(
@Query("api_key") api_key: String = API_KEY,
@Query("with_genres") with_genres: String = "878"
): List<MoviesResults.Movies>
}
MoviesRepository.kt
package com.example.moviesapp.network
import androidx.lifecycle.MutableLiveData
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) {
//This function will be called later on in the ViewModel
suspend fun getSearchResults(query:String): MutableLiveData<List<MoviesResults.Movies>> {
return moviesApi.getMovies(query, API_KEY,).
}
suspend fun getTrendingMovies(): List<MoviesResults.Movies> {
return moviesApi.getTrendingMovies(API_KEY, MEDIA_TYPE, TIME_WINDOW)
}
suspend fun getActionMovies(): List<MoviesResults.Movies> {
return moviesApi.getActionMovies(API_KEY,"28")
}
suspend fun getComedyMovies(): List<MoviesResults.Movies> {
return moviesApi.getComedyMovies(API_KEY,"35")
}
suspend fun getHorrorMovies(): List<MoviesResults.Movies> {
return moviesApi.getHorrorMovies(API_KEY,"27")
}
suspend fun getRomanceMovies(): List<MoviesResults.Movies> {
return moviesApi.getRomanceMovies(API_KEY,"10749")
}
suspend fun getScifiMovies(): List<MoviesResults.Movies> {
return moviesApi.getScifiMovies(API_KEY,"878")
}
}
MoviesListViewModel.kt
package com.example.moviesapp.ui
import androidx.lifecycle.*
import com.example.moviesapp.network.MoviesRepository
import com.example.moviesapp.network.MoviesResults
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
const val DEFAULT_QUERY = " "
@HiltViewModel
class MoviesListViewModel @Inject constructor(
private val repository: MoviesRepository,
): ViewModel() {
private val _moviesAction = MutableLiveData<List<MoviesResults.Movies>>()
val moviesAction: LiveData<List<MoviesResults.Movies>> = _moviesAction
private val _moviesComedy = MutableLiveData<List<MoviesResults.Movies>>()
val moviesComedy: LiveData<List<MoviesResults.Movies>> = _moviesComedy
private val _moviesHorror = MutableLiveData<List<MoviesResults.Movies>>()
val moviesHorror: LiveData<List<MoviesResults.Movies>> = _moviesHorror
private val _moviesRomance = MutableLiveData<List<MoviesResults.Movies>>()
val moviesRomance: LiveData<List<MoviesResults.Movies>> = _moviesRomance
private val _moviesScifi = MutableLiveData<List<MoviesResults.Movies>>()
val moviesScifi: LiveData<List<MoviesResults.Movies>> = _moviesScifi
private val _moviesTrending= MutableLiveData<List<MoviesResults.Movies>>()
val moviesTrending: LiveData<List<MoviesResults.Movies>> = _moviesTrending
fun getAction() {
viewModelScope.launch {
_moviesAction.value = repository.getActionMovies()
}
}
fun getComedy() {
viewModelScope.launch {
_moviesComedy.value = repository.getComedyMovies()
}
}
fun getHorror() {
viewModelScope.launch {
_moviesHorror.value = repository.getHorrorMovies()
}
}
fun getRomance() {
viewModelScope.launch {
_moviesRomance.value = repository.getRomanceMovies()
}
}
fun getScifi() {
viewModelScope.launch {
_moviesScifi.value = repository.getScifiMovies()
}
}
fun getTrending() {
viewModelScope.launch {
_moviesTrending.value = repository.getTrendingMovies()
}
}
private var currentQuery = MutableLiveData(DEFAULT_QUERY)
val movies = currentQuery.switchMap {
queryString ->
viewModelScope.launch {
repository.getSearchResults(queryString)
}
}
fun searchMovies(query: String) {
currentQuery.value = query
}
class MoviesListViewModelFactory @Inject constructor(private val repository: MoviesRepository, private val movie: MoviesResults.Movies): ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MoviesListViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return MoviesListViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
}
MoviesListAdapter.kt
package com.example.moviesapp.ui
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.moviesapp.R
import com.example.moviesapp.databinding.MovieLayoutBinding
import com.example.moviesapp.network.MoviesResults
import java.util.*
val IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w500"
class MoviesListAdapter constructor(private val listener: OnItemClickListener) :
ListAdapter<MoviesResults.Movies, MoviesListAdapter.MoviesListViewHolder>(DiffCallback) {
private var movies: List<MoviesResults.Movies> = Collections.emptyList()
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) {
val currentItem = movies[position]
holder.bind(currentItem)
}
inner class MoviesListViewHolder(val binding: MovieLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
init {
binding.root.setOnClickListener {
val position = absoluteAdapterPosition
if (position != RecyclerView.NO_POSITION) {
val item = movies[position]
listener.onItemClick(item)
}
}
}
init {
binding.root.setOnClickListener {
val position = absoluteAdapterPosition
if (position != RecyclerView.NO_POSITION) {
val item = movies[position]
listener.onFavoriteClick(item)
}
}
}
init {
if (binding.favoritesCheckbox.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)
}
}
private fun showToast(string: String) {
Toast.makeText(itemView.context, string, Toast.LENGTH_SHORT).show()
}
}
interface OnItemClickListener {
fun onItemClick(movie: MoviesResults.Movies)
fun onFavoriteClick(movie: MoviesResults.Movies)
}
override fun getItemCount(): Int {
return movies.size
}
companion object DiffCallback : DiffUtil.ItemCallback<MoviesResults.Movies>() {
override fun areItemsTheSame(
oldItem: MoviesResults.Movies,
newItem: MoviesResults.Movies
): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(
oldItem: MoviesResults.Movies,
newItem: MoviesResults.Movies
): Boolean {
return oldItem == newItem
}
}
}
MoviesListFragment.kt
package com.example.moviesapp.ui.Fragments
import android.os.Bundle
import android.view.*
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.moviesapp.R
import com.example.moviesapp.databinding.FragmentMoviesListBinding
import com.example.moviesapp.network.MoviesResults
import com.example.moviesapp.ui.DaoViewModel
import com.example.moviesapp.ui.MoviesListAdapter
import com.example.moviesapp.ui.MoviesListViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MoviesListFragment : Fragment(), MoviesListAdapter.OnItemClickListener {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_movies_list, container, false)
}
private val daoViewModel by viewModels<DaoViewModel>()
private val viewModel by viewModels<MoviesListViewModel>()
private var _binding: FragmentMoviesListBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//View is inflated layout
_binding = FragmentMoviesListBinding.bind(view)
val adapter = MoviesListAdapter(this)
binding.apply {
recyclerView.layoutManager = LinearLayoutManager(requireContext())
//Disable animations
recyclerView.setHasFixedSize(true)
recyclerView.adapter = adapter
}
//Observe the movies livedata
//Use viewLifecycleOwner instead of this because the UI should stop being updated when the fragment view is destroyed
viewModel.getTrending()
viewModel.moviesTrending.observe(viewLifecycleOwner) {
adapter.submitList(it)
}
//Display trending movies
//loadstate is of type combined loadstates, which combines the loadstate of different scenarios(when we refresh dataset or when we append new data to it) into this one object
//We can use it to check for these scenarios and make our views visible or unvisible according to it
setHasOptionsMenu(true)
}
override fun onItemClick(movie: MoviesResults.Movies) {
val action = MoviesListFragmentDirections.actionMoviesListFragmentToMoviesDetailsFragment(movie)
findNavController().navigate(action)
}
override fun onFavoriteClick(movie: MoviesResults.Movies) {
daoViewModel.addMovieToFavs(movie)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
// Inflate the gallery menu
inflater.inflate(R.menu.menu_gallery, menu)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
如前所述,here 在将 Coroutine
与 LiveData
一起使用时,您应该使用 liveData
生成器函数。像这样
val movies = currentQuery.switchMap { queryString ->
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
val result = repository.getSearchResults(queryString)
emit(result)
}
}
对于我来说,我通常使用builder函数liveData {}
。
它是一个构建器函数,允许您使用协程触发实时数据。与常规 livedata 的不同之处在于,使用此构建器函数,您可以使用 emit()
函数触发 LiveData,而不是使用 setValue()
或 postValue()
来触发 LiveData。
@HiltViewModel
class MoviesListViewModel @Inject constructor(private val repository: MoviesRepository): ViewModel() {
val switchMapLiveData = _yourLiveData.switchMap { yourLiveDataValue ->
liveData {
emit(repository.yourFunction(yourLiveDataValue))
}
}
}