I've just ventured into the fun world of Android development, but had a very quirky problem with the test app I was working on.
The app uses a TableLayout where each TableRow contains an EditText and some Buttons.
The TableRows can be added and removed at runtime. It all appeared to be working okay, until I accidentally tilted my device. The display responded and rearranged the layout, but suddenly all of the values were the same on each row.
After some head-scratching I figured out what was going on. Because of the orientation change Android was restarting the activity. When this happens Android tries to save and then restore your instance state, but it does this by storing data relative to the component id.
In my case, because the rows are all created from the same layout, then the EditText in every row has the same id. The result as far as I can tell, is that when the info is saved it is being overwritten for each row, so that the last row wins out.
When restoring there is only one value associated with that id and so it gets applied to every row!
In my case I was able to work around it as I didn't really need to keep the values anyway. In my onSaveInstanceState I DON'T call super.onSaveInstanceState, and likewise in onRestoreInstanceState.
So that finally brings me to my question!
What if I DID want those individual row values to be saved and restored? Is there an accepted way of generating unique ids on reused components (in my case the TableRow)?
If I were you, I would not use your_view.setId(your_new_id) on an EditText view, because this makes your app less stable: What if another view happens to have the exact same Id as your_new_id?
I would use your_view.setTag(some_unique_tag) and then use findViewWithTag(some_unique_tag) to look up the EditText view again.
findViewWithTag(some_unique_tag) could be any Object - I personally prefer String because then it's easy to make some descriptive and unique tags.
Remember, it's only the Views that you use .setTag on that has tags.
In addition to setId there is a generateViewId method in the View class. If you want it pre 17 versions you can just copy it from sources.
You could generate your TableRows in Java and use View.setId(). You might also put the table row in a XML layout file, load it via java & set the Ids - but seems more tricky.
http://developer.android.com/reference/android/view/View.html#setId(int)
Related
I use Android DataBinding Library (Two-way) with LiveData (binding syntax #={})
To reuse UI, I intensively use include layout mechanism when designing layout file.
Actually, I include a same layout file multiple times in building a form layout.
Everything gone well until the DEVICE ROTATION. After the device rotates, all the field (editText) get the same value as in the last row (as shown in the picture below).
The problem happens when the activity is re-created after the rotation so I can prevent this by setting for android:configChanges of the activity.
But I'm curious about the root of this problem and how to solve its..
You can find the major parts of the source code below or full source code.
Thanks in advance.
SOURCE CODE
Layout for a row (1 TextView & 1 EditText)
Reuse the layout above 2 time in main layout
ViewModel
Main activity - Binding in OnCreate
You need to remove this line binding.setLifecycleOwner(this);. I did verify myself.
To one who may concern about this problem, the reason seems to be related to the ID of editText in form row layout (Layout for a row (1 TextView & 1 EditText)), i.e. android:id="#+id/editTextID" in this case.
Three rows for first name, last name and password created by using the same row layout so editTexts for these fields haves the same ids.
After the rotation, the frameworks could notify the changes in edit text of the last row (password) but the two first rows also receive these updates. That probably causes the problem.
To resolve it, simple remove android:id="#+id/editTextID" in the row layout.
There is nothing related to ViewModel or its lifeCycle.
add this line to your activity in manifest file,
android:configChanges="keyboardHidden|orientation|screenSize"
Cross posting from Android Google group
I ran into this issue earlier today:
https://code.google.com/p/android/issues/detail?id=55106
The problem is:
LinearLayout has children
every child is inflated from the same XML layout file
a child has nested element with some ID.
The ID is used to call findViewById(ID) to set some value
Everything is displayed properly on startup, but after orientation change every child of LinearLayout displays the value of the last data item
As soon as ID is removed from the child XML layout file, everything starts working properly
The bug has the sample project attached that demonstrates the problem.
Just wanted to ask if somebody has experienced the same issue and knows a workaround?
Thank you in advance.
EDIT:
The situation with LinearLayout is pretty flaky. I finally made the project work - see the second attached project at
https://code.google.com/p/android/issues/detail?id=55106
If you go to item_main.xml and change android:textIsSelectable from false to true, the bug shows up.
Keep android:textIsSelectable="true", but remove android:id="#+id/text" - the bug disappears again
The main reason I was experimenting with this LinearLayout is because I wanted to follow thy layout animation sample.
http://developer.android.com/training/animation/layout.html
But it seems the situation is so unstable with LinearLayout and orientation change that I need to go back to the working ListView+Adapter approach and figure out how to do similar layout animations when adding items to the adapter and revalidating the list.
Another workaround is removing all views from your LinearLayout in onSaveInstanceState method.
#Override
protected void onSaveInstanceState(Bundle outState) {
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.yourLinearLayoutId);
linearLayout.removeAllViews();
super.onSaveInstanceState(outState);
}
Do you have any particular reason why you are inflating 50 children layouts and then inserting them into a ScrollView instead of using an AdapterView?
It seems to me like you should probably be using ListView with some sort of Adapter that will handle the row inflating and data binding for you, which will result in not only improved efficiency and performance but will let you avoid this "bug" which you are encountering.
Any time you are inflating Views within a loop it should be a strong hint that you ought to re-consider your approach and start using an AdapterView of some sort (ListView, Gallery etc...)
When you add a number of TextViews and re-use the same id for them (android:id="#+id/text") my best guess would be that you're stumbling on unexpected behavior (at best) once you invoke the findViewByID method. To do what you apparently want to do (use a list of TextViews you could be doing something like what was proposed here, i.e. create an array of TextViews, instance them and keep them for internal reference.
What you found isn't actually a bug, it's behaving like it should: all the TextViews have the same id, so all will change at the same time.
I looked up the actual reference in the documentation (emphasis mine):
An ID need not be unique throughout the entire tree, but it should be
unique within the part of the tree you are searching (which may often
be the entire tree, so it's best to be completely unique when
possible).
If you have a layout that has a bunch of views that won't be changed during runtime, for example a TextView used as a label, is it proper to remove the ID from it, or to label it like a view that you would use?
What about layouts? If I have a bunch or table rows in a table, should each have a unique ID or should I clear the field?
It seems to me like it removes a lot of clutter if I clear the IDs if views that I won't be changing but I don't want to do that if it's bad practice.
Thanks.
You only need to define an id for an UI component if you want to reference this component later from your program code (e.g. findViewById(R.id.my_textview) ).
Because you said the views won't change during runtime you don't have to define an id for every view.
It can even help you if you don't define an id, because it keeps the auto complete function of your IDE clean. If you have a lot of layouts (which contain lots of ui elements) and you define an id for each component then you will have a nearly endless list of component ids at the end.
#matt: if your are using canvas to draw the views and set layouts then id is not necessary from my knowledge and I never used id fro and I think its not bad practise.
I assumed that each layout's element id's such as buttons, textviews, edittexts, etc. were unique and private to that layout. This being said, you set the Activity to use a certain layout, you should only be able to find view id's based on the id's set in that specific layout.
However, I am finding now that I can reference whatever view id I want from my Activity regardless of the physical layout I have specified with "setContentView(R.layout.THELAYOUTIWANTTOTARGET)". Is this behavior normal, I figured only id's of those elements on the layout I specified above would be available? Looking at R.java, I believe all the id's I specified on all layouts are made public variables, thus, no id's can have the same name or unexpected behavior may occur!
The implications for this on my project is that I must now change all id's in all layouts to be unique. I figured my supplier layout > title textview would have been different from my customer layout > title textview, is this not the case?
Thank you for clarifying!
Your assumption is correct. IDS are global, and for large projects it's sometimes a pain, leading to very long ID names. But that can also be used as an advantage, as you can reuse layouts on different activities.
For example, you can have a layout for a specific part of your activity (a custom buttons bar for example) that you may want to add to several activities. In this case, you can just inflate it into a specific ViewGroup of the first activity, and also in another ViewGroup in another activity. The methods to access specific buttons based on their ids can then be reused in both activities.
The method findViewById will only work on the activity you call it from. If a button (or any other component) exists on different activities, only the one in your current activity will be returned.
Yes, it is normal. As far as I know, there is no way to change it.
So yes, the implication that you will have to have different names for views in different layouts is correct.
EDIT:
Actually... scratch that. I was under the impression that it was necessary, but according to
http://developer.android.com/reference/android/view/View.html
it isn't even necessary to have id's be unique in a single file. Just make sure you aren't searching in a tree that has multiple ids that are the same or you will always get the first occurrence.
It makes sense too since it doesn't really matter if the views have the same ID in R. I will keep this in mind going forward.
i got the following "problem".
I want to have an activity thats shows me the standings of some teams at a specific gameday.
therefor i would add a spinner and a TableLayout. At the first Start the activity should show the standings of the actual gameday but then you can choose any other gaymeday and the standing should get updated.
Whats the best way to create this activity?
assemble the whole TableLayout with all TableRows and TextViews, give them ids and update those views via id during runtime. Problem: huge unflexible hardcoded layout.xml
assemble the layout during runtime, add ids, update via ids
assemble the layout during runtime. on update remove old views and create new ones
assemble the layout during runtime. on update restart the activity
just whant to know which one is the best. or is there any other way to achieve that
thx Cheetah
If I were you, I'd actually use a GridView with an Adapter. This will abstract away all the handling of layout changes. You just have to worry about mapping your data to appropriate views. This example maps ImageViews to a GridView, but there's no reason you couldn't map to TextViews containing your data in a GridView. Also, because you're using an adapter, you can take advantage of all the Loader classes and they're asynchronous loading capabilities.
In addition, using the approach will allow you program to easily adapt as your dataset changes. You may want to add more data to the table in the future and this approach will allow you to easily do that without having to constantly change your xml layouts.
Does the number of views change? If no. Best way is to use the already existent views and update their values. Try to avoid recreating/reinflating views since that's an expensive task.