Best way to update a single element using Paging Library - android

Which is the best way to update a single element when using the new paging library?
Let's say we have the Paging with network google sample using the PageKeyedSubredditDataSource. Imagine we want to make a change of a single element of RedditPost. So, we want to check if it is in the list and if so, update it. The update should not be as easy as calling invalidate() which will make a call to the first page (maybe the RedditPost is in the 5th page. We don't want to update all elements, just one).

Please note that all this works over the Paging with network google sample. Although that, the idea is there.
#Sarquella helped me with this solution. Add this classes to your project. Basically we are extending ViewHolder to be LifeCycle Owner, as it is already done by default with Activities and Fragments.
The LifecycleViewHolder:
abstract class LifecycleViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView),
LifecycleOwner {
private val lifecycleRegistry = LifecycleRegistry(this)
fun onAttached() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
}
fun onDetached() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
}
override fun getLifecycle(): Lifecycle = lifecycleRegistry
}
LifecycleOwner is a single method interface that denotes that the class has a Lifecycle. You can find more information here.
The LifecyclePagedListAdapter:
abstract class LifecyclePagedListAdapter<T, VH : LifecycleViewHolder>(diffCallback: DiffUtil.ItemCallback<T>) :
PagedListAdapter<T, VH>(diffCallback) {
override fun onViewAttachedToWindow(holder: VH) {
super.onViewAttachedToWindow(holder)
holder.onAttached()
}
override fun onViewDetachedFromWindow(holder: VH) {
super.onViewDetachedFromWindow(holder)
holder.onDetached()
}
}
The LifecycleAdapter (in the case you need it):
abstract class LifecycleAdapter<VH : LifecycleViewHolder> :
RecyclerView.Adapter<VH>() {
override fun onViewAttachedToWindow(holder: VH) {
super.onViewAttachedToWindow(holder)
holder.onAttached()
}
override fun onViewDetachedFromWindow(holder: VH) {
super.onViewDetachedFromWindow(holder)
holder.onDetached()
}
}
Then, extends MyAdapter to LifecyclePagedListAdapter<MyEntity, LifecycleViewHolder>(MY_COMPARATOR) and MyViewHolder to LifecycleViewHolder(view). You'll have to complete your classes based on what we have changed, accordingly. Now we can observe to a liveData object on MyViewHolder class. So we can add this to MyViewHolder class (assuming you're using Dependency Injection). Basically, we'll do the same we do for Fragments or Activities:
private lateinit var myViewModel: MyViewModel
init {
(itemView.context as? AppCompatActivity)?.let{
myViewModel = ViewModelProviders.of(it).get(MyViewModel::class.java)
}
}
Then, inside the bind() method:
fun bind(myCell: MyEntity?) {
myViewModel.myLiveData.observe(this, Observer {
// Buala!! Check if it is the cell you want to change and update it.
if (it != null && myCell != null && it.id == myCell.id) {
updateCell(it)
}
})
}

Related

I'm wondering why I have to write "private" from SUPER NEW

I'm just studying Kotlin about MVP Model.
after I made MainPresenter class, I connected with interface, mainContract
and I faced a problem. I fixed it up but I can't explain it by myself so can you explain why I have to add 'private'?
MainPresenter
class mainPresenter : mainContract.Presenter {
private lateinit var mainModel: mainModel
private lateinit var view: mainContract.View
// here's private is that i ask u
override fun setView(view: mainContract.View) {
}
override fun setModel(model: mainModel) {
}
override fun onConfirm() {
}
}
//here is interface
interface mainContract {
interface Presenter {
fun setView(view: mainContract.View)
fun setModel(model: mainModel)
fun onConfirm()
}
interface View {
fun showButtonText(text: String)
}
}
This has been answered here:
https://javarevisited.blogspot.com/2012/03/private-in-java-why-should-you-always.html
Or even here: https://softwareengineering.stackexchange.com/questions/143736/why-do-we-need-private-variables
A short answer: "none of your business how this class works, here is what you have to know". If you are writing a single app, there is no real harm in making everything public. However - if you are writing a library, you might want to keep the same user facing functions, and modify non-user facing functions - internal API.

How to observe ViewModel that makes a request for a call back

I am trying to make a request to a library that gives me a call back.
Manager.getInstance().request(new CallBack())
I want to put this in a ViewModel so that I can observe it from the Activity.
class RequestViewModel : ViewModel, CallBack {
fun request() {
Manager.getInstance().request(this)
}
override fun onFinished(result : List<String>?) {
}
override fun onFailed() {
}
}
How can I make it so that I can observe when this has finished? I know I could make my Activity implement this CallBack, but I don't want to couple Activity to this.
Ideally this would be a LiveData or Observable.
If I understand the question correctly, you can submit the data acquired in onFinished method to the LiveData instance that should be observed by a view component, e.g.
class RequestViewModel : ViewModel, CallBack {
private val _liveData = MutableLiveData<SomeResult<List<String>>>
val liveData: LiveData<SomeResult<List<String>>> get() = _liveData
fun request() {
Manager.getInstance().request(this)
}
override fun onFinished(result : List<String>?) {
if (result != null) {
_liveData.postValue(SomeResult.success(result))
} else {
_liveData.postValue(SomeResult.failure())
}
}
override fun onFailed() {
_liveData.postValue(SomeResult.failure())
}
}
And somewhere in your object that corresponds to a view component:
viewModel.liveData.observe(lifecycleOwner, Observer<List<String>> {
handleResponse(it)
})
whereas lifecycleOwner typically is your AppCompatActivity or android.support.v4.Fragment inheritor.
I would advise you to decouple requesting from ViewModel and create a class called Repository to handle all the requests. In this class you could have a MutableLiveData object which can be observed and whenever new requested data is retrieved, use mutableLiveData.postValue(retrievedData) for MutableLiveData which notifies the observes about the new changes.
To read more about repository, you can follow these links:
Google's Guide to App Architecture
Codelab tutorial with Repository pattern

Reuse methods in Kotlin, Android

I need to open one activity from several different points in the app. Let's say from Settings fragment, Main Activity and Navigation drawer (fragment). I don't want to copy/paste the same method and the method is very specific, it should be exactly the same (because it registeres Firebase events). How to structure the code in effective way? Where to put this method? One idea is to have a global ActivityUtils.kt file with just methods and it would be used to store these methods. I'm interested in the alternatives and what are pros and cons of each.
I would create a companion object in the Activity you need to open:
class YourActivity : AppCompatActivity() {
companion object {
fun start(ctx: Context) {
// put your logic here (registering of Firebase events)
val i = Intent(ctx, YourActivity::class.java)
ctx.startActivity(i)
}
}
}
And call it from another activity:
YourActivity.start(this)
or from another fragment:
YourActivity.start(context)
Use an extension method:
fun Activity.doMyStuff() {}
That can be called from any class extending Activity:
doMyStuff()
Extension functions like this shouldn't go inside a class, but rather inside a file. So if you were to make an ActivityUtils.kt file, don't have any sort of class ActivityUtils {} stuff in it. The function(s) should just go directly into the file.
Why not to use MVP?
Like,
interface IView {
val context: Context
}
interface IPresenter {
fun launchActivity(view: IView)
}
class MyActivityModel
{
var key = "key"
/*some other data*/
fun getParcelableObject(): Parcelable
{
return /*some parcelable from model data*/
}
}
class MyActivity : AppCompatActivity(), IView
{
override val context: Context
get() = this
}
class MyActivityPresenter() : IPresenter
{
private var model: MyActivityModel = MyActivityModel()
override fun launchActivity(view: IView)
{
val intent = Intent(view.context, MyActivity::class.java)
intent.putExtra(model.key, model.getParcelableObject())
view.context.startActivity(intent)
}
fun setSomeDataToModel(someData: Any) {
}
}
/*Everyone who wants to use presenter, must be a Context and implement IView*/
fun use()//in some fragment, or activity implementing IView
{
MyActivityPresenter().launchActivity(this)
//or
val presenter = MyActivityPresenter()
presenter.setSomeDataToModel("some data")
presenter.launchActivity(this)
}

Clean Architecture: ViewModel with multiple UseCases on Android

This is more of an Architecture question than a bug fixing one.
Let's assume this app lets users mark a Bus and/or Bus Stations as a favourite. My question is, should I have a ViewModel with both UseCases or should I build a UseCase that encapsulates the current logic?
Also for the question part, I'm not entirely sure the way I should expose the combined data to the UI layer (see favouritesExposedLiveData)
Thanks in advance any feedback is welcome, here's my ViewModel you can assume each UseCase is passing the correct data from the data source(s).
open class FavouritesViewModel #Inject internal constructor(
private val getFavouriteStationsUseCase: GetFavouriteStationsUseCase,
private val getFavouriteBusesUseCase: GetFavouriteBusesUseCase,
private val favouriteMapper: FavouriteMapper,
private val busMapper: BusMapper,
private val stationMapper: StationMapper) : ViewModel() {
private val favouriteBusesLiveData: MutableLiveData<Resource<List<BusView>>> = MutableLiveData()
private val favouriteStationsLiveData: MutableLiveData<Resource<List<StationView>>> = MutableLiveData()
private lateinit var favouritesMediatorLiveData: MediatorLiveData<List<FavouriteView>>
private lateinit var favouritesExposedLiveData: LiveData<Resource<List<FavouriteView>>>
init {
fetchFavourites()
}
override fun onCleared() {
getFavouriteStationsUseCase.dispose()
getFavouriteBusesUseCase.dispose()
super.onCleared()
}
fun getFavourites(): LiveData<Resource<List<FavouriteView>>> {
return favouritesExposedLiveData
}
private fun fetchFavourites() {
favouritesMediatorLiveData.addSource(favouriteStationsLiveData, { favouriteStationListResource ->
if (favouriteStationListResource?.status == ResourceState.SUCCESS) {
favouriteStationListResource.data?.map {
favouriteMapper.mapFromView(it)
}
}
})
favouritesMediatorLiveData.addSource(favouriteBusesLiveData, { favouriteBusesListResource ->
if (favouriteBusesListResource?.status == ResourceState.SUCCESS) {
favouriteBusesListResource.data?.map {
favouriteMapper.mapFromView(it)
}
}
})
getFavouriteStationsUseCase.execute(FavouriteStationsSubscriber())
getFavouriteBusesUseCase.execute(FavouriteBusesSubscriber())
}
inner class FavouriteStationsSubscriber : DisposableSubscriber<List<Station>>() {
override fun onComplete() {}
override fun onNext(t: List<Station>) {
favouriteStationsLiveData.postValue(Resource(ResourceState.SUCCESS, t.map { stationMapper.mapToView(it) }, null))
}
override fun onError(exception: Throwable) {
favouriteStationsLiveData.postValue(Resource(ResourceState.ERROR, null, exception.message))
}
}
inner class FavouriteBusesSubscriber : DisposableSubscriber<List<Bus>>() {
override fun onComplete() {}
override fun onNext(t: List<Bus>) {
favouriteBusesLiveData.postValue(Resource(ResourceState.SUCCESS, t.map { busMapper.mapToView(it) }, null))
}
override fun onError(exception: Throwable) {
favouriteBusesLiveData.postValue(Resource(ResourceState.ERROR, null, exception.message))
}
}
}
Note: Currently the MediatorLiveData (favouritesMediatorLiveData)is not binding the data back to the favouritesExposedLiveData since at this time, I'm not sure this is the correct way to go ;).
Ideally a ViewModel would only have the view state for its view. By using the MediatorLiveData you could aggregate all sources of state into one that represents the view state over time.
What you can have is a data class that represents your ViewState that you construct on your view model and is your exposed LiveData
data class FavouritesViewState(val favoriteStations: List<Station>, val favoritBuses: List<Bus>)
However you know depend on the ViewModel to construct the final ViewState which kinda breaks the single responsibility principle and also makes you dependent of an Android framework.
I would approach it using a composite UseCase that had both station and bus use cases and returns the composed data that you can then easily expose from the ViewModel.
The whole point of a ViewModel is that it is a model of what the view is using. It should be as close to that as possible.. Unless you are presenting stations and buses in the same view list (seems ugly), otherwise, they are separate views, and should get separate models.

Kotlin generics issue

The error occurs when passing this to onResume.
Somehow it doesn't recognize that this implements ActivityLifecycleType, Am I missing something?
open class BaseActivity<ViewModelType: ActivityViewModel<*>>: RxAppCompatActivity(), ActivityLifecycleType {
protected var viewModel: ViewModelType? = null
#CallSuper
override fun onResume() {
super.onResume()
viewModel?.onResume(this) ==> Error Required Nothing, Find BaseActivity<ViewModelType>
}
}
open class ActivityViewModel<in ViewType: ActivityLifecycleType> {
fun onResume(view: ViewType) {
// Do something
}
}
interface ActivityLifecycleType {
fun lifecycle(): Observable<ActivityEvent>
}
Kotlin's generics' more strict that you have you write use the code below:
open class BaseActivity<ViewModelType : ActivityViewModel<ActivityLifecycleType>> : ActivityLifecycleType, RxAppCompatActivity() {
protected var viewModel: ViewModelType? = null
#CallSuper
override fun onResume() {
super.onResume()
viewModel?.onResume(this#BaseActivity) // ==> Error Required Nothing, Find BaseActivity<ViewModelType>
}
}
open class ActivityViewModel<in ViewType : ActivityLifecycleType> {
fun onResume(view: ViewType) {
// Do something
}
}
interface ActivityLifecycleType {
fun lifecycle(): Observable<ActivityEvent>
}
What I've done is to change the declaration in the first line.
Java is too weak to check the generic type but Kotlin do.
Mention there're two things you have to do next:
implement lifecycle in BaseActivity or make it abstract.
it's recommended to use lateinit var viewModel instead of nullable types

Categories

Resources