I am working on an Android class that contains an ArrayList of generic objects. I am looking to fire an event in this class whenever an element of said ArrayList is modified.
In an ideal world, the ArrayList itself should be a private member, and the class would contain the public methods to add/update/delete an element and everything would be all fine and dandy.
Unfortunately, the ArrayList is exposed as a public member, so it and its elements are being modified all over the place (application). Without rewriting a boat load of code and/or going on a wild goose chase in the code, I am hoping I can find way to trigger an event when ArrayList is modified in the class containing the list. Any ideas?
You can subclass ArrayList and trigger an action (call a callback for example) after some of it's methods were invoked.
Then replace the original ArrayList in your Android class with your implementation.
P.S. Example:
public class MyArrayList<E> extends ArrayList<E> {
#Override
public boolean add(E object) {
// Do some action here
return super.add(object);
};
#Override
public void add(int index, E object) {
super.add(index, object);
// Do some action here
};
#Override
public E remove(int index) {
// Do some action here
return super.remove(index);
}
// etc...
}
Since it subclasses ArrayList you won't get any errors in your code, and everything that worked before, will work without any changes.
With a little creativity the class can be made more elegant and efficient, but the general idea is there.
Edit: Yep. Sorry, was a little hasty with those returns. Fixed, and thanks Petar
Related
I have multiple fragments which does the same thing but presented in different way to the user. However, the functionality in all those fragments more like same as in delete, add etc. That said, I do not want to duplicate the code. Therefore created a manager class so that I could have the centralized code. But, the problem now is, when the user is performing an action say, when the user is deleting an item, the fragment does not get refreshed. So, I need to send the message to the fragment from the manager class to refresh the list. I have the following pseudo code to give an insight...
public class MyFragment extends Fragment{
ManagerClass mManagerClass;
private void onItemSelected() {
mManagerClass = new ManagerClass(itemId);
}
public void refreshItems() {
ItemDao.query();
}
}
public class ManagerClass {
public ManagerClass(int itemId) {
DeleteItem(itemId);
}
private void DeleteItem(int itemId) {
//when this task completes it should call the MrFragment.refreshItems();
//Keep in mind that I cannot pass the Fragment becuse this ManagerClass is designed to handle more than two fragments
//MAY BE I SHOULD DO CALLBACK BUT HOW?.... When i try to implement there several callbacks but not sure which one should I use and how...
}
}
Any help would be greatly appreciated...
I was so stupid and dumb at the time. There are multiple ways to achieve this but the quick resolutions are:
1. Have a ABSTRACT class and extend the fragment out of it or
2. Use the OBSERVER pattern
I went with the second option, Observer pattern and works like a charm!
Background:
Nothing special, I'm using Java for Android.
Problem:
I want to allow my users to create multiple instances of an object and register a callback Interface into each instance (think of an error callback).
The object has several children/sub-children/etc... Each child can return this event.
I expect 1-10 instances in entire lifetime of app.
I'm trying to avoid using a singleton/static events listener.
Possible solutions (and reasons not to use them):
1) Register a BroadcastReceiver in each parent-object instance and allow each grand child notify the event on Intent level. This should allow the main object to notify my user about the event.
The problem is the multiple instances would require multiple BroadcastReceivers which I expect to be heavy or just less than optimal.
2) Register one BroadcastReceiver and find a way to let it decide which instance of the object should be notified of an event, and actually send it to it. The problem is that I'm not sure how to notify the objects themselves.
3) Let the callback interface pass as an argument from parent to each of the children/grandchilren/etc... But this would be messy.
4) Use something like EventBus library (which I understand would be the same thing as BroadcastReceiver, only Reflection based, thus slower).
5) Anything else?
I don't know if this is the best solution for you but I think it would work if I understand your requirements correctly.
public class Listener extends Observable implements Observer {
private List<Observer> clients = new ArrayList<>();
public void addClient(Observer client){
clients.add(client);
}
#Override
public void update(Observable o, Object arg) {
for(Observer client : clients){
client.update(o, arg); // Or whatever you need to do
}
}
public class DataSource extends Observable {
private Observer observer;
public DataSource(Observer o){
observer = o;
}
// Notify observer of changes at appropriate time
}
public class Consumer implements Observer {
public Consumer(){
Listener listener = ...;
listener.addClient(this);
}
#Override
public void update(Observable o, Object arg) {
// Handle appropriately
}
}
}
DataSource is your "sub-objects", Consumer is the end client of the events, and Listener is the class in the middle. I don't know why the clients can't directly register for events with the "sub-objects" but that is what you said! This is modeled as inner classes here for simplicity but I assume you would not do that.
This is a continuation from the question I 1st asked here, Creating a new ArrayPagerAdapter with variety of Fragments. You were dead on about me using the wrong ArrayAdapter I just needed to use the one that has v4 support. I have posted the code for it below. One of the next blocks i'm getting stuck on right now is creating the PageDescriptor objects in the ArrayList passed into SimplePageAdapter. I've tried copying and pasting the SimplePageDescriptor class used in the Demo into my code but I am getting an error when trying to return from the Parceable.Creator method. It says SimplePageDescriptor has private access in com.commonsware.cwac.pager.SimplePageDescriptor. I guess the main thing i'm trying to grasp is how to use the SimplePageDescriptor from the demo in my own code. Do I just use the entire pager folder? I have posted my code for the SimplePagerAdapter and the SimplePageDescriptor below.
class SimplePagerAdapter extends ArrayPagerAdapter<android.support.v4.app.Fragment> {
public SimplePagerAdapter(FragmentManager fragmentManager,
ArrayList<PageDescriptor> descriptors) {
super(fragmentManager, descriptors);
}
#Override
protected Fragment createFragment(PageDescriptor desc) {
mMainFragment = JudgeMainFragment.newInstance();
mClassifyFragment = JudgeClassifyFragment.newInstance();
mSidebarFragment = JudgeSidebarFragment.newInstance((SidebarCall) mActivity);
mVerdictFragment = JudgeVerdictFragment.newInstance();
return (mMainFragment.newInstance());
}
}
public static final Parcelable.Creator<com.commonsware.cwac.pager.SimplePageDescriptor> CREATOR=
new Parcelable.Creator<com.commonsware.cwac.pager.SimplePageDescriptor>() {
public com.commonsware.cwac.pager.SimplePageDescriptor createFromParcel(Parcel in) {
//This is the line I get the error at
return new com.commonsware.cwac.pager.SimplePageDescriptor(in);
}
public com.commonsware.cwac.pager.SimplePageDescriptor[] newArray(int size) {
return new com.commonsware.cwac.pager.SimplePageDescriptor[size];
}
};
One of the next blocks i'm getting stuck on right now is creating the PageDescriptor objects in the ArrayList passed into SimplePageAdapter
PageDescriptor is an interface. Create your own class (e.g., BlainePageDescriptor) that implements the interface. This is covered in the documentation.
I've tried copying and pasting the SimplePageDescriptor class used in the Demo into my code
That will not solve your problem.
Your problem, as I understand it, is that you want your ArrayPagerAdapter to be able to handle N different types of pages (JudgeMainFragment, JudgeClassifyFragment, etc.). That requires you to return the proper fragment from createFragment(), given the supplied PageDescriptor. Hence, you need to create your own PageDescriptor implementation (e.g., BlainePageDescriptor). That class needs to hold onto sufficient information to both satisfy the PageDescriptor interface and be able to tell createFragment() what sort of fragment to create.
I'm testing an Android app with Espresso. I'm trying to write a helper function in a helper class that can check the value of a spinner with the following code:
public static void assertSpinner(MainActivity activity, int id, int val) {
Spinner s = (Spinner) activity.findViewById(id);
assertNotNull(s);
assertEquals(val, s.getSelectedItemPosition());
}
I can then call the helper from my test with:
assertSpinner(getActivity(),R.id.someSpinner,12);
Though it seems weird that every assertSpinner's first arg is getActivity(). I'd like to call getActivity() in the helper function instead so I don't need to pass it, but it seems that is only made available because the test extends ActivityInstrumentationTestCase2. Is there any way to get this value without having to pass it to each of my helpers, or does that not fit the Android way?
No, I don't think there's a clean/easy way to get the current Activity from outside of a test.
However, you could do this cleanly with a custom view matcher. Something like
static Matcher<View> withSelectedItemPosition(final int selectedItemPosition) {
return new BoundedMatcher<View, Spinner>(Spinner.class) {
#Override protected boolean matchesSafely(Spinner spinner) {
return spinner.getSelectedItemPosition() == selectedItemPosition;
}
#Override public void describeTo(Description description) {
description.appendText("with selected item position: ")
.appendValue(selectedItemPosition);
}
};
}
Then you could do
onView(withId(R.id.my_spinner)).check(matches(withSelectedItemPosition(5)));
It's a bit of extra code, but more idiomatic. Espresso really discourages you from interacting with the view hierarchy directly; ideally your tests should never call methods like findViewById.
I'm writing an application which run a background Service which communicate with a remote server.
when the server sends me a new message, i need to update an object which is represent in the UI and then to update the UI View to represent the new state of the object (for example if the object's background propery is true - set the background of the View to green and if false set the background of the view to red).
I'm using a list view to show all an ArrayList of all those objects throw an ArrayAdapter.
I have an Application object (named app) for static reference and i have there a CurrentActivity property which store the current activity running (or null if the UI is closed).
i'm using this code to update the UI:
in my Service:
onNewMessage(boolean backgruond)
{
if (app.getCurrentActivity != null)
app.getCurrentActivity.onNewMessage(background);
}
in my Activity:
onNewMessage(boolean background)
{
object.setBackground(bacground);
Log.d("Background", String.valueof(background));
runOnUiThread(new Runnable() {
#Override
public void run()
{
arrayAdapter.notifyDataSetChanged();
}
});
}
and although the Log returns the right background state, the view isn't refreshing with the notifyDataSetChanged();
i've tried to send message to Activity throw BroadcastRecevier but it much more complicated because i have lots of messages coming from the server and i will have to register many receivers.
And besides - i don't understand why would the recevier work and this mechanism wont..
example of working method which updates the ListView:
ListViewActivity - inheritance from BaseActivity:
#Override
public void onUnFriend(FacebookUser facebookUser, boolean isYouRemovedClient)
{
super.onUnFriend(facebookUser, isYouRemovedClient);
updateView();
}
BaseActivity (the super class which extends Activity):
public void onUnFriend(FacebookUser facebookUser, boolean isYouRemovedClient)
{
facebookUser.setApplicationFriend(false);
app.getApplicationFriends().remove(facebookUser);
app.getDatabaseManager().deleteApplicationFriend(facebookUser.getId());
if (isYouRemovedClient)
app.showToast(facebookUser.getName() + " has removed from your friends", true);
else
app.showToast(facebookUser.getName() + " has removed you from friends", true);
}
this one works and does change the background color in the ListView.
not working example
ListViewActivity:
#Override
public void onFriendRequestAccepted(FacebookUser facebookUser, boolean showDialog) {
super.onFriendRequestAccepted(facebookUser, showDialog);
updateView();
}
BaseActivity:
public void onFriendRequestAccepted(FacebookUser facebookUser, boolean showDialog)
{
facebookUser.setApplicationFriend(true);
app.getApplicationFriends().add(facebookUser);
app.getDatabaseManager().addApplicationFriend(facebookUser);
if (showDialog)
app.showNewEventActivity(facebookUser, EventDialogManager.EVENT_FRIEND_ACCEPTED);
}
no update is made... i can't really understand why..
i have there a CurrentActivity property which store the current activity running (or null if the UI is closed)
I do not recommend this practice. It relies upon you consistently and reliably updating that Application data member, and it increases the coupling between your service and your UI.
and although the Log returns the right background state, the view isn't refreshing with the notifyDataSetChanged();
It would appear that you did not change the data in the adapter. Certainly, there is no evidence in the code that you have here that you updated the data in the adapter.
BTW, neither of the code snippets you have shown here are likely to compile (first is not valid Java, second has a typo).
i have lots of messages coming from the server and i will have to register many receivers
No, you will have to register one receiver, and in onReceive(), use an if statement (or perhaps a switch, if you prefer) to distinguish one message from another.
In addition to what CommonsWare said, I assume that object in the first line of your onNewMessage is the view. setBackround accepts an int parameter, not a boolean.
Use 0xFF00FF00 for green and 0xFFFF0000 for red.
By the way, it's a very bad practice to keep static references of Context objects and it's derived classes (Application and Activity both derive from Context, and keeping a static reference of them may lead to serious memory leaks. Read more here.)
Use a BroadcastReceiver instead. They are much more simple comparing to how you described them - you only need one.