I have a piece of code using AndroidAnnotations which is very similar to the one found at:
https://github.com/excilys/androidannotations/wiki/Adapters-and-lists
However - I want to pass an argument to the List adapter to specify which list - i.e.
#AfterInject
void initAdapter() {
persons = personFinder.findAll(companyName);
}
What is the best way to associate companyName with the Adapter? I can't use the constructor with AnroidAnnotations - and #AfterViews is called before the #AfterViews of the parent fragment, so I can't call setters then.
I have currently hacked in a call to set the params manually then refresh the view and removed the #AfterViews - but its nasty and unreliable as I duplicate the pattern down the hierarchy.
EDIT
Just calling the setter works in the most simple case - and is what I currently have.
But doesn't work well in the more complicated case. i.e
EFragment->EViewGroup->EBean ListAdapter
Since I can't use the constructor, I have to wait until the full hierarchy is rendered and laid out before the fragment tells the ViewGroup which Company to show company info, which in turn tells the ListAdapter which company so I can get which people, etc.
It doesn't take much effort for it to get very messy and if my data was on the web - the UI would probably render like a webpage from the 90s.
I was hoping to use something like #Extras - or have a way to pass arguments for #AfterInject to use, or even just put the companyId in the Fragment Context without tying my ListAdapter to only work with one type of Fragment...
Try this
#EFragment
public class MyFragment extends Fragment {
#FragmentArg("myStringArgument")
String myMessage;
#FragmentArg
String anotherStringArgument;
#FragmentArg("myDateExtra")
Date myDateArgumentWithDefaultValue = new Date();
}
Source:
https://github.com/excilys/androidannotations/wiki/FragmentArg
Related
I want to improve my recyclerview list adapter via binding view model from fragment to my adapter.
So my new recyclerview adapter updates data via subscribe to view model.
Now i need to close submit list override fun. Because i don't need to update adapter via fragment. To update data i use binded view model instead.
My solving is:
#Deprecated("use vm instead", ReplaceWith("throw RuntimeException(\"Calling from fragment is deprecated!\")"))
override fun submitList(data: MutableList<InvitedGuyVo>?) {
throw RuntimeException("Calling from fragment is deprecated!")
}
But my doubt is good practice to throw exception if i want to bun override fun?
The API does not deny one from calling the method. It may turn out there is a usage of the class via a base class or interface, where they may not see the deprecation.
The final keyword may help to deny the method from being re-implemented in the inheritor classes.
You may have a stronger #Deprecated annotation in Kotlin with the level set to HIDDEN, e.g.
#Deprecated("message", level = DeprecationLevel.HIDDEN)
The annotation may the method invisible for an IDE, but still visible for the binary code.
The best way to solve the problem, but probably too hard way, could be to create a dedicated hierarchy of classes or interfaces, where there is no way in principle to call the method that you try to hide.
I want to make an app that has a vertical RecyclerView with nested horizontal RecyclerViews. I don't understand how to use properly an MVP pattern in such case. MVP "rule" says that it should be only one View for a screen.
My View interface:
public interface ViewLayer {
void showProductsInCategory(int categoryId, List<ProductModel> productList, PresenterLayer presenter);
void showCategories(List<CategoryModel> categoryItemList, PresenterLayer presenter);
}
Presenter:
public interface PresenterLayer {
void onViewReady();
}
Model:
public interface InteractorLayer {
void getProducts(int categoryId);
void getCategories();
}
Model listener interface:
public interface InteractorListener {
void onProductsLoaded(int id, List<ProductModel> products);
void onCategoriesLoaded(List<CategoryModel> categories);
}
CategoryModel:
public class CategoryModel {
private String categoryName;
private List<ProductModel> productList;
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public List<ProductModel> getProductList() {
return productList;
}
public void setProductList(List<ProductModel> productList) {
this.productList = productList;
}
}
So I have to select each nested RecyclerView by categoryId to add the data to their adapter. Can I create separate Model-View-Presenter interfaces for every horizontal RecyclerView?
UPD:
Step by step
1) MainActivity.onCreate calls presenter.onViewReady()
2) Presenter calls interactorLayer.getCategories()
3) Model calls InteractorListener.onCategoriesLoaded(List<CategoryModel> categories)
4) Presenter calls ViewLayer(MainActivity) showCategories(List<CategoryModel> categoryItemList, PresenterLayer presenter)
5) MainActivity sets that categoryItemList to the outer RecyclerView's adapter. Now each categoryItem has null productList
6) In the method onCategoriesLoaded(...) after ViewLayer.showCategories(...) Presenter calls Model's InteractorLayer.getProducts(i) in the cycle for each Category
7) After any productList loaded Presenter calls ViewLayer's showProductsInCategory(...)
8) MainActivity gets the Adapter of the main RecyclerView, gets a Category item and sets the productList for it.
9) MainActivity calls Adapter's notifyDataSetChanged()
10) The inner RecyclerView sets new productList when onBinding calls
I think its very complicated. What can I do with that?
UPD 03/24/2017
Source code: https://github.com/Lex74/ProductsShop
First, I'd like to state that I don't think of myself as a MVP guru, rather as someone who's striving to understand the pattern,
My favourite MVP reference: The Clean Architecture from Uncle Bob's blog
According to this blog post, there is something called The Dependency Rule:
...source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle...
For example, the Presenter class does not need to know anything about RecyclerView or RecyclerView.Adapter. It needs some interface to pass information to the outer layer.
The methods of the interface depend on the use case: with a List, one would like to be able to
pass a reference to the whole data List (showCategories())
refresh single list items (showProductsInCategory())
So I think the Dependency Rule says, that the ViewLayer interface has to offer methods which satisfy the needs of the [Model layer and the] Presenter layer. As a Presenter, I simply don't care whether the View out there is a ListView or maybe not a View at all but rather some combination of sound and vibration signals.
On the other hand, it seems to be perfectly ok for a View class to know the name (and methods of) its Presenter class, so maybe the PresenterLayer interface is no must-have.
It's entirely up to the View how the data will be offered to the user. A nested View structure still is just a complicated View. So I dont' think one needs to provide nested interfaces.
In some cases with nested Lists, the Presenter might need a method to update an item of the inner List, something like showSingleProductInCategory(ProductModel product, int categoryPosition, int productPosition).
Another interesting question: who keeps (and may modify) the data? In my opinion, the Presenter is responsible for the data, and it should only pass a reference to the data into the View layer or notify it of changes. An Adapter should not have the right to modify the original data list, a Presenter should never have to ask the Adapter "how many items are there?" and I don't really like the idea of two separate data lists. The names of the various notify... methods seem to indicate that I'm on the right track there.
This means Presenter will always hold on to the original data List. If data changes, the Presenter will update its data (may be clear() and "copy the new items", may also be more fine-grained, depending on what ProductLoader is offering) Afterwards, Presenter will notify the Adapter via the ViewLayer interface.
Link to a zip file with the modified Java classes
EDIT
Somehow I doubt that "one View for one screen" will work well for Android. Imagine the typical Master-Detail situation. If the screen is large, you will want to use the space and show both Fragments at once.
So if you have one View (and one Presenter) per Fragment, everything will work for all types of screens. It's up to the Activity to manage the Fragments depending on the screen size.
I've already explained that I like to have the Adapter of some ListView or RecyclerView implement the interface which is required as a callback for the Presenter. (All the Fragment in its role as callback could do would be to pass the information on to the Adapteranyway)
On the other hand, a Fragment may well contain several groups of data. Some of them may be somehow related (like all the songs by one particular artist), others (all those ads...) rather not. The Presenter needs methods to tell the View what to show to the user: one method for the artist, one for the advertisement etc.
So if I had an app with a handful of Fragments, the interface would contain methods like
void showAdvertisement(AdObject ad);
void showArtistInfo(Artist artist);
... and the Presenter would expect some class implementing this specific interface in its Constructor. (Plus the Adapter for the songs), and I'd have the Fragment implement the interface for all the non-collection data.
In a project with several apps, one might consider using generic interfaces
(one for any kind of detail information, one for collections). Then one would have a method showData(T data), and the Presenter in the example above would expect one callback for the advertisement and one for the artist info:
MyPlaylistPresenter (DetailInterface<AdObject> adCallback, DetailInterface<Artist> artistCallback, CollectionInterface<Song> songsCallback){...}
and then in the Fragment one would write:
MyPlaylistPresenter presenter = new MyPlaylistPresenter(this, this, adapter);
A little bit like Lego :), but all in all less interface classes. And methods which do basically the same thing have the same name all over the project, so I think it contributes to maintainability.
Now about your other question:
If your app has a Model on the client side, then I think you're right.
On the other hand, there are projects where the Model is part of the backend. Then the Presenter would be the logical choice.
Goal(s):
1: Effortless updating for dynamic items.
Example:
I have a List<T> returned from an API, I use that list in my RecyclerView.Adapter. User swipes to refresh and a new list is returned from the API containing some new items and some updated old items. Now the older list needs to remove duplicate items.
Note: assume all items have an updated attribute that might change if a user interacts with it.
2: Immediate user feedback (this might tie in with goal #1).
Example:
To insert a new item into the RecyclerView.Adapter it needs to be created in an API first. Implementation creates object in the RecyclerView.Adapter and in the API simultaneously. When the new object is returned from the API the immediate object that was previously injected right away into the RecyclerView.Adapter "syncs" with the API response. This way the user sees immediate feedback.
Code Example:
I don't really have anything in mind for Goal #1 BUT for Goal 2 I was thinking something like this maybe inside my ViewHolder? (I have heard that updating / syncing models in Viewholders is not a good practice in general because viewholders recycle):
// JAVA 7
private void createNewObjectToBeInsertedIntoRecyclerView(String data) {
// Pass callback to API and at the same time insert object into adapter
mAdapter.addNewObject(data);
mPresenter.createObject(new SyncRequestCallback() {
#Override
public void onSuccessFromAPI(ModelObject model) {
mAdapter.updateObject(model);
}
});
}
// JAVA 8
private void createNewObjectToBeInsertedIntoRecyclerView(String data) {
// Pass callback to API and at the same time insert object into adapter
mAdapter.addNewObject(data);
mPresenter.createObject((sync) -> { mAdapter.updateObject(model); });
}
This is just off the top of my head and it is definitely bug prone.
How Can I Achieve This?:
Looking for a robust solution here, but something that doesn't involve content providers (if possible).
You should not do anything like that in the ViewHolder, just bind the data you got from the API to the UI.
What you should do is operate on the Adapter
when the new List<T> returns from the API, just make the old list in the adapter to point to this new one (oldList = newList) and call mAdapter.notifyDataSetChanged()
You can do like point 1) but that way updates the whole Adapter. If you know where in the Adapter you have inserted that item (and I assume you know), just call mAdapter.notifyItemInserted(position) or alternatively, if you have already created it the Adapter, call mAdapter.notifyItemChanged(position)
I have two activities, a MainActivity and a secondary activity (e.g.: an about screen), then I have an asynctask which updates the UI on the MainActivity. This part works fine, the asynctask updates the UI by calling a method inside the MainActivity which inflates the UI and sets some values. This method also makes all UI components visible.
What doesn't work is, after going to the About screen and back to the MainActivity, the UI is completely blank. I don't understand why this stops working after navigating back from a different activity, which otherwise works fine.
Can someone please advise?
Here's how I draw the UI. This is how I update it from the thread, and it works, until I go to the about screen:
private void DisplayMainContent()
{
Context context = Util.DataStruct.LoadContext();
Log.d("debug", "DisplayMainContent() loaded a context " + context.toString());
RelativeLayout parent = (RelativeLayout)((Activity)context).findViewById(R.id.action_settings);
LayoutInflater li = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = li.inflate(R.layout.activity_main, parent);
TextView version = (TextView) v.findViewById(R.id.latestVerField);
version.setText(Util.DataStruct.GetVal("version"));
}
little story about vanishing data..
advice:
do not use new activity to achieve this - do your about as dialog or dialog fragment
nice example how to show dialog using fragment
don't use static - instead use singleton pattern
Singletons preserve the conventional class approach, and don't require that you use the static keyword everywhere. They may be more demanding to implement at first, but will greatly simplify the architecture of your program. Unlike static classes, we can use singletons as parameters or objects. Also,you can use singletons with interfaces just like any other class.
where i see problem:
this line is all u need to trace yr mistake:
(i think any other fragment of yr code is irrelevant to yr problem)
version.setText(Util.DataStruct.GetVal("version"));
Explanation why:
Util.DataStruct:
should be singleton with valid hard reference to it eg. in Applictation class or any other which life is longer as the activity u use to display data.
are you aware of the existence of garbage collector?
what i'm trying to point out ? why u should avoid STATIC !?
Code(data) flow:
app launched - initializes static class/variables etc
your variables are feed (via async or else way)
your app is closed by ANDROID OS - regardless of the reason
os recreates "stack"
but not yr variables - they are empty/null/defalt - not referenced by values as they shoud in normal code flow
context:
from where do u use yr DisplayMainContent() ? for what u need context there ?
context should be "anchor" for yr app methods which need it. (it's like certain security stuff - "hi this app fragment belong to me i have the right to modify and view contents - so to do any stuuf u pass nearest context u got - from fragment activity dialog widget etc")
if u can use getContext() eg. ("from parent") - dont use any static one
example:
in fragment:
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Context context = container.getContext();
}
in adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Context context = parent.getContext();
}
about inflation
- use :
LayoutInflater.from(context).inflate(res,ViewGroup,attachToRoot);
do u use parent in inflation(in fragment doubtless u use in activity doubtful)
for #bcorso:
Do not use more resources than you need.
#TomaszBest sorry, but you really don't know what you're talking
about: Util.DataStruct.GetVal() is calling a static method of the
static class Util.DataStruct, and therefore must return a static class
variable. Static class variables are singletons (only one will ever be
created), and it will not get garbage collected.
An object referenced through a static member variable is strongly referenced until the class is unloaded. A normal ClassLoader never unloads a class, but those used by application do under the right conditions.
If the static field is changed to reference a different object, the original object pointed to by the static field is eligible for GC just like any other object!
The initialization of static variables is covered in Section 2.11 Static Initializers of suns JVM spec. The specification does not define the implementation of Garbage collection - garbage collection rules for static objects will vary depending on your VM.
in sum:
If your class is holding onto this object permanently, it will only be released when the vm exits. Only Classes and interfaces loaded by the bootstrap loader may not be unloaded.
I'm currently developing an android app using MVP Pattern.
When I try to develop an Activity, I should use a ListView. So I'm using Adapter for ListView. But I heard Adapter is similar to Presenter on MVP Pattern.
I think if Apdater is smiliar to Presenter, then I should make Presenter for updating ListView instead of Adapter.
When this situation, how to develop ListView? Just use Adapter and keep using MVP Pattern?
Thanks for your reading.
Adapter is part of the view. In fact, all Android dependencies should be a part of the view.
To keep the adapter isolated from your model and your presenter use to be a hard task.
I have released a library called PaperKnife for this purpose.
You can use PaperKnife to decouple the adapter from the model and the presenter layer. Follow the next steps:
Abstract the model layer using CellElement interface. Your view layer does't need to know your model.
Create a class to provide the information for your row view. You can use your presenter. Implements the class CellDataProvider and create methods to provide all the information. Annotate your provider methods with #DataSource("DataId") to perform the mapping. Your data methods receive the instance of your model class. For example:
public class SamplePresenterImpl implements SamplePresenter, CellDataProvider {
#DataSource("Title")
public String getTitle(Item item) {
return item.getTitle();
}
// etc.
}
Create a ViewHolder in your adapter and implements the CellViewHolder interface. Create methods to manage the views and use DataTarget("DataId")
static class ViewHolder extends CellViewHolder {
#DataTarget("Title")
public String setTitle(String title) {
mTextViewTitle.setText(title);
}
}
Execute the mapping in your adapter getView method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// etc.
PaperKnife.map(mList.get(position))
.dataProvider(mCellDataProvider)
.into(viewHolder);
return convertView;
}
In this way your view layer just know the CellElement interface, and your presenter is responsible to provide data to your adapter.
Yes, the Adapter should be the P component in an MVP pattern. In fact ListViews are pretty much written as MVP- the getView() function needs to set all the values of the view each time its called, that's almost the definition of what a presenter must do. Although it's also easy to use it in an MVC type way- simply have getView call functions on the View that pass it the model and do that work in the Views. So really either way will work, just pick your preference.
If you do use an MVP model with complex list rows, I like to make the rows a custom compound View and put more descriptive function names on it- so rather than going listRow.findViewById(R.id.textView).setText(filename) I'll go listRow.setFilename(filename) and let the view know what to do with that. That kind of blurs the bounds of MVP and MVC a bit, but I find it a good balance of readability of your adapter and avoiding some of the awkwardness pure MVC sometimes brings.
If there is only a listview in that activity then there is no need to write a separate presenter because Adapter is actually working as Presenter for ListView. But if you have other UI components than ListView that need to be updated then you must need to write a separate Presenter for those UI components.