android studio: unique identifier for each element - android

I have an android application where not all elements have unique ID's. For example, two TextViews are each called "itemButton" and are on the same screen. I want to give every element a unique identifier by setting a tag on each element.
My current solution is to iterate through every element in the application and set the tag for each element. This is a very expensive solution because I have many elements. Is there another property you know of that would help identify an element other than setting a unique tag for each element?

View IDs have no special requirement that they be unique. However, you will run into difficulties if you use non-unique IDs on a single screen.
The two most common issues you will face if you use non-unique IDs are (1) failure of the system to automatically save instance state for the view and (2) findViewById() returning the "wrong" view.
Activity.findViewById() will search the view hierarchy for the first view it finds with the matching ID. If you have two views in your hierarchy with the same ID, that means you won't be able to find the second one using this method. However, you can use View.findViewById() instead.
View.findViewById() will search the view hierarchy starting from the view you're invoking the method on, which means that you can differentiate between two views with the same ID as long as they have different parents.
In your case, I suspect you can do something like the following:
View parentOne = findViewById(R.id.parentOne);
View childOne = parentOne.findViewById(R.id.someIdBeingReused);
View parentTwo = findViewById(R.id.parentTwo);
View childTwo = parentTwo.findViewById(R.id.someIdBeingReused);

I don't think you got it right, you should assign unique ids to your elements by using in each of them: android:id="#+id/YOUR_ID", then you can find them with findViewById(R.id.YOUR_ID), so, if you have two text views, lets say, username and password, you set the ids on each:
<TextView android:id="#+id/username" .../>
<TextView android:id="#+id/password" .../>
and then you get them in your activity or fragment with:
TextView txtUsername = findViewById(R.id.username);
TextView txtPassword = findViewById(R.id.password);

Related

Querying root and indirect sibling views in Android ListView

I have a ListView to show a list of articles. Each ListView element is a LinearLayout. On each article, there is a TextView button to edit the title (another TextView). However, the button and the title are not under a direct parent (and actually not at the same level).
A sample structure is shown below:
<LinearLayout>
<LinearLayout
android:id="#+id/title_zone">
<TextView
android:id="#+id/title">
<ImageView
android:id="#+id/icon_popularity">
</LinearLayout><!--end of title_zone-->
<LinearLayout
android:id="#+id/content_zone">
...
</LinearLayout><!--end of content_zone-->
<LinearLayout
android:id="#+id/button_zone">
<LinearLayout
android:id="#+id/author_buttons">
<TextView
android:id="#+id/edit_title_button"
android:onClick="editTitle">
...
</LinearLayout><!--end of author_buttons-->
</LinearLayout><!--end of button_zone-->
</LinearLayout>
I write a SimpleAdapter to apply data to views, so the root LinearLayout will have a tag of the article ID. When editTitle() is called, it needs to find its parent's parent's parent to the root. And after new title is entered, a message will send to server containing new title and the article ID. Also, the title text will be changed visually, which means I need to find the title TextView based on the root.
The problem is that this querying root process is tightly coupled to the UI structure. If I changed the structure in XML, I need to pay attention to change the querying code in Java. (The querying title view is relatively easy, if the root is obtained.)
Is there a more maintainable way to implement my purpose?
OK. A solution that works for my situation is as follows:
In the customized simple adapter for this ListView, set the article ID as the edit_title_button's tag. And set the article ID composed string as the root LinearLayout's tag, e.g., "a_123" if the article ID is 123.
So, when editTitle(View v) is called, we can get the story ID by v.getTag(). Of course, we can also get the ListView by its ID (e.g., ListView list=findViewById(R.id.my_list_view);). Then, we can simply get the root LinearLayout by list.findViewWithTag("a_123").
Since the root LinearLayout is obtained without knowing the UI structure, following codes are easy to maintain.

Is it possible to generate a list of `EditText` pairs that automatically retain their state?

I am running into a very frustrating series of problems. I would like a definitive solution; thus, I will award bounty for this question.
Requirements
Generate a list of pairs of EditText views that automatically retain their values on orientation change (screen rotation).
The number of EditText pairs is determined at runtime.
Failing method: Use a ListView
Use a ListView that has an ArrayAdapter. The array adapter uses a layout to generate each pair of TextView views. The XML for a ListView item might appear as:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<EditText
android:id="#+id/edit_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<EditText
android:id="#+id/edit_second"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
The ArrayAdapter would simply inflate the layout for each view.
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View row = inflater.inflate(R.layout.listview_pair, parent, false);
The values entered into the EditText are not retained on orientation change. Since the EditText views are instantiated through the ArrayAdapter, the Android runtime has no way of automatically pre-populating the EditText views with their last state before rotation. This means we must save/restore user-input manually -- failing requirement #1.
Failing method: Use a TableLayout and TableRows
Instead of using a ListView, we can inflate one TableRow layout for each EditText pair and attach it to the TableLayout. Since we only know the number of pairs at runtime (per requirement #2) we must instantiate the layout programmatically. Something like:
for (int i = 0; i < numPairs; ++i)
{
TableRow row = (TableRow) View.inflate(this, R.layout.tablerow_pair, null);
table.addView(row);
}
This also fails to retain state on orientation change. Each pair of EditText views has the same Ids as all other EditText pairs in the ListView. This happens because we instantiate the same layout for each item in the list; thus, they all have the same Ids. On rotation, Android will give every pair of EditText values the same values since they share the same Id.
Recapitulation
So, is there a way to create a list of EditText pairs that retain their state automatically upon orientation change? It seems like there must be a way, yet researching this question is difficult because it's a fairly specialized use case. I would be glad to reward some bounty for somebody who can give me a nice explanation of this situation.
You can use setId() with your EditText widgets, whether you create those widgets via their constructors or via layout inflation (per your second strategy). Use generateViewId() to get distinct view IDs.

How to add editable View components to a layout in Android?

So, what I'd like is: defining a component, which includes TextView-s and an ImageView. This is an item, which I'd like to add to a (for example Linear) layout, so I can display all the custom items, I added one after the other.
The point is, that these items have to be editable, because a database query result will define their text content and the image.
Your custom component should be a ViewGroup itself. You can add any number of TextViews and ImageViews to it, and access them by their ID.
MyCustomViewGroup component = (MyCustomViewGroup)linearLayout.findViewById(...);
TextView textView1 = (TextView)component.findViewById(...);
ImageView imageView = (ImageView)component.findViewById(...);
You can go for the XML approach for defining your component. For instance, you can define your component as a LinearLayout, then add all the elements (TextViews, ImageViews) you need to that layout.
As for the "editable" part, just provide your elements with an id property
android:id="#+id/my_view"; that way you may obtain them through a findViewById(R.id.my_view) call.
You cast the results to whatever View implementation you are expecting, then change it's text/content/image with the interpreted results from your query.

How to Dynamically check if XML contains UI Element

I have multiple XML files that need to be used by one Activity for its view, the Activity will load an XML file from the name passed to it when its created. These XML files will contain a set of UI elements such as buttons that will have a standardised name (ie UpBtn, DownBtn).
The views will be different (containing different button names) but I want to be able to check if a button of a specific name exists within the XML so that I can perform a specific action in the Activity.
Is there a way of doing this or will I have to resort to having an Activity per XML?
In your xml, provide your views with an unique id
<Button
android:id="#+id/upBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
In your activity, provided that you have already called setContentView() with one of the XML files, you can call findViewById() to find a particular view by its id:
Button upBtnView = (Button) findViewById(R.id.upBtn);
If this view is present in the xml you provided, upBtnView will be the button you want; otherwise, upBtnView will be null, and you will know that it is not there in your layout.
You can use findViewById( id ) and if it returns null then the id you specified doesn't exist.

Duplicate layout IDs returning as -1 after view replacement

Short Story:
I have a layout "layout.xml", which gets replaced by another layout "success.xml" after a successful web request. Both layouts have an ImageView that provides the backgrounds to the layouts. These 2 backgrounds both need to be the same, and both are dependent on a user preference.
Longer Story: This all happens in a Fragmnet with an AsyncTask replacing the contentView with "success.xml" in onPostExecute after the web request. This happens as follows:
View view = getView();
view = null;
view = View.inflate(context, R.layout.success, null);
What I tried to do is give both ImageViews the following android:id="#+id/background_image" and then call
ImageView background = (ImageView)view.findViewById(R.id.background_image);
background.setImageResource(R.drawable.bg1);
This background-setting works for the initial view (layout.xml), but on trying to change to "success.xml", I get a NullPointException because background is null.
I've checked and the View's id is set to -1 while the original view's background_image id is set to something sensible and valid.
I've also tried setting the second view's background id like this: android:id="#id/background_image", i.e. without the '+', but still no luck.
The added complication is that it's not just 2 layouts, but about 5 that I need to do this for, so it would be really handy to recycle view id's.
Any help would be greatly appreciated.
Your code for replacing the fragment's view will not do what you want, the original view will remain the same as you change only a reference to that view and not the actual object.
To replace the view of the fragment with the new layout you could have another ViewGroup(for example a FrameLayout) in the basic layout (layout.xml) wrapping your current content(don't forget to give it an id) of layouts.xml(as I understand this is the basic layout). Then, when it's time to replace the layout you could simply do:
// remove the current content
((ViewGroup) getView().findViewById(R.id.yourWrapperLayout)).removeAllViews();
// add the new content
View.inflate(context, R.layout.success, ((ViewGroup) getView().findViewById(R.id.yourWrapperLayout)));
You could avoid adding an extra layout if, by any chance, all your five layouts have the same type for the root view(like a LinearLayout etc). In this case you would use the same code as above but you'll modify the other layouts file to use a merge tag. Also, you'll be looking for the id of the root in the layout.xml layout into which you'll add the content of the other files.
Then you could have the same ids, but you'll have to reinitialize any reference to the views(meaning that you'll have to search for the view again if you store a reference to the view(like a Button field in the fragment class)).

Categories

Resources