As part of an Activity, I have a class encapsulating functionality for a single object and want to display details of the object in a ListView when a particular button is pressed.
Attempt One:
If I pass the ListView and this to the object to store (!) and then try to call the ArrayAdapter, I get a runtime error:
Source not found
Code segment (method within the class)...
private void displayTouch(Touch lasttouch) {
String mLine = "";
/* Build up line of analysis */
...
/* Display line */
mAnalysis[lasttouch.mSequence] = mLine;
mViewAnalysis.setAdapter(new ArrayAdapter<String> (mActivity,R.layout.simplerow,mAnalysis));
} // End of method displayTouch
Attempt Two
If I try to display data in the ListView from within the OnClick Listener, I get an error message in Eclipse:
The constructor ArrayAdapter (new View.OnClickListener(){}, int, String[] is undefined.
Code segment (within the OnClick Listener of the Activity)...
/* Record details */
OnClickListener CourtListener = new OnClickListener() {
public void onClick(View v) {
...
/* Analyse */
...
/* Capture analysis */
lRoster.setAdapter(new ArrayAdapter<String> (this,R.layout.simplerow,playerArray));
} // End of event onClick
}; // End of listener CourtListener
In this code playerArray is dimensioned within the Activity's onCreate;
Both attempts have weaknesses of approach (in addition to not working) so I'll re-factor once I can get something working.
Essentially, how do I display data generated within an object to a ListView within an Activity from the OnClick Listener of another View in the same Activity? Everything is within one package and within the Activity.
Perhaps this blogpost might help: Putting custom objects in ListView
Related
I'm looking for the best implementation pattern in Android to update a list when one of the elements change in a different activity.
Imagine this user journey:
An async process fetches ten (10) contact profiles from a web server. These are placed in an array and an adapter is notified. The ten (10) contact profiles are now displayed in a list.
The user clicks on contact profile five (5). It opens up an activity with details of this contact profile. The user decides they like it and clicks 'add to favourite'. This triggers an async request to the web server that the user has favourited contact profile five (5).
The user clicks back. They are now presented again with the list. The problem is the list is outdated now and doesn't show that profile five (5) is favourited.
Do you:
Async call the web server for the updated data and notify the adapter to refresh the entire list. This seems inefficient as the call for the list can take a couple of seconds.
On favouriting the profile store the object somewhere (perhaps in a singleton service) marked for 'refresh'. OnResume in the List activity do you sniff the variable and update just that element in the list.
Ensure the list array is static available. Update the array from the detail activity. OnResume in the activity always notify the adapter for a refresh.
Ensure the list array and adapter is static available. Update the array and notify the adapter from the detail activity.
Any other options? What is the best design principle for this?
Async call the web server for the updated data and notify the adapter
to refresh the entire list. This seems inefficient as the call for the
list can take a couple of seconds.
As you say, it's very inefficient. Creating an Object is expensive in Android. Creating a List of many object is much more expensive.
On favouriting the profile store the object somewhere (perhaps in a
singleton service) marked for 'refresh'. OnResume in the List activity
do you sniff the variable and update just that element in the list.
This is not a good solution because there is a probability that the app crashes before we refresh the object or the app get killed by the device.
Ensure the list array is static available. Update the array from the
detail activity. OnResume in the activity always notify the adapter
for a refresh.
Updating the array via a static method or variable is not a good solution because it makes your detail Activity get coupled with the list. Also, you can't make sure that only the detail activity that change the list if your project get bigger.
Ensure the list array and adapter is static available. Update the
array and notify the adapter from the detail activity.
Same as the above, static variable or object is a no go.
You better use an Event Bus system like EventBus.
Whenever you clicks 'add to favourite' in detail activity, send the async request to update favourite to the web server and also send Event to the list activity to update the specific profile object. For example, if your profile has id "777" and the profile is favourited in detail activity then you need to send the Event something like this in your :
btnFavourite.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Send event when click favourite.
EventBus.getDefault.post(new RefreshProfileEvent(id, true);
}
});
RefreshProfileEvent is a simple pojo:
public class RefreshProfileEvent {
private String id;
private boolean isFavourited;
public RefreshProfileEvent(String id, boolean isFavourited) {
this.id = id;
this.isFavourited = isFavourited;
}
//getter and setter
}
Then you can receive the Event in your list activity to update the selected profile:
public class YourListActivity {
...
#Override
protected onCreate() {
...
EventBus.getDefault().register(this);
}
#Override
protected onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(RefreshProfileEvent event) {
// Refresh specific profile
// For example, your profile is saved in List<Profile> mProfiles
// Search for profile by its id.
for(int i = 0; i < mProfiles.size(); i++) {
if(mProfiles.getId().equals(event.getId()) {
// Refresh the profile in the adapter.
// I assume the adapter is RecyclerView adapter named mAdapter
mProfiles.get(i).isFavourited(true);
mAdapter.notifyItemChanged(i);
// Stop searching.
break;
}
}
}
You don't need to wait for AsyncTask request result returned by the server. Just make the profile favourited first and silently waiting for the result. If your request success, don't do anything. But if the request error, make the profile unfavourited and send unobstructive message like SnackBar to inform the user.
Third option is the best when a user changes the data in detail activity the array should be changed and then when the use returns to main activity call Adapter.notifyDataSetChanged(); will do the trick
For an ArrayAdapter , notifyDataSetChanged only works if you use the add() , insert() , remove() , and clear() on the Adapter.
You can do something like this:
#Override
protected void onResume() {
super.onResume();
Refresh();
}
public void Refresh(){
items = //response....
CustomAdapter adapter = new CustomAdapter(MainActivity.this,items);
list.setAdapter(adapter);
}
On every onResume activity it will refresh the list. Hope it helps you.
my project involves the following:
1) An EditText view (inputText) in which the user is supposed to type a name
2) A Button that, when pressed, creates a Person object whose name is in inputText and saves that object to the realm database. Then it refreshes the textLog to include the new Person's name.
3) A 'TextView' (textLog) that shows a list of all the names of the Person objects in the realm database.
My problem is that clicking on the button refreshes the text log before it saves the person object to the database. This means the new person's name doesn't show up until I click the button again to create a newer Person object. I want the UI to refresh after the object is saved to the database, so it is always up to date. The following code is from my MainActivity class. Earlier I had done Handler handler = new Handler(Looper.getMainLooper());.
// called when button is clicked
private void submit(View view)
{
final String input = inputText.getText()
.toString()
.trim();
// asynchronous transaction
realm.executeTransactionAsync(realm -> {
realm.copyToRealm(new Person(input));
handler.post(this::refresh);
});
}
private void refresh()
{
textLog.setText(realm.where(Person.class)
.findAll()
.stream()
.map(Person::getName)
.collect(Collectors.joining("\n")));
}
Add Realm.Transaction.OnSuccess() and Realm.Transaction.OnError() callbacks to your async transaction. When those methods are called, you know the transaction is complete and you can refresh your UI.
It looks like you have a race condition. You do realm.executeTransactionAsync and then immediately do handler.post(this::refresh); - there's no guarantee that they'll execute in the order you want them to.
I'm trying to change Roman's Nurik WizardPager so that in one of the steps display some data from my database.
I'm taking a Model and an UI from the library and I modify them so I can have a
DisplayOrderPage with a parameter ArrayList for my data
public DisplayOrderPage(ModelCallbacks callbacks, ArrayList<Salads> ord , String title) {
super(callbacks, ord, title);
}
and a DisplayOrderFragment which is going to display the data.
I can get an ArrayList with my data from my database in the MainActivity but I don't know how to pass that data to the SandwichWizardModel since it's not an Activity.
you can do one thing.. also pass the context of the activity as parameter. And declare a method in the activity which change the gui within a handler .. like this
in Activity
changeGUI(){
new Handler().post(new Runnable(){
// change gui
}
}
and call like
methodInCLasse(this, arrayList);
I have seen few question on SOF, but neither of them helped.
In my application I have a List of users which can be accessed by clicking friends of a user. The flow is:
Go to my profile
Click on my friends to go to an Activity which has list of users(my friends)
Click on any of the listView Item takes to that user's profile
From that profile I can see that user's friends list the same way as mine.
The problem is all these listView items have a button to add as friend which makes me and that user as friend in that list(like follow changes to following in twitter) now I come back through the backstack, and somewhere in one of the previous listViews that user is present for whom the button is still add as friend.
How to change the button(a flag in my adapter data) for that user in all the ListViews?
Use Interface to send events back to the activity and update the list or database when the event is received.
Interfaces are ways of passing messages to "the outer world". Just look at a simple button onClickListener. You basically call a setOnClickListener(this) on the button and implement onClickListener, which is an interface here. Whenever the button is clicked, you get an event in onClick. It is the safest way to pass messages between activities without the need of intents ( which according to me is a huge pain in the ... ) Here is an example:
Example:
class A extends Activity implements EventInterface{
public A(){
//set a listener. (Do not forget it!!!)
//You can call it wherever you want;
//just make sure that it is called before you need something out of it.
//safest place is onCreate.
setEventInterfaceListener( A.this );
}
//this method will be automatically added once you implement EventInterface.
void eventFromClassB(int event){
//you receive events here.
//Check the "event" variable to see which event it is.
}
}
class B{
//Interface logic
public interface EventInterface{
public static int BUTTON_CLICKED = 1;
void eventFromClassB(int event);
}
static EventInterface events;
public static void setEventInterfaceListener(EventInterface listener) {
events = listener;
}
private void dispatchEvent(int trigger) {
if (events != null) {
events.eventFromClassB(trigger);
}
}
//Interface ends
void yourMethod(){
//Call this whenever you want to send an event.
dispatchEvent( BUTTON_CLICKED );
}
}
I've tried the following approaches to try to get the TextView to update from the model:
TextChangeListeners - error
Other Threads
asynctask
Added a refresh button which updates the data, I force the click
Scenario is as follows:
Standard thread running which updates the Model (MVC) object every second, which works perfectly.
When the Model object is updated, it notifies all classes which implement "Observer".
My GolfHomeScreen extends Activity and implements Observer (code below).
The GolfHomeScreen.update (Observable observable, Object data) method works perfectly. It executes every second as expected and S.O.P the correct data (see --> //* #1 *).
This then kicks off a Thread. I read on this site that you have to UI Thread to execute code which updates widgets and then run the "runOnUiThread" method - the code looked something like I have done below but I found it a little hard to follow. Anyway, this thread runs (see --> //* #2 ) which then executes method onClick(View v) (see --> // #3 *).
System.out.println("driverType :" + mvcModel.getDriverName()); (see --> //* #4 *) works perfectly.
//* #5 * - it MUST update but it doesn't.
If I actually physically click the button, the data is displayed correctly on the screen. My only guess is that the screen must not refresh on the forced click or else I am not using the UI thread to make the update.
NOTE: If the code below is missing something, it is because I removed a ton of code to simplify the understanding of what's going on.
public class GolfHomeScreen extends Activity implements Observer
{
GolfHomeScreen golfHomeScreen = null;
TextView driverName = null; // Type of driver used by golfer (eg: 1 wood)
Button refreshData = null;
#Override public void onClick(View v) //*** #3 ***
{
if ((v.getId()) == 12345) // ID of wood (12345 = 1 wood)
{
//*** #4 ***
System.out.println("driverType :" + mvcModel.getDriverName()); //THIS PRINTS OUT THE LATEST DATA!!!
//*** #5 ***
driverName.setText(String.valueOf(mvcModel.getDriverName())); //THIS DOESN'T AFFECT THE SCREEN?? WHY :-(
}
}
#Override public void onCreate(Bundle savedInstanceState)
{
golfHomeScreen = this;
refreshData = indViewById(R.id.golfhomescreen_button_refreshData);
driverName = (TextView) findViewById(R.id.golfhomescreen_text_drivername);
}
#Override public void update(Observable observable, Object data)
{
// #1 ***
System.out.println("driverType :" + mvcModel.getDriverName());
golfHomeScreen.runOnUiThread(new Runnable()
{
#Override
public void run()
{
refreshData.performClick(); //*** #2 ***
}
});
}
}
I'll run a few suggestions and a few fixes and hopefully your problem will be gone.
I'm saying that because conceptually you do have some wrong stuff, and my answer is kind of fixing those concepts:
remove GolfHomeScreen golfHomeScreen = null; and golfHomeScreen = this; you don't need an object referencing to itself. It's just making confusion. This line golfHomeScreen.runOnUiThread(new Runnable() you should just call runOnUiThread(new Runnable()
Change System.out.pr*** to Log.d(TAG, MESSAGE); that's just because that is Android specialised way of dumping logs and it won't really matter for your result. It's nicer than standard Java System.out because allows you to filter those logs by TAG and severity.
Do never call refreshData.performClick(); or anything similar to that because you want the action inside that button to be performed. If you want a certain thing to happen from more than one source, create a method doThatThing(), put the actions in there, and from the Click and from the Update, you call the method. The way you're doing is a bit of a "Rube Goldberg machine" that is updating something, to click a button, to change a text. Just change the text.
do never compare a view ID with a number if ((v.getId()) == 12345) the ID of views created from XML are generated by the system during compilation and you can't know if it's a fixed number. You should compare with the static int ID of that view, like this : if(v.getId() == R.id.golfhomescreen_text_drivername). If the view was not created in XML (which is not the case here), you can compare with the actual object if(v.equals(driverName))
with those changes I believe your code will work. Make sure to let me know and mark as correct answer if in fact does.