Good day developers, I'm having trouble figuring out how and what should I do correctly so that I won't violate the MVP principle so I have the following situation.
A view that shows List of object, when a particular object is clicked my ViewHolder tells the Presenter about the click event.
What I want is ,when the object is clicked I want to start a new Activity that will display that particular object that was clicked.
Option 1.
Pass the id of the clicked object to Interactor(How should I do it I have no idea) of newly started activity, where a new api call will be called and will return a result based on given id.
Option 2.
On the other hand if I don't want to make a new api call and would like to simply pass the Object to the Iterator of new activity.
(What is better for performance to make a new api call or pass the object around?)
If any of my options seems somehow okay, then how should I implement them correctly?
If they are not then what is a alternative way of doing it?
Your best option is to use the new Blueprints which include Viewmodels in the form of LiveData, and there are specific mechanisms in the viewmodel for sharing the model between views. Basically the model is held with the key being a view id, but if you have a view and then a number of fragments, they can all access it.
Technically you have control over the key:
final UserModel viewModel = ViewModelProviders.of(this).get(UserModel.class);
What I like about this is it solves the scoping problem that has haunted MVC: either use Singletons or pass data around all over the place.
Related
I have 30 users displayed in a LazyColumn. On user click, I navigate forward to UserDetailsScreen. My question, is it better to pass the ID of the user and create another database (Room) call inside the second screen, or is it better to pass the object as a String?
I read an answer from #ianhanniballake which says that:
Passing complex data structures over arguments is considered an anti-pattern.
As per my understanding, it's better to only send the ID of the object and not the object itself. However, I have also read, that we can convert the object into a String and pass it further to next screen. Are there any downsides in this case?
Which option is more likely to be used? Is the second one also considered an anti-pattern?
I created a library here: https://github.com/chanjungkim/ALiveData
This library is made because of MutableLiveData<ArrayList<T>>. Many people who learns about LiveData complains or they are confused with this type when they need to manipulate(add, remove, etc) the MutableLiveData. That's because ArrayList is easy to manipulate and _arrayList.value!!.add(item) or _arrayList.value!!.remove(0) seems to notify. But they don't.
At the end, when we want to notify, we must assign a value like _arrayList.value!! = mList. ArrayList and List both need to set the data like _arrayList.value!! = mArrayList or _arrayList.value!! = mList.
My question is List doesn't have add(), remove(), etc. On the other hand, ArrayList already has those functions and helps us manipulate the list much easier.
some people suggested like this
_list.value = list.value.toMutableList().doWhatever().toList()
So, what's the point of using List over ArrayList? Could you give me example with the explanation of using it?
LiveData can be used in different ways, and of course there is no one correct way, but a very common way of using it is within the Android MVVM architecture recommended by Google for use in Android apps.
In this architecture, the Activity (or Fragment) observe the LiveData of the ViewModel. When doing this, the goal would be to make the UI as 'dumb' as possible, where you try to handle as much of the app logic and behaviour in the ViewModel, and the Activity simply observes and reflects it on the UI.
In a case like this, it is often preferable for the values of the LiveData being observed to be immutable.
By doing this, it limits the Activity from being able to manipulate the data it is observing, such as add()ing or remove()ing anything from it. As just described, the goal should be to limit the UI's ability to make exactly these type of changes. If the Activity wants to add() an item to an ArrayList that it is observing, it should instead do this by calling a method on the ViewModel, which will in turn update it's own LiveData.value to the new, updated list, which will in turn be observed by the Activity and updated on the UI.
By only allowing the Activity to observe the immutable values, it helps enforce this separation of concerns, and limits any accidental 'leak' of logic into the Activity itself.
This idea can be extended further by ensuring that the observed values are of type LiveData, and not MutableLiveData. Using the latter can allow the Activity to manipulate the live data on its own, and break the MVVM pattern.
A List is an interface, and defines the methods that must be implemented by any classes that would like to behave like a list. It can be considered the 'most basic' version of a list, and only defines the minimum requirements for an implementing class to behave like a list.
In the same way, the List interface itself extends the Collections interface, which in turn extends the Iterable interface. Each one adds more functionality to the one before it... kind of like lego blocks stacked on top of each other, creating more complex shapes.
An ArrayList is a class, which implements MutableList (which itself implements List). This means that an ArrayList can be instantiated, and passed around as an actual object. Because of this object oriented design, and according to the Liskov substitution principle, any class (or interface) can be replaced by a subclass (or class implementing the interface) interchangeably.
This is a fundamental principle to object oriented design. It helps break parts of the application down into smaller, more basic and more manageable pieces, and then grow as required.
To answer your question more specifically, if the class that is observing the LiveData only cares about the methods defined in the List interface, then that is the all it requires to know about the value. The actual value could in fact be an ArrayList, a MutableList or even a custom class MyOwnFancyList<E>: List<E>, it does not matter to the observer, just as long as it implements the List interface.
I have two adapters:
private StreamingNowAdapter mStreamingNowAdapter;
private UserStreamingNowAdapter mUserStreamingNowAdapter;
When I load them with Arrays
mStreamingNowAdapter = new StreamingNowAdapter(mEventId, mEventName,
mEventPreview, mNumberViewers, mContext);
mUserStreamingNowAdapter = new UserStreamingNowAdapter(mUserId,
mUsername, mProfilePic, mStreamingUserNumberViewers, mContext);
and call notifyDataSetChanged(); how come when I call this method on one adapter, both adapters become notified and displayed?
I only called the method on the first adapter
mStreamingNowAdapter.notifyDataSetChanged();
and it loads my data into my second recycler mUsersStreamingNowAdapter without me calling notifyDataSetChanged on the second adapter.
Is this a bug or is it supposed to be like this?
Edit1: Two separate recyclerviews, both adapters attached to their own recyclers.
RecyclerView mRecyclerViewStreaming =
view.findViewById(R.id.fraghome_recycler_streaming_now);
RecyclerView mRecyclerViewFollowing =
view.findViewById(R.id.fraghome_recycler_followed_users);
mRecyclerViewStreaming.setLayoutManager(new
LinearLayoutManager(getContext()));
mRecyclerViewFollowing.setLayoutManager(new
LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false));
mRecyclerViewStreaming.setAdapter(mStreamingNowAdapter);
mRecyclerViewFollowing.setAdapter(mUserStreamingNowAdapter);
I've checked the layout multiple times to make sure both the recyclerview's id's are not identical. Still can't figure out why this is happening.
A correctly managed RecyclerView and its required artifact (an Adapter, its data, etc.) should not interfere with another correctly managed RecyclerView or any other component for what is worth.
Unfortunately, the shared code you provided is not sufficient to determine what could be happening in your project.
To the best of my knowledge, RecyclerView adapters don't talk to each other in any way so a notifyDataSetChanged() on an adapter, should have no impact whatsoever on another adapter, regardless of where said adapters are declared (assuming they are different instances, of course).
All this being said, I'll offer you my personal comments about the code you posted. Please don't take it personally, for I'm reviewing the code, not the author, from a pure technical point of view based upon my experience with Android.
Fragment
It immediately caught my attention to see that a Fragment, a component known for being completely wild when it comes to lifecycle management and state, has so many responsibilities.
It starts by having no less than eight array lists, and managing two recyclerviews, and its respective adapters, and even the LayoutManagers used by the lists.
Not happy with having to manage all this, this Fragment also needs to handle a networking layer, error handling (not yet implemented), and data storage (in memory for now).
Because you may have put this together really fast simply to illustrate a case, I will let the fragment in peace for now.
Data
You're putting a lot of stress on your fragment and adapters by splitting the data in a very inefficient way. 4 array lists, one per field? What?
for(HomeFragmentPojo.StreamingNow streamingNow : streamingNowArrayList){
mEventId.add(streamingNow.getEvent_id());
mEventName.add(streamingNow.getEvent_name());
mEventPreview.add(streamingNow.getEvent_preview());
mNumberViewers.add(streamingNow.getNumber_viewers());
}
Why are you doing this?
If you have a StreamingNow object that has all 4 fields.
Why don't you have a ListAdapter<StreamingNow, ViewHolder>() that simply takes a list of StreamingNow objects?
You can simply do adapter.submitList(...) to pass the list once you get it and the adapter (when properly created) will do the right thing.
If you ever update the list, you simply submit it again, and the adapter will calculate the difference and only update the rows that need to be updated.
The BIND method, is also simple, because it would look like: (pseudo code)
viewHolder.eventName = getItem(position).name
viewHolder.viewerCount = getItem(position).viewerCount
//For the image...
Glide.with(mContext)
.load(getItem(position).preview)
.apply(RequestOptions.centerCropTransform())
.into(viewHolder.preview);
...
You get the idea.
Based upon the properties I see, your StreamingNow "pojo" kinda looks like that
class StreamingNow {
String id;
String name;
String preview; //maybe an URL? You use Glide so in the bind method, use it.
String viewerCount;
}
When I look at your other adapter, I see that it has the same data...
Id, Name, Pic, Count of Viewers.
There is no need to use a different adapter, you can reuse the same adapter assuming they have to behave the same way; if the Users adapter has to do something different on click, for example, you could pass the min information needed for the adapter to decide what to do when an item is clicked. Or better yet, you could simply pass a generic Listener so the adapter simply calls "this item "X" was clicked". Because these adapters aren't here to make decisions and call framework things, they have a lot of work in their plates, and this is not and should not be their responsibility.
Whoever manages, creates, and maintains these adapters (so far, your Fragment) should be the one in charge of doing this. Or even better yet, a ViewModel or Presenter should receive this and make an informed decision.
In any case, the click event inside the adapter is not the biggest problem here, and it will work anyway.
What I am trying to leave written here, is that your problem is a lack of separation of concerns between components.
The smaller and more concrete your classes are, the easier it is to replace them, test them, debug them, enhance them, etc.
You're throwing all your code in a big place. If you separate it, it's a lot easier to keep it under control.
So, what's the deal?
I suggest you take a step back, enhance your RecyclerView to use a ListAdapter
I wouldn't perform the network request in onCreate, instead do it in onViewCreated or similar, because during onCreate in a Fragment, there's no guarantee that the view will be created. (Remember when I told you Fragments were a pain?).
I wouldn't have that network code in the Fragment to be honest; it's quite simple to add a ViewModel and have the data there. Then you can have the ViewModel request the data during onCreate, but you'll only get the LiveData from it when your lifecycle allows it... which is the whole point of LiveData and observing it.
If this sounds like a lot of work, it may as well be, especially if you are new to Android. But it will pay off.
Good luck!
I have list of items shown with MVP pattern. View is an Activity (a.k.a. ItemsList). I want to show next Activity (also MVP'ed, a.k.a. ItemDetails) with details of item clicked in ItemsList.
Model for ItemsList is fed with data from Repository. So actually it's RMVP. It looks like this:
|->Model<->Presenter<->View [ItemsList]
Repository<-|
|->Model<->Presenter<->View [ItemDetails]
So Model for ItemsList already knows exactly what Model item was clicked so I could pass it right away from ItemsList to ItemDetails without fetching data from Repository again / mapping from data to Model / making new Model for ItemDetails etc.
How should I pass data (e.g. which item was clicked) between Activities in MVP?
Solution 1
Pass those data with Intent (similar to what is discussed here) but ... then what to do with that data? You unpack it from Intent in Activity (View) while you should have it on the other side of MVP, i.e. in Model. You pass it from View thru Presenter to Model?
But then Model in ItemDetails is not created from the "downside of MVP" (from Repository) but from "upperside of MVP" (from Presenter).
Solution 2
Pass only ID of clicked item to ItemDetails View (similar to what android10/Android-CleanArchitecture proposes in UserDetailsActivity with field private int userId; this is also what googlecodelabs' NoteDetailPresenter uses)
However there may a problem because I may have two implementations of Repository:
one with cache and then I may pass ID of clicked item to ItemDetails View (however it seems to be over-engineered), similar to what android10/Android-CleanArchitecture proposes in UserDetailsActivity with field private int userId;
second without cache and then I can't even ask for ID because I don't have access to list fetched for ItemsList
Recently, I have also used MVP in my application. I found same issue and pass data from ItemListActivity to ItemDetailActivity using intent. I passed model class which implements Parcelable interface and found no issue, it works fine.
The reason is : In ItemDetailActivity, we have to fetch data from database or from repository in your case which increases one operation in your application.
In ItemlistActivity you will perform only single operation to get all the data. While, if you pass data from ItemListActivity to ItemDetailActivity than, you just have to get data in ItemDetailActivity without any special operation.
I suggest you to go for Solution 1.
In an application I have used both those methods plus a third option.
In the third option I use an application level cache and stuff the object there and pass the cache key to the new intent. This is a variation of solution 2 without the repo cache. I know I will only need the item in the cache temporarily; therefore, it is very important to remove the item from the cache to prevent memory leaks.
I generally like the second solution as I don't have to make the object parcelable (perhaps being a bit lazy).
I did not notice performance differences from any of the methods.
In the end, I settled on Solution 1 when passing view models (I have all my view models parcelable). Solution 2 when using domian models (since they are in the database already, it's just easier to pass a key around). Solution 2 with cache if the domian object is in a transient state (either a new domain object not yet persisted or a domain object where the state might be inconsistant with the database i.e. changes not yet persisted and the activity get suspended for some reason)
If you are using view-first navigation, I think you can create your own presenter-layer bundle (or something like that) and send it with intent to the new Activity. Then the new Activity will extract this bundle and pass it to the presenter.
In addition, I think there's a third consideration - in MVP, you're Model is modelling the problem domain and should be able to exist regardless of the chosen V and P. If your ItemDetails and ItemList are part of the same problem domain, it's likely that your Model would model them both, and what you're infact looking to do is present the data in two different ways (one as a list, one as detail).
In which case, it may be viable to share a Model, and just have different presentation and viewing layers. In which case, once your detail has been passed back to you Model, it's just a case of attaching a new presenter to present the chosen ItemDetail.
When an item is clicked, you should pass that info up to the presenter who will decide what data needs to be sent to which activity. Then your presenter can package the data and target activity class into an intent and pass it back to the original view. For example, something like navigateToActivity(Intent i). The original view starts the activity with the intent it is given. This allows the originating view to be dumb about what the next screen needs to be (as there may be multiple options). Business logic is the presenter's job.
Then the new view's presenter unpacks the data sent with the intent when the presenter is initialized, as Kishan said. The target view is also dumb in terms of knowing what to do with the data in the intent.
The case: I use guide from androidhive to implement two tabs that by one activity. First one - list with complex objects in recyclerview, second - map with elements from this list. As DB I use SQLite but I'm going to migrate on nice and cozy Realm.
In SQLite case general approach which looks appropriate is to make list item object Parcelable and then use bundle to transfer data from activity to fragments.
One call to the database, seems legit.
But in case of Realm I cannot use Parcelable because it requires getters/setters methods only.
What is the best way in this case?
You should only send an ID of the object you want to display and then fetch the object in the fragment as described here: https://realm.io/docs/java/latest/#intents
I belive you can make your model class implement Parcelable, it won't affect anything related to realm, you'll use the getters/setters along with the CREATOR object.
Another option is to use realm Parceler library, as mentioned in the docs, it generates the code required to make the objects parcelable so that you can pass them between activities or fragments.