我正在电影应用程序上工作,我想在其中加载电影并将其另存为本地数据库中的缓存。我遵循了Udacity的“使用Kotlin构建Andoid应用程序”课程“火星房地产”的示例。我的应用程序正在使用实时数据,MVVM,数据绑定,房间和改造。每次加载新数据时,它们都会保存在db中,并且是“真相”的唯一来源。
这是主要布局:
<layout 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">
<data>
<variable
name="viewModel"
type="com.example.moviesapp.presentation.main.MainViewModel"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.moviesapp.presentation.MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/movies_grid"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="6dp"
android:clipToPadding="false"
android:background="@android:color/black"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:listData="@{viewModel.movies}"
app:spanCount="3"
tools:itemCount="16"
tools:listitem="@layout/grid_view_item"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
片段:
class MainFragment : DaggerFragment(), SharedPreferences.OnSharedPreferenceChangeListener {
/**
// * Lazily initialize [MainViewModel].
// */
private val viewModel: MainViewModel by lazy {
ViewModelProviders.of(this, providerFactory).get(MainViewModel::class.java)
}
@Inject
lateinit var providerFactory: ViewModelProviderFactory
@Inject
lateinit var sharedPreferences: SharedPreferences
override fun onResume() {
super.onResume()
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
val binding = FragmentMainBinding.inflate(inflater)
binding.lifecycleOwner = this
binding.viewModel = viewModel
binding.moviesGrid.adapter = MainAdapter(MainAdapter.OnClickListener {
viewModel.displayMovieDetails(it)
})
viewModel.navigateToSelectedMovie.observe(this, Observer {
if (null != it) {
this.findNavController().navigate(MainFragmentDirections.actionShowDetail(it.id))
viewModel.displayMovieDetailsComplete()
}
})
setHasOptionsMenu(true)
return binding.root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.main, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_settings -> {
this.findNavController().navigate(
MainFragmentDirections.actionShowSettings()
)
true
}
R.id.action_search -> {
this.findNavController().navigate(
MainFragmentDirections.actionMainFragmentToSearchFragment()
)
true
}
R.id.action_back -> {
viewModel.paginateBack()
true
}
R.id.action_forward -> {
viewModel.paginateForward()
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onDestroy() {
super.onDestroy()
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
viewModel.onPreferencesChanged()
}
}
查看模型:
class MainViewModel @Inject constructor(application: Application, private val getMoviesUseCase: GetMoviesUseCase) :
AndroidViewModel(application) {
private var currentPage = 1
private val totalPages: Int by lazy {
with (PreferenceManager.getDefaultSharedPreferences(application)) {
getInt(TOTAL_PAGES, 0)
}
}
// get movies saved in local db
val movies = getMoviesUseCase.allMovies()
init {
loadMovies()
}
private fun loadMovies() {
if (isInternetAvailable(getApplication()))
viewModelScope.launch {
getMoviesUseCase.refreshMovies(currentPage)
}
}
private val _navigateToSelectedMovie = MutableLiveData<Movie>()
val navigateToSelectedMovie: LiveData<Movie>
get() = _navigateToSelectedMovie
fun displayMovieDetails(movie: Movie?) {
if (movie != null) _navigateToSelectedMovie.value = movie
}
fun displayMovieDetailsComplete() {
_navigateToSelectedMovie.value = null
}
适配器:
class MainAdapter(private val onClickListener: OnClickListener) :
ListAdapter<Movie, MainAdapter.MovieViewHolder>(DiffCallback) {
class MovieViewHolder(private var binding: GridViewItemBinding):
RecyclerView.ViewHolder(binding.root ) {
fun bind(movie: Movie) {
binding.movie = movie
binding.executePendingBindings()
}
}
companion object DiffCallback : DiffUtil.ItemCallback<Movie>() {
override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean {
return oldItem.id == newItem.id
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
return MovieViewHolder(
GridViewItemBinding.inflate(
LayoutInflater.from(parent.context)
)
)
}
override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
val movie = getItem(position)
holder.itemView.setOnClickListener {
onClickListener.onClick(movie)
}
holder.bind(movie)
}
class OnClickListener(val clickListener: (movie: Movie) -> Unit) {
fun onClick(movie: Movie) = clickListener(movie)
}
}
数据绑定适配器:
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, data: List<Movie>?) {
val adapter = recyclerView.adapter as MainAdapter
adapter.submitList(data)
}
@BindingAdapter("posterImageUrl")
fun bindPosterImage(imgView: ImageView, imgUrl: String?) {
imgUrl?.let {
val imgUri = (IMAGE_BASE_URL + POSTER_SIZE + imgUrl).toUri().buildUpon().build()
Glide.with(imgView.context)
.load(imgUri)
.apply(RequestOptions().placeholder(R.drawable.loading_animation).error(R.drawable.ic_broken_image))
.into(imgView)
}
}
我现在在View模型中显示新的实时数据,但是Recyclerview直到刷新后才会更新。 viewmodel实例通过数据绑定传递到recyclerview。我现在在片段中称实时数据为Observ方法,但在此示例中不知道它是如何完成的。在火星房地产示例中,它似乎正在工作。我找不到代码的区别。
谢谢,护甲
// in view model, is the type of movies LiveData<List<Movie>> ?
val movies = getMoviesUseCase.allMovies()
如果从getMoviesUseCase.allMovies()返回的电影类型为LivaData<List<Movie>>
类型,则观察者(Recyclerview)可以接收更改。
如果电影的类型是List<Movie>
,则观察者无法接收更改,因为没有人通知观察者。
如果电影的类型为List<Movie>
,请将其更改为LiveData<List<Movie>>
。
private val _movies = MutableLiveData<List<Movie>>().apply { value = emptyList() }
val movies: LiveData<List<Movie>> = _movies
并尝试像这样初始化_movies:
viewModelScope.launch {
_movies.value = getMoviesUseCase.allMovies()
}