I work on a Battleships game as my first Android application.
I decided to use a ViewSwitcher that holds two GridViews. One bUserGrid is for the User field (hosting user ships), bComputerGrid is for the Computer field (showing user shots).
GridView bComputerGrid, bUserGrid, mComputerGrid, mUserGrid;
ViewSwitcher bigSwitcher, miniSwitcher;
User strikes (taps Computer field).The application runs the AsyncTask userAttack which defines whether that strike was successful or not.
bComputerGrid.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
runUserAttack(position);
}
});
If user missed, then in a onPostExecute() section of userAttack I call notifyDataSetChanged() in order to refresh the bComputerGrid.
protected void onPostExecute(String message) {
...
bComputerAdapter.notifyDataSetChanged();
mComputerAdapter.notifyDataSetChanged();
firstmateMsg.setText(message);
mUserBundle = mUserAttack.getmUBundle();
mUserAttack=null;
if (isCancelled()) return;
if (mUserBundle.getBoolean("userMissed")) {
mComputerAttack = new computerAttack();
mComputerAttack.execute(mComputerBundle);
} else {
if (doesWon(seqComputerShips)) {
showDialog(0);
}
}
}
But it doesn’t happen! Following statement doesn’t update bComputerGrid, if user misses.
bComputerAdapter.notifyDataSetChanged();
Then here in onPostExecute() section of userAttack I start AsyncTask computerAttack. In onPreExecute() section of computerAttack I switch bComputerGrid to bUserGrid by calling
bigSwitcher.showNext();
Switching happens but bComputerGrid hasn't been updated before that!
Computer does its strike and successfully refreshes the user field in onProgressUpdate() section of computerAttack by calling bUserAdapter.notifyDataSetChanged(). When Computer missed, the application comes to wait for the user strike (tap) again.
protected void onProgressUpdate(String... messages) {
...
mUserAdapter.notifyDataSetChanged();
bUserAdapter.notifyDataSetChanged();
}
protected void onPostExecute(Boolean result) {
mComputerBundle = mComputerAttack.getmCBundle();
mComputerAttack=null;
if (field_switch) {
switchViews();
}
}
But why I can’t refresh bComputerGrid before that switching and Computer shot happens?
Could you, please, help me?
Where you update your adapters? You change underlying data of the adapter, or you change the adapter list?
Adapters are also part of the UI, so changes of the adapter itself should happen only in a GUI thread. I got a couple of UI Exceptions (on display) telling me so in some other project as I changed the adapter contents in a Service Thread.
Eventually you have to recreate your adapter list to reflect changes in underlying data?
Related
I have ListView of Persons, ArrayAdapter to display images of this persons. There is two buttons "like" and "dislike" under photos of each person. If I press like or dislike button, this item deleted with animation from list:
protected void removeListItem(View rowView, final int positon) {
Animation out = AnimationUtils.makeOutAnimation(getActivity(), true);
rowView.startAnimation(out);
rowView.setVisibility(View.INVISIBLE);
Handler handle = new Handler();
handle.postDelayed(new Runnable() {
public void run() {
persons.remove(positon);
mMyAnimListAdapter.notifyDataSetChanged();
}
}, 500);
}
Also there is some API that generates list of person that we can get. This API makes changes to the list (status changes to one of the next value: "none", "removed", "liked, "disliked").
If status changed to "removed" I need to remove this person from list and update ListView. I do this so:
if (changedPerson.getStatus().equals("removed")) {
persons.remove(index);
mMyAnimListAdapter.notifyDataSetChanged();
notifyRemovedStatus(idOfChangedPerson);
}
But when I do this, a lot of bugs occured:
List updates only after tap.
The fragment isn't closed if last item removed from list.
ArrayIdexOfBound Exception some times occured if we press on like/dislike button.
Can you help me, why is this hepenned and how to fix it?
The solution was easy, here code without bugs:
if (changedPerson.getStatus().equals("removed")) {
persons.remove(index);
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
mMyAnimListAdapter.notifyDataSetChanged();
if (persons.size() == 0) {
getActivity().onBackPressed();
}
}});
notifyRemovedStatus(idOfChangedPerson);
}
I violated this rule "Do not access the Android UI toolkit from outside the UI thread".
This video explains working conceop:
https://www.udacity.com/course/viewer#!/c-ud853/l-1469948762/e-1530568562/m-1530568563
And this documentation was useful: http://developer.android.com/guide/components/processes-and-threads.html
long time watcher, first time writer :P
I got this problem:
I can't seem to change anything that has to do with the layout of android from my playSoundThread.
In this example, I use EventListeners. I already tried the simple way. I passed the ScrollView through, so that the thread can change it. But when it's happening, the thread stops immediately. And even when I use EventListeners, the same Problem occurs.
Changing a variable and posting log information works fine, but not layout Objects.
The first thing is, that I want to scroll a HorizontalScrollView from out the Thread's run() method.
the second case is, that, if the thread comes to it's end, I wanna fire an "i'm finished"-Event and change the image and function of an ImageButton
Here's the run()-method of the thread
public void run() {
if(this.playbackPosition < rhythm.tracks.get(0).sounds.size()) {
for (Track t : rhythm.tracks) {
if (t.sounds.get(this.playbackPosition).equals("1")) {
this.sp.play(t.SoundID, 1, 1, 1, 0, 1);
}
}
this.playbackPosition++;
if ( this.playbackPosition >= (this.scrollIndex*(192/this.zoom)) ){
this.scrollIndex++;
//Here I wanna fire the "Scroll" event
for(ScrollListener sl : scrollListeners){
sl.update(scrollPositions[scrollIndex]);
}
}
}
//This is the point where the playback is finished and the event to change a button is fired
else {
tmpListener.update();
}
}
}
The declaration of the OnPlaybackFinishedListener can be found in the class Player, which is the parent of the PlaySoundThread:
public void addOnPlaybackFinishedListener(){
tmpListener = new OnPlaybackFinishedListener() {
#Override
public void update() {
scheduledExecutorService.shutdown();
//this is a seconds Listener, which was implemented to test, if the problem still occurs with a little listener chain
shutdownListener.update();
}
};
}
public void addShutdownListener(OnExecutorShutdown sl){
this.shutdownListener = sl;
}
And here's the part of the MainActivity which is the parent class of Player and adds the shutdown listener and the ScrollListener:
awesomePlayer.addScrollListener(new ScrollListener(){
public void update(int position){
Log.i("ScrollListener update()","Running ScrollTo( "+position+", "+VIEW_rhythmscroll.getScrollY()+")");
VIEW_rhythmscroll.scrollTo(position, VIEW_rhythmscroll.getScrollY());
}
});
awesomePlayer.addOnPlaybackFinishedListener();
awesomePlayer.addShutdownListener(new OnExecutorShutdown() {
#Override
public void update() {
// TODO Auto-generated method stub
//This method changes the Pause Button to a Play Button with a new OnClickListener and a new Picture
BUTTON_STOP.performClick();
}
});
Can anyone help? Is there another way to avoid this problem? I'm developing on Android 2.2
Is it even possible to access UI elements from a thread?
Thanks in advance :)
You can't modify UI elements from a seperate thread, UI elements have to be modified from the main, UI Thread. There are a lot of topics on this, but you can update the UI by using an AsyncTask's onPostExecute(), onPreExecute(), or onProgressUpdate() methods, the Activity class's runOnUiThread(Runnable action), or by sending a Message to a Handler.
I know there are a tonne of answers on SO for this but I can't seem to get it working how I'd like. I have a SherlockFragment containing a ListView which is populated by some data retrieving from a file. I've added a refresh button to the Fragment, the idea being that when you press it it retrieves any changes in the file and adds them to the view if necessary. I also added the popular PullToRefresh library.
Unfortunately, nothing changes however when I reload the Fragment (for example, rotating the device) I can see the new data. I've read about notifyDataSetChanged() but it isn't working for me. The only thing I've gotten working is calling mPager.notifyDataSetChanged() from my main FragmentActivity class and having the following set in my ViewPager adapter:
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
This accomplishes what I want, but it's not very sleek about it, and you can see it forcing the reload as it were. I'm currently using PullToRefresh and it forces it off page abruptly and quite frankly it just looks bad. To put it bluntly I need to call onCreateView of my Fragment from my AsyncTask reloading the data.
If needed I'll post code, there's just a lot of it so I wouldn't want to post anything unneeded.
Oh, and please note I tried notifyDataSetChanged() in the onPostExecute() of my task.
Thanks for any guidance.
I found a way around this but it's not perfect, so I won't accept this in case something better comes along.
Because upon return to my Fragment onResume() is called (this answer is also pretty specific to my project), I just did the following:
#Override
public void onResume(){
super.onResume();
// Make the adapter again
adapter = new FeedListAdapter(this, feed);
// Set it to the list again
list.setAdapter(adapter);
}
This refreshes the list (badly) and is still fairly noticeable, although if I use a button instead of PullToRefresh it isn't.
My solution (however bad) alongside PullToRefresh is to stick it in a handler with a delayed trigger to let the pulled down "refresh" section disappear before it runs.
#Override
public void onResume(){
super.onResume();
new Handler().postDelayed(new Runnable() {
public void run() {
adapter = new FeedListAdapter(this, feed);
list.setAdapter(adapter);
}
}, 500);
}
Again, this is pretty specific to my project and a very strange way of doing it, so any "perfect" answer please share :)
EDIT: There was still an issue with it being a bit jumpy with PullToRefresh, so my solution was to wait in a new thread until the PullToRefresh is hidden again, then rebuild the list (it's messy, but it works so whatever):
#Override
public void onResume(){
super.onResume();
// Start a new thread
new Thread(new Runnable() {
#Override
public void run() {
try {
// Wait until the PullToRefresh is hidden
synchronized(this){
wait(190);
}
} catch(InterruptedException ex) { }
// Post a new runnable
threadHandler.post(new Runnable(){
public void run(){
// Recreate the adapter from the new feed
adapter = new FeedListAdapter(FeedListActivity.this, feed);
// Set the recreated adapter
list.setAdapter(adapter);
}
});
}
}).start();
}
}
EDIT 2:
Just noticed I missed something obvious. I just changed my list.onRefreshComplete(); for the PullToRefresh view to my onResume() and the jumpiness was taken care of. Still, I think the above solution is more impressive :p
So my code:
#Override
public void onResume(){
super.onResume();
// Recreate the adapter from the new feed
adapter = new FeedListAdapter(this, feed);
// Set the recreated adapter
list.setAdapter(adapter);
// The list has finished refreshing
list.onRefreshComplete();
}
The problem I have is that listView.getLastVisiblePosition always returns -1 so I can't hide the searchView. I check this right after setting the adapter and anywhere I have tried to put this it still returns -1. I didn't see in the Docs why this would be but I imagine it would return -1 if the ListView is not showing any items. However, listView.getFirstVisiblePosition() returns 0 always, even when there is more than one item showing.
I have tried both methods Here but it doesn't make a difference when getting the wrong value.
#SuppressLint("NewApi") private void setFilters(String curType, Object curFilter)
{
// initialize several lists
itemsAdapter = new ArrayAdapter<Rowdata>(this, R.layout.list_item_text, foodItems);
listView.setAdapter(itemsAdapter);
int numItems = listView.getLastVisiblePosition() - listView.getFirstVisiblePosition();
if (numItems > foodItems.length)
{ searchField.setVisibility(View.GONE); }
else
{ searchField.setVisibility(View.VISIBLE); }
}
This method is called any time a Button is pressed or text is changed that can filter through the list. So the question is why would listView.getLastVisiblePosition() always return -1 and why would listView.getFirstVisiblePosition() always return 0? No errors/exceptions, everything runs fine except for not getting the expected results. Note: itemsAdapter.getCount() returns the correct value.
Also, I have to support API >=10
Edit
If anyone needs clarification, let me know. But basically, I have an EditText I use to search through the list. I want to hide this when there aren't more items in the list than what fit on the screen. listView.getLastVisiblePosition() always returns -1
I would really like to know the cause of the original problem but if anyone has any better way of hiding the search box when items all fit on the screen, I am open to suggestions.
Update
I put a breakpoint in onItemClick() and there I get the correct values for getFirstVisiblePosition(), getLastVisiblePosition(), and listView.getChildCount(). Before this, I get 0, -1, and null respectively.
What you need to do is roughly
listview.post(new Runnable() {
public void run() {
listview.getLastVisiblePosition();
}
});
Why this way and not directly?
Android apps run in a big event loop known as the UI / main thread. Everything that is executed in there is the result of some event. For example when your Activity needs to be created that's some sort of Activity creation event. The loop will execute code that handles this event and will for example once your are considered "created" call the onCreate method. It might call more than one method within the same iteration but that's not really important.
When you setup things like the UI in any of those onSomething methods nothing is actually drawn directly. All you do is set some state variables like a new Adapter. Once you return from those on methods the system gains back control and will check what it needs to do next.
The system will for example check if the window needs to be redrawn and if so will enqueue a redraw event in the event queue which is at a later point executed by the loop. If nothing needs to be drawn it's just idle and will wait for example for touch events that are enqueued for that loop as well.
Back to your problem: By calling .setAdapter() you essentially reset all states of the ListView. And since actual updates of the ListView will only happen after you hand control back to the system you will get nothing useful out of .getLastVisiblePosition().
What needs to happen before is that ListView is instructed to be redrawn or to measure it's new size, count the amount of items it has and so on. Once it has done that it will be able to give you the required information.
.post(Runnable r) simply enqueues a Runnable into the eventqueue which is then executed by the loop once it's first in the queue.
a Runnable does not require a Thread, it's just a regular Object with a method named run() and the contract of a Runnable is simply that something (which often happens to be a Thread) can call the run() method to execute whatever you want to run. Magical loop does that.
Result of you posting a runnable is looks inn pseudo code somewhat like this:
void loop() {
yourActivity.onSomething() { loop.enqueue(runnable) }
ListView.redraw() // |
runnable.run() // <--+
}
My suggestion to resolve this problem will not be professional or light weight.
I am suggesting that you should get count of all views in listView and check every one of them are they visible.
example:
private int getIndexOfLastVisibleView(ListView view){
int count = view.getChildCount()-1;
for(int i = count ; i>=0 ; i--){
View checkedView = view.getChildAt(i);
if(checkedView.isShown()){
return i;
}
}
return -1;
}
May not be perfect but I hope that it will work.
You can refer to my answer here Strange problem with broadcast receiver in Android not exactly the same but you can get the idea why your code not working.
To make it more clear, when you set the adapter to the ListView, nothing has been drawn yet and the method getLastVisiblePosition() can only return the correct value after the listview finish drawing all of it's visible children and know which one is the last visible one.
So, the most appropriate approach I can suggest here is trigger a callback after the listView finished drawing and we get the correct value then.
The ListView with listener after drawing:
static class MyListView extends ListView {
private OnDrawCompletedListener mOnDrawCompletedListener;
public MyListView(Context context) {
super(context);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mOnDrawCompletedListener != null) {
mOnDrawCompletedListener.onDrawCompleted();
}
}
public void setOnDrawCompletedListener(OnDrawCompletedListener listener) {
mOnDrawCompletedListener = listener;
}
public static interface OnDrawCompletedListener {
public void onDrawCompleted();
}
}
The sample code for getting last visible position
mListView.setAdapter(new EfficientAdapter(this));
//Will get -1 here
Log.e("Question-17953268",
"getLastVisiblePosition = "
+ mListView.getLastVisiblePosition());
mListView.setOnDrawCompletedListener(new OnDrawCompletedListener() {
#Override
public void onDrawCompleted() {
//Will get correct value here
Log.e("Question-17953268",
"getLastVisiblePosition = "
+ mListView.getLastVisiblePosition());
}
});
Thanks to zapl's answer I was able to get what I needed. I thought I would post the full code in case it helps anyone
listView.post(new Runnable()
{
public void run()
{
int numItemsVisible = listView.getLastVisiblePosition() -
listView.getFirstVisiblePosition();
if (itemsAdapter.getCount() - 1 > numItemsVisible)
{ searchField.setVisibility(View.VISIBLE); }
else
{
searchField.setVisibility(View.GONE);
setFilters("searchtext", "");
}
}
});
I'm having a problem refresh the data in list view.
I get the data in the list from a server, and when I want to refresh the data I need to go to the server and receive the new data.
the notifyDataSetChanged() not helping and also the ListView.invalidateViews not helping.
when I rotate the device the list updated.
how can I load the list view in the same way the screen rotation do it?
This is the code on create that fill the list view.
thanks in advance.
query = new ParseQuery(PET_CLASS_NAME);
petListView.addHeaderView((View)getLayoutInflater().inflate(R.layout.header_row, null));
petDetailIntent = new Intent(getApplicationContext(), PetDetailActivity.class);
selectCityIntent = new Intent(this, CitiesActivity.class);
loadingIntent = new Intent(getApplicationContext(), LoadingActivity.class);
startActivityForResult(loadingIntent, LOADING_INTENT_CODE);
/*the user see list of pets that are still missing*/
query.whereEqualTo(PET_FOUNDED, false);
selectedCity = settings.getString("cityQuery", "");
if(selectedCity != ""){
query.whereEqualTo(PET_CITY, selectedCity);
}
query.findInBackground(new FindCallback() {
#Override
public void done(List<ParseObject> list, ParseException e) {
if (e == null) { //objects retrieved well
petList.addAll(list);
//MyAdapter
adapter = new MyAdapter(
getApplicationContext(),
android.R.layout.simple_list_item_1,
R.id.tv_pet_name,
petList);
setListAdapter(adapter);
}
else{
toaster(getResources().getString(R.string.error_message_load_pets));
finish();
}
finishActivity(LOADING_INTENT_CODE);
}
});
Use a AsyncTask for loadData from Server. It will load it faster.
Try this out:
private class YourTask extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... s) {
//Here you have to make the loading / parsing tasks
//Don't call any UI actions here. For example a Toast.show() this will couse Exceptions
// UI stuff you have to make in onPostExecute method
}
#Override
protected void onPreExecute() {
// This method will called during doInBackground is in process
// Here you can for example show a ProgressDialog
}
#Override
protected void onPostExecute(Long result) {
// onPostExecute is called when doInBackground finished
**// Here you can for example fill your Listview with the content loaded in doInBackground method**
}
}
And than you just have to call this AsyncTask always if you loading content from your server:
new YourTask().execute("");
Try it out!.. Hope this helps..
When you rotate the device, the activity is actually started stopped and started and your initial request will be made again.
You should place your request code into a method and recall it yourself
Similar to what Rawkode mentioned, it doesn't seem like the code that actually does the work of retrieving data from the server is reusable (since it lives in onCreate()). Take a look at this diagram: http://developer.android.com/images/activity_lifecycle.png. As you can can see, the onCreate() method only gets executed once, unless the Activity is re-created (i.e. rotating screen).
Also, from the given code, there doesn't seem to be evidence of a refresh method either. How will users be able to refresh the data? Consider refactoring your code such that the work is done in a method that you can call later on (i.e. refreshData()) and then figure out a way in which you would like your users to refresh. For example, you can use either the ActionBar with a refresh ActionItem, or a menu option or even a button.