Реализовать функцию поиска с использованием DataSource / DataSourceFactory / PagedList в kotlin MVVM? - PullRequest
0 голосов
/ 19 июня 2020

В настоящее время изучаю язык Kotlin, я пытаюсь изучить архитектуру MVVM с помощью API TMDB.

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

Процесс выглядит следующим образом (вы найдете код после):

  • Наблюдение за моей деятельностью в LiveData<PagedList<Movie>> ViewModel
  • Это LiveData<PageList<Movie>> создается с использованием DataSourceFactory
  • Сам DataSourceFactory создает экземпляр DataSource.

Проблема в том, что DataSource создается в начале приложения и loadInit метод вызывается автоматически (см. схему «путь» и код ниже).

enter image description here

============= ==================== Активность ============================= =

class MainActivity : AppCompatActivity() {

    //private lateinit var viewModel_mainActivity : MainActivityViewModel
    private lateinit var viewModel_mainActivity : MainActivityViewModelSearch

    private lateinit var m_recyclerview : RecyclerView
    private lateinit var m_progressBar : ProgressBar
    private lateinit var m_errorTextView : TextView
    private lateinit var m_editText : EditText
    private lateinit var m_buttonSearch : Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val apiService : TMDBInterface = TMDBClient.getClient()
        viewModel_mainActivity = getViewModel(apiService)

        val movieAdapter = MoviePagedListAdapter(this)

    [...]

        m_buttonSearch = findViewById(R.id.activity_main_buttonSearch)
        m_buttonSearch.setOnClickListener {
            if(m_editText.text.length > 0){
                viewModel_mainActivity.searchMovie(m_editText.text.toString())
            }
        }

        viewModel_mainActivity.moviePagedList.observe(this, Observer {
            movieAdapter.submitList(it)
        })

        viewModel_mainActivity.networkState.observe(this, Observer {
            m_progressBar.visibility = if (viewModel_mainActivity.listIsEmpty() && it == NetworkState.LOADING) View.VISIBLE else View.GONE
            m_errorTextView.visibility = if (viewModel_mainActivity.listIsEmpty() && it == NetworkState.ERROR) View.VISIBLE else View.GONE

            if(!viewModel_mainActivity.listIsEmpty()){
                movieAdapter.setNetworkState(it)
            }
        })
    }

    private fun getViewModel(apiService:TMDBInterface): MainActivityViewModelSearch {
        return ViewModelProviders.of(this, object : ViewModelProvider.Factory {
            override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                return MainActivityViewModelSearch(apiService) as T
            }
        })[MainActivityViewModelSearch::class.java]
    }
}

================================ ViewModel ======= ======================

class MainActivityViewModelSearch(private val apiService : TMDBInterface) : ViewModel(){

    private val compositeDisposable = CompositeDisposable()

    lateinit var movieDataSourceSearchFactory : MovieDataSourceFactorySearch


    val moviePagedList : LiveData<PagedList<Movie>> by lazy {
        fetchLiveMoviePagedList()
    }

    val networkState : LiveData<NetworkState> by lazy {
        Transformations.switchMap<MovieDataSourceSearch,NetworkState>( //permet d'accéder a networkState inclu dans movieLiveDataSource de la factory
            movieDataSourceSearchFactory.movieLiveDataSourceSearch,MovieDataSourceSearch::networkState
        )
    }

    fun listIsEmpty(): Boolean{
        return moviePagedList.value?.isEmpty() ?: true //retourne si la liste est vide ou non et vrai si la liste est nulle
    }


    fun fetchLiveMoviePagedList() : LiveData<PagedList<Movie>>{
        movieDataSourceSearchFactory = MovieDataSourceFactorySearch(apiService,compositeDisposable) //on créer la factory

        val config = PagedList.Config.Builder()
            .setEnablePlaceholders(false)     
            .setPageSize(POST_PER_PAGE)
            .build()

        val moviePagedListbuild = LivePagedListBuilder(movieDataSourceSearchFactory,config).build()

        return moviePagedListbuild
    }

    override fun onCleared(){
        super.onCleared()
        compositeDisposable.dispose()
    }
}

=================== ================ DataSourceFactory ================================

class MovieDataSourceFactorySearch(private val apiService : TMDBInterface, private val compositeDisposable : CompositeDisposable)
    : DataSource.Factory<Int, Movie>() {

    val movieLiveDataSourceSearch = MutableLiveData<MovieDataSourceSearch>()

    override fun create(): DataSource<Int, Movie> {
        Log.i("ON CREATE FACTORY",this.toString())
        val movieDataSourceSearch = MovieDataSourceSearch(apiService,compositeDisposable) //on créer la dataSource pour notre liste de la main activity

        movieLiveDataSourceSearch.postValue(movieDataSourceSearch)
        return movieDataSourceSearch
    }
}

==== ============================ Источник данных ===================== ========

class MovieDataSourceSearch(private val apiService : TMDBInterface, private val compositeDisposable: CompositeDisposable) : PageKeyedDataSource<Int, Movie>(){ //on étend pagekeyedData source car on en a besoin pour gérer la pagination

    private var page = FIRST_PAGE
    val networkState : MutableLiveData<NetworkState> = MutableLiveData()

    //fonction utile pour chargée la toute première page demandée
    override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, Movie>) {
        networkState.postValue(NetworkState.LOADING) //on charge la premiere page, on change l'état de notre réseau
        Log.i("LOAD INITIAL DATASOURCE",this.toString())
        compositeDisposable.add(
            apiService.getSearchMovie("Star", page)
                .subscribeOn(Schedulers.io()) //notre méthode retourne un Single de movie details, on s'abonne au pool de thread Scheduler.io pour surveiller l'appel réseaux
                .subscribe(                   //le subscribe permet de s'abonner au résultat qui à deux paramètre, un pour le succès et un pour l'échec
                    {                         // l'appel est un succès, on a récupérer un film
                        callback.onResult(it.movieList,null,page+1) //on passe à la callback le résultat de la requete
                        networkState.postValue(NetworkState.LOADED)
                    },
                    { //l'appel est un échec
                        networkState.postValue(NetworkState.ERROR)
                        Log.e("MovieDataSource",it.message)
                    }
                )
        )
    }

    //fonction utile pour charger lors d'un scroll vers le bas de l'utilisateur
    override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Movie>) {
        networkState.postValue(NetworkState.LOADING) //on charge une nouvelle page, on change l'état de notre réseau
        Log.i("LOAD AFTER DATASOURCE",this.toString())
        compositeDisposable.add(
            apiService.getSearchMovie("Star",params.key)
                .subscribeOn(Schedulers.io()) //notre méthode retourne un Single de movie details, on s'abonne au pool de thread Scheduler.io pour surveiller l'appel réseaux
                .subscribe(                   //le subscribe permet de s'abonner au résultat qui à deux paramètre, un pour le succès et un pour l'échec
                    {                         // l'appel est un succès, on a récupérer un film
                        if(it.totalPages >= params.key){ //si on est pas encore à la fin
                            callback.onResult(it.movieList,params.key+1) //on renvoi la nouvelle page qui vien d'être chargée
                            networkState.postValue(NetworkState.LOADED) //on indique le chargement est effectué
                        }
                        else{ //si on est à la fin
                            networkState.postValue(NetworkState.ENDOFLIST) //on change notre networkstate pour indiqué que c'est la fin
                        }
                    },
                    { //l'appel est un échec
                        networkState.postValue(NetworkState.ERROR)
                        Log.e("MovieDataSource",it.message)
                    }
                )
        )
    }
}

Теперь я хотел бы разрешить пользователю искать фильмы по тексту.

Мой идеальный сценарий был бы следующим:

  • Пользователь запускает приложение и видит EditText и кнопку (представление корзины пусто, поэтому PagedList тоже пуст).

  • Он входит текст и щелчок по кнопке, PagedList полностью очищается, поэтому, если предыдущий mov ie был показан, они исчезли.

  • После очистки мы вызываем DataSource для вызова запроса и получить список фильмов.

  • Мы обновляем адаптер новым PagedList.

В настоящее время, если я изменяю loadInit и loadAfter с помощью "searchMov *" 1074 * ", он работает, но текст для поиска вводится непосредственно в DataSource и не может быть изменен, так что это похоже на известные фильмы ... th Пользователь искал фильмы со звездочкой в ​​названии, но не может это изменить ...

enter image description here

Мой вопрос:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...