Better way to change listId/query in data source paging - android

I can't find a better way to change listId of my VideosDataSource methods like load initial. I'm using view pager so it load 2 fragment at a time that's why i can't use getter/setter to set listId of my data source.
here my data source class:
class VideosDataSource(
private val networkService: NetworkService,
private val compositeDisposable: CompositeDisposable
): PageKeyedDataSource<String, Item>() {
var state: MutableLiveData<State> = MutableLiveData()
private var retryCompletable: Completable? = null
private var listId = "PL8fVUTBmJhHKEJjTNWn-ykf67rVrFWYtC"
override fun loadInitial(params: LoadInitialParams<String>, callback: LoadInitialCallback<String, Item>) {
updateState(State.LOADING)
compositeDisposable.add(
networkService.getPlaylistVideos(listId
,""
,Constants.API_KEY)
.subscribe( { response ->
updateState(State.DONE)
callback.onResult(response.items, response.prevPageToken, response.nextPageToken)
},
{
updateState(State.ERROR)
setRetry(Action { loadInitial(params,callback) })
}
)
)
}
here i'm trying to change listId in my view pager fragment.
my data source factory:
class VideosDataSourceFactory(
private val compositeDisposable: CompositeDisposable,
private val networkService: NetworkService
): DataSource.Factory<String, Item>() {
val videosDataSourceLiveData = MutableLiveData<VideosDataSource>()
override fun create(): DataSource<String, Item> {
val videosDataSource = VideosDataSource(networkService,
compositeDisposable)
videosDataSourceLiveData.postValue(videosDataSource)
return videosDataSource
}
}
my view model:
class PageViewModel(application: Application) :
AndroidViewModel(application) {
//paging
private val networkService = NetworkService.getService()
var videosList: LiveData<PagedList<Item>>
private val compositeDisposable = CompositeDisposable()
private val pageSize = 50
private val videosDataSourceFactory: VideosDataSourceFactory
init {
//paging
videosDataSourceFactory = VideosDataSourceFactory(compositeDisposable, networkService)
val config = PagedList.Config.Builder()
.setPageSize(pageSize)
.setInitialLoadSizeHint(pageSize)
.setEnablePlaceholders(false)
.build()
videosList = LivePagedListBuilder<String, Item>(videosDataSourceFactory, config).build()
}
In fragmnet onClick() i want to send listId to data source.
Whit some approaches like getter/setter i can be able to send listId to data source but view pager create two or three fragment at a time the value is override in getter/setter.
I'm looking for the better way to send data from fragment to data source.

I did it. The idea of constructor is good but not works for me because of init method of view model called before setting the id to factory constructor. But thanks to mr.pskink for his comments to use parameter constructor.
So here's how i did it.
In fragment i set list to view model.
companion object {
private const val ARG_SECOND = "arg_second"
#JvmStatic
fun newInstance(second: Array<Pair<String, String>>): PlaceholderFragment {
return PlaceholderFragment().apply {
arguments = Bundle().apply {
putString(ARG_SECOND,second[0].second)
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(PageViewModel::class.java).apply {
setListId(arguments?.getString(ARG_SECOND) ?: "")
}
}
In view model i create a method:
fun setListId(listId: String){
videosDataSourceFactory.listId = listId
}
In data source factory i craete a variable:
val videosDataSourceLiveData = MutableLiveData<VideosDataSource>()
lateinit var listId:String
override fun create(): DataSource<String, Item> {
val videosDataSource = VideosDataSource(networkService, compositeDisposable,listId)
videosDataSourceLiveData.postValue(videosDataSource)
return videosDataSource
}
And then get it via constructor of data source here:
class VideosDataSource(
private val networkService: NetworkService,
private val compositeDisposable: CompositeDisposable,
private val listId: String
): PageKeyedDataSource<String, Item>() {
var state: MutableLiveData<State> = MutableLiveData()
private var retryCompletable: Completable? = null
override fun loadInitial(params: LoadInitialParams<String>, callback:
LoadInitialCallback<String, Item>) {
updateState(State.LOADING)
compositeDisposable.add(
networkService.getPlaylistVideos(listId
,""
,Constants.API_KEY)
.subscribe( { response ->
updateState(State.DONE)
callback.onResult(response.items, response.prevPageToken, response.nextPageToken)
},
{
updateState(State.ERROR)
setRetry(Action { loadInitial(params,callback) })
}
)
)
}

Related

How to convert a Flow<CustomType> to StateFlow<UIState>? - Android Kotlin

I am learning Android development, and as I saw in many topics, people were talking about that LiveData is not recommended to use anymore. I mean it's not up-to-date, and we should use Flows instead.
I am trying to get data from ROOM database with Flows and then convert them to StateFlow because as I know they are observables, and I also want to add UI states to them. Like when I get data successfully, state would change to Success or if it fails, it changes to Error.
I have a simple app for practicing. It stores subscribers with name and email, and show them in a recyclerview.
I've checked a lot of sites, how to use stateIn method, how to use StateFlows and Flows but didn't succeed. What's the most optimal way to do this?
And also what's the proper way of updating recyclerview adapter? Is it okay to change it all the time in MainActivity to a new adapter?
Here is the project (SubscriberViewModel.kt - line 30):
Project link
If I am doing other stuff wrong, please tell me, I want to learn. I appreciate any kind of help.
DAO:
import androidx.room.*
import kotlinx.coroutines.flow.Flow
#Dao
interface SubscriberDAO {
#Insert
suspend fun insertSubscriber(subscriber : Subscriber) : Long
#Update
suspend fun updateSubscriber(subscriber: Subscriber) : Int
#Delete
suspend fun deleteSubscriber(subscriber: Subscriber) : Int
#Query("DELETE FROM subscriber_data_table")
suspend fun deleteAll() : Int
#Query("SELECT * FROM subscriber_data_table")
fun getAllSubscribers() : Flow<List<Subscriber>>
#Query("SELECT * FROM subscriber_data_table WHERE :id=subscriber_id")
fun getSubscriberById(id : Int) : Flow<Subscriber>
}
ViewModel:
class SubscriberViewModel(private val repository: SubscriberRepository) : ViewModel() {
private var isUpdateOrDelete = false
private lateinit var subscriberToUpdateOrDelete: Subscriber
val inputName = MutableStateFlow("")
val inputEmail = MutableStateFlow("")
private val _isDataAvailable = MutableStateFlow(false)
val isDataAvailable : StateFlow<Boolean>
get() = _isDataAvailable
val saveOrUpdateButtonText = MutableStateFlow("Save")
val deleteOrDeleteAllButtonText = MutableStateFlow("Delete all")
/*
//TODO - How to implement this as StateFlow<SubscriberListUiState> ??
//private val _subscribers : MutableStateFlow<SubscriberListUiState>
//val subscribers : StateFlow<SubscriberListUiState>
get() = _subscribers
*/
private fun clearInput() {
inputName.value = ""
inputEmail.value = ""
isUpdateOrDelete = false
saveOrUpdateButtonText.value = "Save"
deleteOrDeleteAllButtonText.value = "Delete all"
}
fun initUpdateAndDelete(subscriber: Subscriber) {
inputName.value = subscriber.name
inputEmail.value = subscriber.email
isUpdateOrDelete = true
subscriberToUpdateOrDelete = subscriber
saveOrUpdateButtonText.value = "Update"
deleteOrDeleteAllButtonText.value = "Delete"
}
fun saveOrUpdate() {
if (isUpdateOrDelete) {
subscriberToUpdateOrDelete.name = inputName.value
subscriberToUpdateOrDelete.email = inputEmail.value
update(subscriberToUpdateOrDelete)
} else {
val name = inputName.value
val email = inputEmail.value
if (name.isNotBlank() && email.isNotBlank()) {
insert(Subscriber(0, name, email))
}
inputName.value = ""
inputEmail.value = ""
}
}
fun deleteOrDeleteAll() {
if (isUpdateOrDelete) {
delete(subscriberToUpdateOrDelete)
} else {
deleteAll()
}
}
private fun insert(subscriber: Subscriber) = viewModelScope.launch(Dispatchers.IO) {
repository.insert(subscriber)
_isDataAvailable.value = true
}
private fun update(subscriber: Subscriber) = viewModelScope.launch(Dispatchers.IO) {
repository.update(subscriber)
clearInput()
}
private fun delete(subscriber: Subscriber) = viewModelScope.launch(Dispatchers.IO) {
repository.delete(subscriber)
clearInput()
}
private fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
repository.deleteAll()
//_subscribers.value = SubscriberListUiState.Success(emptyList())
_isDataAvailable.value = false
}
sealed class SubscriberListUiState {
data class Success(val list : List<Subscriber>) : SubscriberListUiState()
data class Error(val msg : String) : SubscriberListUiState()
}
}
MainActivity:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: SubscriberViewModel
private lateinit var viewModelFactory: SubscriberViewModelFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val dao = SubscriberDatabase.getInstance(application).subscriberDAO
viewModelFactory = SubscriberViewModelFactory(SubscriberRepository(dao))
viewModel = ViewModelProvider(this, viewModelFactory)[SubscriberViewModel::class.java]
binding.viewModel = viewModel
binding.lifecycleOwner = this
initRecycleView()
}
private fun initRecycleView() {
binding.recyclerViewSubscribers.layoutManager = LinearLayoutManager(
this#MainActivity,
LinearLayoutManager.VERTICAL, false
)
displaySubscribersList()
}
private fun displaySubscribersList() {
/*
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.subscribers.collect { uiState ->
when (uiState) {
is SubscriberViewModel.SubscriberListUiState.Success -> {
binding.recyclerViewSubscribers.adapter = SubscriberRecyclerViewAdapter(uiState.list) {
subscriber: Subscriber -> listItemClicked(subscriber)
}
}
is SubscriberViewModel.SubscriberListUiState.Error -> {
Toast.makeText(applicationContext,uiState.msg, Toast.LENGTH_LONG).show()
}
}
}
}
}*/
}
private fun listItemClicked(subscriber: Subscriber) {
Toast.makeText(this, "${subscriber.name} is selected", Toast.LENGTH_SHORT).show()
viewModel.initUpdateAndDelete(subscriber)
}
}
You can convert a Flow type into a StateFlow by using stateIn method.
private val coroutineScope = CoroutineScope(Job())
private val flow: Flow<CustomType>
val stateFlow = flow.stateIn(scope = coroutineScope)
In order to transform the CustomType into UIState, you can use the transformLatest method on Flow. It will be something like below:
stateFlow.transformLatest { customType ->
customType.toUiState()
}
Where you can create an extension function to convert CustomType to UiState like this:
fun CustomType.toUiState() = UiState(
x = x,
y = y... and so on.
)

How pass parameter in hight order suspend function with invoke

I want call hight order suspend function from other class with parameter and i don't know how.
class CharactersListViewModel : ViewModel() {
private val dataSourceFactory =
PageKeyDataSourceFactory(
scope = viewModelScope,
request = suspend {createRequest(0)
}
)
private suspend inline fun createRequest(offset : Int): MutableList<CharacterItem> {
val repository = Injection.provideMarvelRepository()
val response = repository.getCharacters(
offset = offset,
limit = PAGE_MAX_ELEMENTS
)
return CharacterItemMapper().map(response).toMutableList()
}
other class
class PageKeyDataSourceFactory<Value>(
private val scope: CoroutineScope,
private var request: suspend () -> MutableList<Value>
) : DataSource.Factory<Int, Value>() {
private var dataSource: PageKeyDataSource<Value>? = null
override fun create(): DataSource<Int, Value> {
dataSource = PageKeyDataSource(request = request, scope)
sourceLiveData.postValue(dataSource)
return dataSource as PageKeyDataSource<Value>
}
and class here i call function
in loadAfter function comes a params that I want to be used to call request.invoke()
class PageKeyDataSource<Value>(
private val request: suspend() -> MutableList<Value>,
private val scope: CoroutineScope,
) : PageKeyedDataSource<Int, Value>() {
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Value>) {
scope.launch(
CoroutineExceptionHandler { _, _ ->
retry = {
loadAfter(params, callback)
}
networkState.postValue(NetworkState.Error(true))
}
) {
val data = request.invoke()
callback.onResult(data, params.key + PAGE_MAX_ELEMENTS)
networkState.postValue(NetworkState.Success(true, data.isEmpty()))
}
}
}
class PageKeyDataSource<Value>(private val request: suspend (yourParams:YourType) -> MutableList<Value>) {
...
// some code
val list:MutableList<Value> = request.invoke(yourParams)
}

Avoid redundant code when having inheritance in Kotlin

I have following base class :
abstract class BaseViewModel<T, R>(private val schedulerProvider: BaseSchedulerProvider) :
ViewModel() {
private val compositeDisposable = CompositeDisposable()
private val _liveData = MutableLiveData<Resource<T>>()
val liveData: LiveData<Resource<T>>
get() = _liveData
protected abstract val requestObservable: Observable<R>
protected abstract fun getSuccessResult(it: R): T
fun sendRequest() {
_liveData.value = Resource.Loading()
composeObservable { requestObservable }
.subscribe({
_liveData.postValue(Resource.Success(getSuccessResult(it)))
}) {
_liveData.postValue(Resource.Failure(it.localizedMessage))
Timber.e(it)
}.also { compositeDisposable.add(it) }
}
}
And here is child class implementation :
class MainViewModel(
api: PokemonService,
schedulerProvider: BaseSchedulerProvider
) : BaseViewModel<List<Pokemon>, List<NamedResponseModel>>(schedulerProvider) {
override val requestObservable: Observable<List<NamedResponseModel>> =
api.getPokemonList(LIMIT).map { it.results }
override fun getSuccessResult(it: List<NamedResponseModel>): List<Pokemon> = it.asDomainModel()
init {
sendRequest()
}
}
As you see I put init block in child classes to sendRequest() which is a redundant. If I move init block to parent class, it will crash since api is null because init block of parent is called before constructor of child.
Is there any solution to move sendRequest() to parent and avoid redundant in child classes?
Source code can be found : https://github.com/AliRezaeiii/Pokemon
I think you need to change the design of your inheritance. To get the child items to be executed in the parent's initialization, you need to pass the object to the parent constructor.
Here is an example:
abstract class Base(protected val name: String) {
init {
println(name)
}
}
class CBase(private val s: String) : Base(s) {}
fun main() {
CBase("Hello");
}
In your case, which I haven't tested yet:
abstract class BaseViewModel<T, R>(
private val schedulerProvider: BaseSchedulerProvider,
protected val requestObservable: Observable<R>):
ViewModel() {
private val compositeDisposable = CompositeDisposable()
private val _liveData = MutableLiveData<Resource<T>>()
val liveData: LiveData<Resource<T>>
get() = _liveData
protected abstract fun getSuccessResult(it: R): T
fun sendRequest() {
_liveData.value = Resource.Loading()
composeObservable { requestObservable }
.subscribe({
_liveData.postValue(Resource.Success(getSuccessResult(it)))
}) {
_liveData.postValue(Resource.Failure(it.localizedMessage))
Timber.e(it)
}.also { compositeDisposable.add(it) }
}
init {
sendRequest()
}
}
class MainViewModel(
api: PokemonService,
schedulerProvider: BaseSchedulerProvider
) : BaseViewModel<List<Pokemon>, List<NamedResponseModel>>(
schedulerProvider,
api.getPokemonList(LIMIT).map { it.results }
) {
override fun getSuccessResult(it: List<NamedResponseModel>): List<Pokemon> = it.asDomainModel()
}
Here, you can still access the variable requestObservable at the parent's contructor because it is initialized at the constructor parameter, not as an abstract property.
Let me know how it works for you.

How to combine my own DataDource and BoundaryCallback?

I create application based on the Database + Network paging and GitHub rest api.
Using various tutorials, I came to the conclusion that when creating the LivePagedListBuilder in ViewModel, I must pass my query retrieving data from Room, to make it works then with BoundaryCallback.
This query in my code looks like this:
#Query("SELECT * from repositories_table ORDER BY name DESC")
fun getPagedRepos(): DataSource.Factory<Int,Repository>
and its equivalent in the repository:
fun getPagedRepos(): DataSource.Factory<Int, Repository> {
return repositoriesDao.getPagedRepos()
}
However I would like to combine this with my own DataSource, not default one, which would also work with retrofitting data fetching.
Below are the relevant parts of my application:
DataSource
class ReposDataSource(private val contactsRepository: ContactsRepository,
private val scope: CoroutineScope, application: Application): PageKeyedDataSource<Int, Repository>() {
private var supervisorJob = SupervisorJob()
private val PREFS_NAME = "Paging"
private val sharedPref: SharedPreferences = application.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
override fun loadInitial(
params: LoadInitialParams<Int>,
callback: LoadInitialCallback<Int, Repository>
) {
Log.i("RepoBoundaryCallback", "initialTriggered")
val currentPage = 1
val nextPage = currentPage + 1
executeQuery(currentPage, params.requestedLoadSize) {
callback.onResult(it, null, nextPage)
}
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Repository>) {
val currentPage = params.key
val nextPage = currentPage + 1
executeQuery(currentPage, params.requestedLoadSize) {
callback.onResult(it, nextPage)
}
}
override fun invalidate() {
super.invalidate()
supervisorJob.cancelChildren()
}
private fun executeQuery(page: Int, perPage: Int, callback: (List<Repository>) -> Unit) {
scope.launch(getJobErrorHandler() + supervisorJob) {
savePage("current_page", page)
val repos = contactsRepository.fetchPagedRepos(page, perPage)
callback(repos)
}
}
private fun getJobErrorHandler() = CoroutineExceptionHandler { _, e ->
Log.e(ReposDataSource::class.java.simpleName, "An error happened: $e")
}
private fun savePage(KEY_NAME: String, value: Int){
Log.i("RepoBoundaryCallback", value.toString())
val editor: SharedPreferences.Editor = sharedPref.edit()
editor.putInt(KEY_NAME, value)
editor.commit()
}
}
BoundaryCallback
class RepoBoundaryCallback (val repository: ContactsRepository, application: Application) :
PagedList.BoundaryCallback<Repository?>() {
private var callbackJob = Job()
private val coroutineScope = CoroutineScope(
callbackJob + Dispatchers.Main )
private val PREFS_NAME = "Paging"
private val sharedPref: SharedPreferences = application.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
override fun onZeroItemsLoaded() {
Log.i("RepoBoundaryCallback", "onzeroitemstriggered")
super.onZeroItemsLoaded()
fetchUsers(1)
}
override fun onItemAtEndLoaded(itemAtEnd: Repository) {
Log.i("RepoBoundaryCallback", "onitematendriggered")
super.onItemAtEndLoaded(itemAtEnd)
fetchUsers(getCurrentPage("current_page"))
}
private fun fetchUsers(page: Int) {
coroutineScope.launch {
try {
var newRepos = RepoApi.retrofitService.fetchRepos(page)
insertRepoToDb(newRepos)
}
catch (e: Exception){
Log.i("RepoBoundaryCallback", e.toString())
}
}
}
private suspend fun insertRepoToDb(reposList: List<Repository>){
reposList.forEach{repository.insertRepo(it)}
}
private fun getCurrentPage(KEY_NAME: String): Int{
return sharedPref.getInt(KEY_NAME, 0)
}
}
Api query
interface RepoApiService {
#GET("/orgs/google/repos")
suspend fun fetchRepos(#Query("page") page: Int,
#Query("per_page") perPage: Int = 15): List<Repository>
}
ViewModel
class RepositoryViewModel (application: Application) : AndroidViewModel(application) {
companion object{
private const val TAG = "RepositoryViewModel"
}
//var reposList: LiveData<PagedList<Repository>>
private var repoBoundaryCallback: RepoBoundaryCallback? = null
var reposList: LiveData<PagedList<Repository>>? = null
private val repository: ContactsRepository
private var viewModelJob = Job()
private val coroutineScope = CoroutineScope(
viewModelJob + Dispatchers.Main )
init {
val contactsDao = ContactsRoomDatabase.getDatabase(application, viewModelScope).contactsDao()
val contactsExtrasDao = ContactsRoomDatabase.getDatabase(application, viewModelScope).contactsExtrasDao()
val repositoriesDao = ContactsRoomDatabase.getDatabase(application, viewModelScope).repositoriesDao()
val service = RepoApi.retrofitService
repository = ContactsRepository(contactsDao, contactsExtrasDao, repositoriesDao, service)
initializedPagedListBuilder(application)
}
private fun initializedPagedListBuilder(application: Application) {
repoBoundaryCallback = RepoBoundaryCallback(
repository, application
)
val pagedListConfig = PagedList.Config.Builder()
//.setPrefetchDistance(5)
//.setInitialLoadSizeHint(20)
.setEnablePlaceholders(true)
.setPageSize(15).build()
reposList = LivePagedListBuilder(
repository.getPagedRepos(),
pagedListConfig
).setBoundaryCallback(repoBoundaryCallback).build()
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}
In addition, I save the relevant pages in SharedPreferences in the DataSource to then use it in the corresponding BoundaryCallback functions.
So how do you link your own DataSource to BoundaryCallback with Room and Retrofit? I will be grateful for any help.
BoundaryCallback is responsible for triggering invalidation on your current generation of DataSource. With DataSource.Factory generated by Room, this is automatically handled for you as Room will invalidate any DataSource it generates that is affected by writes to DB. This is why a DataSource.Factory is necessary over a single DataSource. Paging sees a single instance of DataSource as a "snapshot" of static data. If the data it's supposed to be loading changes in any way you must call DataSource.invalidate() to allow DataSource.Factory to generate a new up-to-date snapshot.
Since you're implementing your own DataSource, you'll also need to implement a DataSource.Factory and call invalidate() from your BoundaryCallback (doesn't necessarily need to be in the same class, but invalidate() must be triggered when your BoundaryCallback writes updates).

PagingLibrary (LivePageListBuilder) does not not return PagedData

I'm using the Android Paging Library and I am having trouble getting results back from my DataSource.
I am basically getting an empty LiveData from my LivePagedListBuilder. Not sure what I am doing wrong. My loadInitial is also never called.
Here is my ViewModel:
class WorkPackagesViewModel: ViewModel() {
companion object {
const val PAGING_LIMIT_DEFAULT = 10
}
var workPackages: LiveData<PagedList<WorkPackagesQuery.WorkPackage>>? = null
fun fetchWorkPackages(isOnline: Boolean, workWeeK: Configurations.AppWeek, pagingStart: Int?, pagingLimit: Int?) {
val myConfig = PagedList.Config.Builder()
.setInitialLoadSizeHint(pagingLimit ?: PAGING_LIMIT_DEFAULT)
.setPageSize(pagingLimit ?: PAGING_LIMIT_DEFAULT )
.build()
val workPackageDataFactory = WorkPackageDataFactory(workWeeK)
workPackageDataFactory.create()
workPackages = LivePagedListBuilder(workPackageDataFactory, myConfig)
.setInitialLoadKey(pagingStart)
.setFetchExecutor(Executors.newFixedThreadPool(5))
.build()
}
}
}
Here is my DataSource.Factory:
class WorkPackageDataFactory(
private val workWeek : Configurations.AppWeek
) : DataSource.Factory<Int, WorkPackagesQuery.WorkPackage>() {
private var mutableLiveData: MutableLiveData<WorkPackageDataSource>? = MutableLiveData()
override fun create(): DataSource<Int, WorkPackagesQuery.WorkPackage> {
val workPackageDataSource = WorkPackageDataSource(workWeek)
mutableLiveData?.postValue(workPackageDataSource)
return workPackageDataSource
}
}
Here is my datasource file:
class WorkPackageDataSource(
private val workWeek : Configurations.AppWeek
) : PageKeyedDataSource<Int, WorkPackagesQuery.WorkPackage>(), KoinComponent
{
val client : PipefighterApi by inject()
private var parentJob = Job()
private val coroutineContext: CoroutineContext get() = parentJob + Dispatchers.Main
private val scope = CoroutineScope(coroutineContext)
override fun loadInitial( //DOES NOT GET CALLED
params: LoadInitialParams<Int>,
callback: LoadInitialCallback<Int, WorkPackagesQuery.WorkPackage>
) {
scope.launch {
val workPackages = getWorkPackages(1,params.requestedLoadSize)?.workPackages() as MutableList<WorkPackagesQuery.WorkPackage>
callback.onResult(workPackages, null, params.requestedLoadSize + 1 )
}
}
override fun loadAfter(
params: LoadParams<Int>,
callback: LoadCallback<Int, WorkPackagesQuery.WorkPackage>
) {
scope.launch {
val workPackages = getWorkPackages(params.key,params.requestedLoadSize)?.workPackages() as MutableList<WorkPackagesQuery.WorkPackage>
var nextKey : Int? = null
if (params.key.plus(0) != workPackages.size) {
nextKey = params.key.plus(1)
}
callback.onResult(workPackages, nextKey )
}
}
override fun loadBefore(
params: LoadParams<Int>,
callback: LoadCallback<Int, WorkPackagesQuery.WorkPackage>
) {
}
suspend fun getWorkPackages(
initialPage : Int,
requestedLoadSize : Int
) : WorkPackagesQuery.Result? {
return withContext(Dispatchers.IO) {
async { client.workPackages(
workWeek.currentWeekStart(),
workWeek.currentWeekEnd(),
initialPage,
requestedLoadSize
) }.await().response
}
}
}
Here is my fragment
class WorkPackagesFragment : Fragment(), WorkPackagesRecyclerAdapter.OnClickWorkPackage {
companion object {
private const val VERTICAL_ITEM_SPACE = 30
const val PAGING_START = 1
const val PAGING_LIMIT = 10
}
private val workPackagesViewModel: WorkPackagesViewModel by viewModel()
private val mainViewModel : MainViewModel by sharedViewModel()
private val networkConnection: NetworkConnectionHelper by inject()
private lateinit var binding: FragmentWorkPackagesBinding
private lateinit var adapter: WorkPackagesRecyclerAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(
inflater,
com.bechtel.pf.R.layout.fragment_work_packages,
container,
false
)
mainViewModel.week.observe(this, Observer {
it ?: return#Observer
workPackagesViewModel.fetchWorkPackages(networkConnection.connected(), it, PAGING_START, PAGING_LIMIT)
})
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = workPackagesViewModel
workPackagesViewModel.workPackages?.observe(this, Observer {
it ?: return#Observer
adapter = WorkPackagesRecyclerAdapter(this)
adapter.submitList(it)
binding.workPackagesRecyclerView.adapter = adapter
adapter.notifyDataSetChanged()
})
return binding.root
}
}
I don't see where workPackages is observed, but I guess the problem is that you observe workPackages BEFORE you call fetchWorkPackages(). When fetchWorkPackages() is called, it creates a new LiveData and assigns the new one to workPackages. But you only observe to the old LiveData in workPackages before, so this new one doesn't have any active observers and therefore it never trigger loadInitial()
You can try to put fetchWorkPackages() in init {} in ViewModel, or set observe for workPackages AFTER calling fetchWorkPackages() in your Activity or Fragment

Categories

Resources