So I'm trying to implement mvvm architecture with navigation component, this code snipet works just fine.. the problem is that when I navigate from fragmentA to FragmentB there is no way to go back to FragmentA, its like the button is blocked (If I remove the ViewModel the navigation works fine so it has something to do with the ViewModel).
anyone have an idea of whats happening?
private fun initVewModel() {
myViewModel = MyViewModel()
myViewModel = ViewModelProviders
.of(this, createFor(myViewModel))
.get(MyViewModel::class.java)
}
private fun setupObservers() {
myViewModel.myData.observe(viewLifecycleOwner, Observer {
it?.let { resource ->
when (resource.status) {
Status.SUCCESS -> {
resource.data?.let {
val action =
FragmentADirections.actionFragmentAToFragmentB()
Navigation.findNavController(requireView()).navigate(action)
}
}
Status.ERROR -> {
//TODO show an error
loading.visibility = View.GONE
}
Status.LOADING -> {
loading.visibility = View.VISIBLE
}
}
}
})
}
This is my ViewModel
class MyViewModel : ViewModel() {
private val searchText = MutableLiveData<String>()
//This method is called on a search button click
fun setSearchText(text: String) {
searchText.value = text
}
val myData = searchText.switchMap { text ->
liveData(Dispatchers.IO) {
emit(Resource.loading(data = null))
try {
emit(Resource.success(data = dataOrigin.getData(text)))
} catch (exception: Exception) {
exception.printStackTrace()
emit(Resource.error(data = null, message = exception.message ?: "Error Occurred!"))
}
}
}
}
That's because your myData Observer will be immediately triggered with the latest data on back navigation and trigger the SUCCESS branch once again.
I recommend either using SharedFlows or checking out a SingleLiveEvent pattern for LiveData, where you can only consume an event once.
UPDATE:
A possible solution for the consumable pattern:
class Consumable<T>(private val event: T) {
private var isConsumed = false
fun consume(): T? {
if (isConsumed) return null
isConsumed = true
return event
}
fun peek() = event
}
Then in the ViewModel:
val event = MutableLiveData<Consumable<YourEventClass>>()
...
event.value = Consumable(XYEvent)
And you'd observe it like this:
viewModel.event.observe{ event ->
event.consume()?.let{
// Do your navigation here
}
}
Additionally you could write an extension function .consume(), for consumable livedatas, which unwraps the value
Related
There is a convenient wrapper that allows you to reduce the boilerplate when you work with LiveData - observeEvents.
open class Event<T>(value: T? = null) {
val liveData = MutableLiveData(value)
protected var hasBeenHandled = false
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled || liveData.value == null) {
null
} else {
hasBeenHandled = true
liveData.value
}
}
companion object {
fun <T> LifecycleOwner.observeEvents(event: Event<T>, body: (T?) -> Unit) {
event.liveData.observe(this) { body(event.getContentIfNotHandled()) }
}
}
}
class MutableEvent<T>(value: T? = null) : Event<T>(value) {
#MainThread
fun fireEvent(event: T) {
hasBeenHandled = false
liveData.value = event
}
#WorkerThread
fun postEvent(event: T) {
hasBeenHandled = false
liveData.postValue(event)
}
}
Next, we can see how to use it.
There is the following sealed class for specific events:
sealed class ProductEvent {
data class AddProduct(val data: SomeProduct) : ProductEvent()
data class RemoveProduct(val productId: String) : ProductEvent()
}
ViewModel code:
private val _productEvents = MutableEvent<ProductEvent>()
val productEvents = _productEvents
private fun addProduct() {
val product: SomeProduct = repository.getProduct()
_productEvents.fireEvent(ProductEvent.AddProduct(product)
}
Activity/Fragment code:
observeEvents(viewModel.productEvents) { event ->
event?.let {
when(event) {
is ProductEvent.AddProduct -> // add product
is ProductEvent.RemoveProduct-> // remove product
}
}
}
Everything works fine, but there is one thing.
For example, when we use registerForActivityResult:
private val result = registerForActivityResult(ActivityResultContracts.StartActivityForResult())
{ result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.getIntExtra(SomeActivity.SOME_RESULT, 0)?.let {
// do work that will call ProductEvent
// viewModel.addProduct() - for example
}
}
}
When SomeActivity finishes and we return here, this code will run before LifecycleOwner will be active and because of that a subscriber will not be called.
There is a solution (lifecycleScope.launchWhenResumed), but the fact is that if we define our LiveData as usual:
// viewModel
private val _product = MutableLiveData<SomeProduct>()
val product = _product
// Activity/Fragment
viewModel.product.observe(lifecycleOwner) {}
then the subscriber will work as expected.
I would like to know what the difference. observeEvents is merely a wrapper that does the same thing, but for some reason works a little differently.
I m relatively new in kotlin flows and I m creating the Login Module using Flows in android. I have been stuck from past few days in flows as I m collecting it in ViewModels but I m facing problem when requesting with wrong Credentials its caching all the state. After entering the right credentials the user navigate to main Activity but the instance of the MainActivity is being created with every emitted State: Example(User Enter 3 wrong Credential and 1 Right Credential: 4 Instance of MainActivity Created). So, Is there any way that I can cancel the previous emit and only show the latest request. I m using the collectLatest as well but its not working too. Below is the code.
LoginActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(mViewBinding.root)
loginListener()
}
override fun onStart() {
super.onStart()
initViews()
handleNetworkChanges()
}
private fun observeLogin() {
lifecycleScope.launchWhenCreated {
mViewModel.loginCredentials.collect { state ->
when(state){
is State.Loading -> {
showLoading()
}
is State.Success -> {
Timber.d("I m in Success" + state.data)
val intent = Intent(this#LoginActivity,MainActivity::class.java)
startActivity(intent)
closeLoading()
finish()
}
is State.Error -> {
val errorResponse = gson.fromJson(state.message,LoginResponse::class.java)
showToast(errorResponse.messages)
closeLoading()
}
}
}
}
}
private fun loginListener() {
mViewBinding.neumorphButtonSignIn.setOnClickListener {
observeLogin()
phoneNumber = mViewBinding.edtPhoneNumber.text.toString()
pin = mViewBinding.oldPIN.text.toString()
if (phoneNumber.isValidPhone()) {
sendLoginCredentials(phoneNumber ,pin)
}
else {
mViewBinding.edtPhoneNumber.snack("Please Enter valid phone number") {
action("ok") {
dismiss()
}
}
}
}
}
private fun sendLoginCredentials(phoneNumber: String , pin: String) = mViewModel.postLoginCredentials("03XXXX" , "1234")
LoginViewModel
#ExperimentalCoroutinesApi
#HiltViewModel
class LoginViewModel #Inject constructor(
private val loginRepository: LoginRepository,
) : ViewModel() {
private val _loginCredentials: MutableStateFlow<State<LoginResponse>> = MutableStateFlow(State.Empty())
val loginCredentials: StateFlow<State<LoginResponse>> get() = _loginCredentials
fun postLoginCredentials(phoneNumber: String, pin: String) {
Timber.d("postLoginCredentials: $phoneNumber + $pin")
_loginCredentials.value = State.loading()
viewModelScope.launch {
loginRepository.login(LoginRequest(phoneNumber,pin))
.map { response -> State.fromResource(response) }
.collect{state -> _loginCredentials.value = state }
}
}
}
LoginRepository
class LoginRepository #Inject constructor(
private val apiInterface: APIInterface
) {
fun login(loginRequest: LoginRequest): Flow<ResponseAPI<LoginResponse>> {
return object : NetworkBoundRepository<LoginRequest, LoginResponse>() {
override suspend fun fetchFromRemote(): Response<LoginResponse> = apiInterface.createLoginRequest(
loginRequest
)
}.asFlow()
}
NetworkBoundRepository
abstract class NetworkBoundRepository<RESULT, REQUEST> {
fun asFlow() = flow<ResponseAPI<REQUEST>> {
val apiResponse = fetchFromRemote()
val remotePosts = apiResponse.body()
if (apiResponse.isSuccessful && remotePosts != null) {
emit(ResponseAPI.Success(remotePosts))
} else {
// Something went wrong! Emit Error state.
emit(ResponseAPI.Failed(apiResponse.errorBody()!!.string()))
}
}.catch { e ->
e.printStackTrace()
emit(ResponseAPI.Failed("Network error! Can't get latest posts."))
}
#MainThread
protected abstract suspend fun fetchFromRemote(): Response<REQUEST>
}
Is there any way that I can create One Instance of MainAcitivity while ignoring the older emitted Responses? Any Operator which can work. Any help in this regard is highly appreciated. Thanks.
Actually, I was calling the observeLogin() from the login click Listener which was creating this mess in my project when I move this to onCreate(). Everything works the way as intended. So, posting this for newbie that won't stuck into this.
I have this code in my view model
class SearchViewModel #ViewModelInject constructor(jobUseCase: JobUseCase) : ViewModel() {
val job = jobUseCase.getSearchJob(...).asLiveData()
}
but thegetSearchJob() need the parameter to fill. and this where I use the job in the activity
private fun getSearchData() {
searchViewModel.job.observe(this, { job ->
if (job != null) {
when (job) {
is Resource.Loading -> binding.progressBar.visibility = View.VISIBLE
is Resource.Success -> {
binding.progressBar.visibility = View.GONE
jobAdapter.setData(job.data)
}
is Resource.Error -> {
binding.progressBar.visibility = View.GONE
}
}
}
})
}
but I don't know how to fill the getSearchJob() parameter in the val. or if there is any idea please help me.
I think it would be simpler for you to have a simple MutableLiveData instance, from which you can expose a regular liveData that can be exposed to the activity, and a onTextChanged Listener simply updates the viewModel, jobUseCase.getSearchJob(...) gets the data, and then you can set the new value to MutableLiveData instance.
if you need something more elegant, then let jobUseCase.getSearchJob(...) take in a flow instance that you can pass in data to which in turn fires off your job livedata
Simple Mutable livedata based example
class SearchViewModel1 constructor(private val jobUseCase: JobUseCase) : ViewModel() {
private val _internalMutableLiveData = MutableLiveData<String>()
private val publicLiveData: LiveData<String> = _internalMutableLiveData
fun updateSearchTerm(term: String) {
viewModelScope.launch {
val result = jobUseCase.getSearchJob(term)
_internalMutableLiveData.value = result
}
}
}
class JobUseCase {
suspend fun getSearchJob(term: String): String {
// fetch Data here
return ""
}
}
My problem is, that when I try to get a document out of my database, that this document aka the object is always null. I only have this problem when I use Kotlin Coroutines to get the document out of my database. Using the standard approach with listeners do work.
EmailRepository
interface EmailRepository {
suspend fun getCalibratePrice(): Flow<EmailEntity?>
suspend fun getRepairPrice(): Flow<EmailEntity?>
}
EmailRepository Implementation
class EmailRepositoryImpl #Inject constructor(private val db: FirebaseFirestore) : EmailRepository {
fun hasInternet(): Boolean {
return true
}
// This works! When using flow to write a document, the document is written!
override fun sendEmail(email: Email)= flow {
emit(EmailStatus.loading())
if (hasInternet()) {
db.collection("emails").add(email).await()
emit(EmailStatus.success(Unit))
} else {
emit(EmailStatus.failed<Unit>("No Email connection"))
}
}.catch {
emit(EmailStatus.failed(it.message.toString()))
}.flowOn(Dispatchers.Main)
// This does not work! "EmailEntity" is always null. I checked the document path!
override suspend fun getCalibratePrice(): Flow<EmailEntity?> = flow {
val result = db.collection("emailprice").document("Kalibrieren").get().await()
emit(result.toObject<EmailEntity>())
}.catch {
}.flowOn(Dispatchers.Main)
// This does not work! "EmailEntity" is always null. I checked the document path!
override suspend fun getRepairPrice(): Flow<EmailEntity?> = flow {
val result = db.collection("emailprice").document("Reparieren").get().await()
emit(result.toObject<EmailEntity>())
}.catch {
}.flowOn(Dispatchers.Main)
}
Viewmodel where I get the data
init {
viewModelScope.launch {
withContext(Dispatchers.IO) {
if (subject.value != null){
when(subject.value) {
"Test" -> {
emailRepository.getCalibratePrice().collect {
emailEntity.value = it
}
}
"Toast" -> {
emailRepository.getRepairPrice().collect {
emailEntity.value = it
}
}
}
}
}
}
}
private val emailEntity = MutableLiveData<EmailEntity?>()
private val _subject = MutableLiveData<String>()
val subject: LiveData<String> get() = _subject
Fragment
#AndroidEntryPoint
class CalibrateRepairMessageFragment() : EmailFragment<FragmentCalibrateRepairMessageBinding>(
R.layout.fragment_calibrate_repair_message,
) {
// Get current toolbar Title and send it to the next fragment.
private val toolbarText: CharSequence by lazy { toolbar_title.text }
override val viewModel: EmailViewModel by navGraphViewModels(R.id.nav_send_email) { defaultViewModelProviderFactory }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Here I set the data from the MutableLiveData "subject". I don't know how to do it better
viewModel.setSubject(toolbarText.toString())
}
}
One would say, that the Firebase rules are the problems here, but that should not be the case here, because the database is open and using the listener approach does work.
I get the subject.value from my CalibrateRepairMessageFragment. When I don't check if(subject.value != null) I get a NullPointerException from my init block.
I will use the emailEntitiy only in my viewModel and not outside it.
I appreciate every help, thank you.
EDIT
This is the new way I get the data. The object is still null! I've also added Timber.d messages in my suspend functions which also never get executed therefore flow never throws an error.. With this new approach I don't get a NullPointerException anymore
private val emailEntity = liveData {
when(subject.value) {
"Test" -> emailRepository.getCalibratePrice().collect {
emit(it)
}
"Toast" -> emailRepository.getRepairPrice().collect {
emit(it)
}
// Else block is never executed, therefore "subject.value" is either Test or toast and the logic works. Still error when using flow!
else -> EmailEntity("ERROR", 0F)
}
}
I check if the emailEntity is null or not with Timber.d("EmailEntity is ${emailEntity.value}") in one of my functions.
I then set the price with val price = MutableLiveData(emailEntity.value?.basePrice ?: 1000F) but because emailentity is null the price is always 1000
EDIT 2
I have now further researched the problem and made a big step forward. When observing the emailEntity from a fragment like CalibrateRepairMessageFragment the value is no longer null.
Furthermore, when observing emailEntity the value is also not null in viewModel, but only when it is observed in one fragment! So how can I observe emailEntity from my viewModel or get the value from my repository and use it in my viewmodel?
Okay, I have solved my problem, this is the final solution:
Status class
sealed class Status<out T> {
data class Success<out T>(val data: T) : Status<T>()
class Loading<T> : Status<T>()
data class Failure<out T>(val message: String?) : Status<T>()
companion object {
fun <T> success(data: T) = Success<T>(data)
fun <T> loading() = Loading<T>()
fun <T> failed(message: String?) = Failure<T>(message)
}
}
EmailRepository
interface EmailRepository {
fun sendEmail(email: Email): Flow<Status<Unit>>
suspend fun getCalibratePrice(): Flow<Status<CalibrateRepairPricing?>>
suspend fun getRepairPrice(): Flow<Status<CalibrateRepairPricing?>>
}
EmailRepositoryImpl
class EmailRepositoryImpl (private val db: FirebaseFirestore) : EmailRepository {
fun hasInternet(): Boolean {
return true
}
override fun sendEmail(email: Email)= flow {
Timber.d("Executed Send Email Repository")
emit(Status.loading())
if (hasInternet()) {
db.collection("emails").add(email).await()
emit(Status.success(Unit))
} else {
emit(Status.failed<Unit>("No Internet connection"))
}
}.catch {
emit(Status.failed(it.message.toString()))
}.flowOn(Dispatchers.Main)
// Sends status and object to viewModel
override suspend fun getCalibratePrice(): Flow<Status<CalibrateRepairPricing?>> = flow {
emit(Status.loading())
val entity = db.collection("emailprice").document("Kalibrieren").get().await().toObject<CalibrateRepairPricing>()
emit(Status.success(entity))
}.catch {
Timber.d("Error on getCalibrate Price")
emit(Status.failed(it.message.toString()))
}
// Sends status and object to viewModel
override suspend fun getRepairPrice(): Flow<Status<CalibrateRepairPricing?>> = flow {
emit(Status.loading())
val entity = db.collection("emailprice").document("Kalibrieren").get().await().toObject<CalibrateRepairPricing>()
emit(Status.success(entity))
}.catch {
Timber.d("Error on getRepairPrice")
emit(Status.failed(it.message.toString()))
}
}
ViewModel
private lateinit var calibrateRepairPrice: CalibrateRepairPricing
private val _calirateRepairPriceErrorState = MutableLiveData<Status<Unit>>()
val calibrateRepairPriceErrorState: LiveData<Status<Unit>> get() = _calirateRepairPriceErrorState
init {
viewModelScope.launch {
when(_subject.value.toString()) {
"Toast" -> emailRepository.getCalibratePrice().collect {
when(it) {
is Status.Success -> {
calibrateRepairPrice = it.data!!
_calirateRepairPriceErrorState.postValue(Status.success(Unit))
}
is Status.Loading -> _calirateRepairPriceErrorState.postValue(Status.loading())
is Status.Failure -> _calirateRepairPriceErrorState.postValue(Status.failed(it.message))
}
}
else -> emailRepository.getRepairPrice().collect {
when(it) {
is Status.Success -> {
calibrateRepairPrice = it.data!!
_calirateRepairPriceErrorState.postValue(Status.success(Unit))
}
is Status.Loading -> _calirateRepairPriceErrorState.postValue(Status.loading())
is Status.Failure -> _calirateRepairPriceErrorState.postValue(Status.failed(it.message))
}
}
}
price.postValue(calibrateRepairPrice.head!!.basePrice)
}
}
You can now observe the status in one of your fragments (but you dont need to!)
Fragment
viewModel.calibrateRepairPriceErrorState.observe(viewLifecycleOwner) { status ->
when(status) {
is Status.Success -> requireContext().toast("Price successfully loaded")
is Status.Loading -> requireContext().toast("Price is loading")
is Status.Failure -> requireContext().toast("Error, Price could not be loaded")
}
}
This is my toast extensions function:
fun Context.toast(text: String, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, text, duration).show()
}
I'm using Paging Library and Android Architecture Components. I simply want to observe pagedlist livedata and update my RecyclerView when there is a change.
I'm observing isLoadingLiveData, isEmptyLiveData and errorLiveData objects which are MediatorLiveData objects created in my ViewModel and observed in my fragment. And also observing resultLiveData which returns the fetched Gist list from remote.
In my ViewModel, I created a PagedList LiveData and whenever it's data changed, I wanted to update isLoadingLiveData, isEmptyLiveData, errorLiveData and PagedListAdapter. Therefore, I defined isLoadingLiveData, isEmptyLiveData, errorLiveData and resultLiveData as MediatorLiveData objects. I added resultLiveData as a source of these objects. So when resultLiveData has changed, these objects' onChanged methods will be called. And resultLiveData is depend on userNameLiveData, so when userNameLiveData has changed, allGistsLiveData will be called and it will fetch the data. For example when the user swipe the list, I'm setting userNameLiveData and doing network call again.
My ViewModel:
private val userNameLiveData = MutableLiveData<String>()
private var gists: LiveData<PagedList<Gist>>? = null
val allGistsLiveData: LiveData<PagedList<Gist>>
get() {
if (null == gists) {
gists = GistModel(NetManager(getApplication()), getApplication()).getYourGists(userNameLiveData.value!!).create(0,
PagedList.Config.Builder()
.setPageSize(PAGED_LIST_PAGE_SIZE)
.setInitialLoadSizeHint(PAGED_LIST_PAGE_SIZE)
.setEnablePlaceholders(PAGED_LIST_ENABLE_PLACEHOLDERS)
.build())
}
return gists!!
}
val resultLiveData = MediatorLiveData<LiveData<PagedList<Gist>>>().apply {
this.addSource(userNameLiveData) {
gists = null
this.value = allGistsLiveData
}
}
val isLoadingLiveData = MediatorLiveData<Boolean>().apply {
this.addSource(resultLiveData) { this.value = false }
}
val isEmptyLiveData = MediatorLiveData<Boolean>().apply {
this.addSource(resultLiveData) { this.value = false }
}
val errorLiveData = MediatorLiveData<Boolean>().apply {
this.addSource(resultLiveData) {
if (it == null) {
this.value = true
}
}
}
fun setUserName(userName: String) {
userNameLiveData.value = userName
}
and my fragment:
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.isEmptyLiveData.observe(this#YourGistsFragment, Observer<Boolean> { isVisible ->
emptyView.visibility = if (isVisible!!) View.VISIBLE else View.GONE
})
viewModel.isLoadingLiveData.observe(this#YourGistsFragment, Observer<Boolean> {
it?.let {
swipeRefreshLayout.isRefreshing = it
}
})
viewModel.errorLiveData.observe(this#YourGistsFragment, Observer<Boolean> {
it?.let {
showSnackBar(context.getString(R.string.unknown_error))
}
})
viewModel.setUserName("melomg")
viewModel.resultLiveData.observe(this#YourGistsFragment, Observer { it -> gistAdapter.setList(it?.value) })
}
override fun onRefresh() {
viewModel.setUserName("melomg")
}
my repository:
fun getYourGists(userName: String): LivePagedListProvider<Int, Gist> {
return remoteDataSource.getYourGists(userName, GitHubApi.getGitHubService(context))
}
my remoteDataSource:
fun getYourGists(username: String, dataProvider: GitHubService): LivePagedListProvider<Int, Gist> {
return object : LivePagedListProvider<Int, Gist>() {
override fun createDataSource(): GistTiledRemoteDataSource<Gist> = object : GistTiledRemoteDataSource<Gist>(username, dataProvider) {
override fun convertToItems(items: ArrayList<Gist>?): ArrayList<Gist>? {
return items
}
}
}
}
I tried to create this solution from this project. But my problem is
resultLiveData has been changing without waiting the network call response and therefore the result of response ignored and my ui is being updated before the data has arrived. Since resultLiveData is changing before request and therefore there is no data yet. Simply how can I observe pagedlist livedata?
Finally, I found a working solution but I don't think it is a best solution. So if you have any ideas please don't hesitate to answer. Here is my solution:
I gave up from wrapping my PagedList data with MediatorLiveData. I still leave my all other live data(isLoadingLiveData, isEmptyLiveData, errorLiveData) as MediatorLiveData but added their source when my gists PagedList LiveData initialized. Here is the code:
private var gists: LiveData<PagedList<Gist>>? = null
private val userNameLiveData = MutableLiveData<String>()
val isLoadingLiveData = MediatorLiveData<Boolean>()
val isEmptyLiveData = MediatorLiveData<Boolean>()
val errorLiveData = MediatorLiveData<ErrorMessage>()
val allGistsLiveData: LiveData<PagedList<Gist>>
get() {
if (gists == null) {
gists = GistModel(NetManager(getApplication()), getApplication()).getYourGists(userNameLiveData.value!!).create(0,
PagedList.Config.Builder()
.setPageSize(Constants.PAGED_LIST_PAGE_SIZE)
.setInitialLoadSizeHint(Constants.PAGED_LIST_PAGE_SIZE)
.setEnablePlaceholders(Constants.PAGED_LIST_ENABLE_PLACEHOLDERS)
.build())
isLoadingLiveData.addSource(gists) { isLoadingLiveData.value = false }
isEmptyLiveData.addSource(gists) {
if (it?.size == 0) {
isEmptyLiveData.value = true
}
}
errorLiveData.addSource(gists) {
if (it == null) {
errorLiveData.value = ErrorMessage(errorCode = ErrorCode.GENERAL_ERROR)
}
}
}
}
return gists!!
}
fun setUserName(userName: String) {
isLoadingLiveData.value = true
userNameLiveData.value = userName
gists = null
allGistsLiveData
}
There is a better way to do this. Look this example:
https://github.com/android/architecture-components-samples/tree/master/PagingWithNetworkSample
The trick is putting inside your DataSource a mutable live data like this:
enum class NetworkState { LOADING, LOADED, FAILURE }
val networkState = MutableLiveData<NetworkState>()
override fun loadInitial(params: LoadInitialParams<String>, callback: LoadInitialCallback<String, OrderService.Resource>) {
networkState.post(NetworkState.LOADING)
ApiClient.listMyObjects(
onSuccess = { list ->
networkState.post(NetworkState.LOADED)
callback.onResult(list)
}
onFailure = {
networkState.post(NetworkState.FAILURE)
}
)
}
Then you can listen to your paged list and network state with:
val myDataSource = MyDataSource.Factory().create()
myDataSource.toLiveData().observe(viewLifeCycle, Observer { pagedList ->
// Update your recycler view
myRecyclerViewAdapter.submitList(pagedList)
})
myDataSource.networkState.observe(viewLifeCycle, Observer { state ->
// Show errors, retry button, progress view etc. according to state
})