348

I'm adding TextViews programmatically in a for-loop and add them to an ArrayList.

How do I use TextView.setId(int id)? What Integer ID do I come up with so it doesn't conflict with other IDs?

znq
  • 42,465
  • 38
  • 114
  • 143

15 Answers15

619

From API level 17 and above, you can call: View.generateViewId()

Then use View.setId(int).

If your app is targeted lower than API level 17, use ViewCompat.generateViewId()

X.Y.
  • 12,991
  • 9
  • 48
  • 62
  • 2
    I put it in my source code because we want to support lower API levels. It is working but the infinite loop is not a good practice. – SXC Sep 19 '13 at 00:07
  • 5
    @SimonXinCheng Infinite loops is a common pattern used in non blocking algorithms. For example have a look at `AtomicInteger` methods implementation. – Idolon Nov 14 '13 at 14:22
  • 7
    Works great! One note: based on my experiments, you must call setId() BEFORE you add the view to an existing layout, or else OnClickListener won't work properly. – Luke Nov 20 '13 at 09:46
  • 4
    Thank you would be too small but THANK YOU. Question, what is `for(;;)` I've never seen that before. What is that called? – Aggressor May 13 '15 at 16:41
  • What if I want to access to a specific view later? How would I know the id? Let's say the 10th out of 50 generated views? – Mitulát báti Dec 20 '15 at 16:54
  • Make sure you increment the number in AtomicInteger for each view you generate with this, otherwise they'll all be 1 and you'll be banging your head for a while trying to figure out why it's not working :) – Tom Hammond Dec 24 '15 at 19:07
  • 5
    @Aggressor : Its an empty 'for' loop. – sid_09 Jan 07 '16 at 09:19
  • Are these id's registered into R.java? – Irshu Jan 14 '16 at 10:03
  • You should've placed ```if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {``` inside `generateViewId()` because this code will work correctly only with the boilerplate code around it – Boris Treukhov May 08 '18 at 08:34
  • I disagree. This is only for below 17 where there's no google implementation on generateViewId(). Everywhere else, you should be using Google's. – X.Y. Sep 26 '18 at 16:06
  • 1
    ViewCompat also has this method for api lower than 17 – M.kazem Akhgary Nov 01 '18 at 07:36
  • 1
    This should be the answer! – Xebozone Dec 28 '18 at 07:20
159

You can set ID's you'll use later in R.id class using an xml resource file, and let Android SDK give them unique values during compile time.

 res/values/ids.xml

<item name="my_edit_text_1" type="id"/>
<item name="my_button_1" type="id"/>
<item name="my_time_picker_1" type="id"/>

To use it in the code:

myEditTextView.setId(R.id.my_edit_text_1);
The Hungry Dictator
  • 3,247
  • 4
  • 34
  • 47
Sai Aditya
  • 2,050
  • 1
  • 11
  • 16
  • 23
    This doesn't work when I have an unknown number of elements I'll be assigning ids to. – Mooing Duck Oct 13 '15 at 18:44
  • 1
    @MooingDuck I know this is a year late, but when I have to assign unique Ids at runtime with an unknown number of elements, I simply use `"int currentId = 1000; whateverView.setId(currentId++);` - That increments the ID every time `currentId++` is used, ensuring a unique ID, and I can store the IDs in my ArrayList for later access. – Mike in SAT Dec 12 '16 at 15:59
  • 4
    @MikeinSAT: That only guarantees that they're unique amongst themselves. That doesn't make it "so it doesn't conflict with other IDs", which is a key part of the question. – Mooing Duck Dec 13 '16 at 03:15
  • 1
    This is the winning answer because others were giving Android Studio's code analysis tool a s--- fit, and because I need an ID that tests know without adding yet another variable. But add ``. – Phlip Feb 24 '20 at 13:18
153

According to View documentation

The identifier does not have to be unique in this view's hierarchy. The identifier should be a positive number.

So you can use any positive integer you like, but in this case there can be some views with equivalent id's. If you want to search for some view in hierarchy calling to setTag with some key objects may be handy.

Nikolay Ivanov
  • 8,637
  • 3
  • 29
  • 33
  • 2
    Interesting, I wasn't aware that IDs need not be unique? So does `findViewById` make any guarantees then as to which view is returned if there's more than one with the same ID? The docs don't mention anything. – Matthias May 03 '12 at 10:04
  • 26
    I think the docs mention something about this. If you have views with the same ID in the same hierarchy then `findViewById` will return the first it finds. – kaneda May 07 '12 at 14:32
  • @kaneda i don't think it gets the first id. it gets the id that's in the layout that you set in setContentView() – Dany Y Jun 19 '13 at 16:15
  • 2
    @DanyY I'm not quite sure if I am understanding correctly what you mean. What I tried to say was that if the layout you set with `setContentView()` has let's say, 10 views with their id set to the same id number in the **same hierarchy**, then a call to `findViewById([repeated_id])` would return the first view set with that one repeated id. That's what I meant. – kaneda Jun 19 '13 at 23:33
  • 53
    -1 I don't agree with this answer because onSaveInstanceState and onRestoreInstanceState need a unique id to be able to save/restore the state of the view hierarchy. If two views have the same id, the state of one of them will be lost. So unless you save the View state all yourself having duplicate ids isn't a good idea. – Emanuel Moecklin Apr 09 '14 at 22:39
  • 4
    The **Id should be unique**. Starting from **API level 17** there is a static method in the View class that generates a random Id to use it as view id. That method ensures that the generated id will not collide with any other view id already generated by the aapt tool during the build time. https://developer.android.com/reference/android/view/View#generateViewId%28%29 – Mahmoud Dec 11 '18 at 12:52
  • @EmanuelMoecklin The quoted documentation from Google tells you it's true. :) A very common example of this is a `RecyclerView`. If your adapter represents every item with the same layout, any ID in that layout will be repeated once for every instance of the view currently in the `RecyclerView`. – spaaarky21 Dec 26 '20 at 08:01
62

Also you can define ids.xml in res/values. You can see an exact example in android's sample code.

samples/ApiDemos/src/com/example/android/apis/RadioGroup1.java
samples/ApiDemp/res/values/ids.xml
aleksandrbel
  • 1,267
  • 1
  • 17
  • 33
yenliangl
  • 784
  • 4
  • 4
  • 15
    Here is also an answer with this approach: http://stackoverflow.com/questions/3216294/android-programatically-add-id-to-r-id – Ixx Apr 21 '12 at 10:57
  • For reference, I found the file in: /samples/android-15/ApiDemos/src/com/example/android/apis/view/RadioGroup1.java – Taylor Edmiston May 28 '12 at 04:00
30

Since API 17, the View class has a static method generateViewId() that will

generate a value suitable for use in setId(int)

Diederik
  • 4,385
  • 3
  • 40
  • 53
26

This works for me:

static int id = 1;

// Returns a valid id that isn't in use
public int findId(){  
    View v = findViewById(id);  
    while (v != null){  
        v = findViewById(++id);  
    }  
    return id++;  
}
Yogu
  • 8,033
  • 4
  • 31
  • 51
dilettante
  • 547
  • 6
  • 10
10

(This was a comment to dilettante's answer but it got too long...hehe)

Of course a static is not needed here. You could use SharedPreferences to save, instead of static. Either way, the reason is to save the current progress so that its not too slow for complicated layouts. Because, in fact, after its used once, it will be rather fast later. However, I dont feel this is a good way to do it because if you have to rebuild your screen again (say onCreate gets called again), then you probably want to start over from the beginning anyhow, eliminating the need for static. Therefore, just make it an instance variable instead of static.

Here is a smaller version that runs a bit faster and might be easier to read:

int fID = 0;

public int findUnusedId() {
    while( findViewById(++fID) != null );
    return fID;
}

This above function should be sufficient. Because, as far as I can tell, android-generated IDs are in the billions, so this will probably return 1 the first time and always be quite fast. Because, it wont actually be looping past the used IDs to find an unused one. However, the loop is there should it actually find a used ID.

However, if you still want the progress saved between subsequent recreations of your app, and want to avoid using static. Here is the SharedPreferences version:

SharedPreferences sp = getSharedPreferences("your_pref_name", MODE_PRIVATE);

public int findUnusedId() {
    int fID = sp.getInt("find_unused_id", 0);
    while( findViewById(++fID) != null );
    SharedPreferences.Editor spe = sp.edit();
    spe.putInt("find_unused_id", fID);
    spe.commit();
    return fID;
}

This answer to a similar question should tell you everything you need to know about IDs with android: https://stackoverflow.com/a/13241629/693927

EDIT/FIX: Just realized I totally goofed up the save. I must have been drunk.

Community
  • 1
  • 1
Pimp Trizkit
  • 16,888
  • 5
  • 22
  • 37
9

The 'Compat' library now also supports the generateViewId() method for API levels prior 17.

Just make sure to use a version of the Compat library that is 27.1.0+

For example, in your build.gradle file, put :

implementation 'com.android.support:appcompat-v7:27.1.1

Then you can simply use the generateViewId() from the ViewCompat class instead of the View class as follow:

//Will assign a unique ID myView.id = ViewCompat.generateViewId()

Happy coding !

6

Just an addition to the answer of @phantomlimb,

while View.generateViewId() require API Level >= 17,
this tool is compatibe with all API.

according to current API Level,
it decide weather using system API or not.

so you can use ViewIdGenerator.generateViewId() and View.generateViewId() in the same time and don't worry about getting same id

import java.util.concurrent.atomic.AtomicInteger;

import android.annotation.SuppressLint;
import android.os.Build;
import android.view.View;

/**
 * {@link View#generateViewId()}要求API Level >= 17,而本工具类可兼容所有API Level
 * <p>
 * 自动判断当前API Level,并优先调用{@link View#generateViewId()},即使本工具类与{@link View#generateViewId()}
 * 混用,也能保证生成的Id唯一
 * <p>
 * =============
 * <p>
 * while {@link View#generateViewId()} require API Level >= 17, this tool is compatibe with all API.
 * <p>
 * according to current API Level, it decide weather using system API or not.<br>
 * so you can use {@link ViewIdGenerator#generateViewId()} and {@link View#generateViewId()} in the
 * same time and don't worry about getting same id
 * 
 * @author fantouchx@gmail.com
 */
public class ViewIdGenerator {
    private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);

    @SuppressLint("NewApi")
    public static int generateViewId() {

        if (Build.VERSION.SDK_INT < 17) {
            for (;;) {
                final int result = sNextGeneratedId.get();
                // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
                int newValue = result + 1;
                if (newValue > 0x00FFFFFF)
                    newValue = 1; // Roll over to 1, not 0.
                if (sNextGeneratedId.compareAndSet(result, newValue)) {
                    return result;
                }
            }
        } else {
            return View.generateViewId();
        }

    }
}
fantouch
  • 1,029
  • 12
  • 10
3

In order to dynamically generate View Id form API 17 use

generateViewId()

Which will generate a value suitable for use in setId(int). This value will not collide with ID values generated at build time by aapt for R.id.

Arun C
  • 8,907
  • 1
  • 25
  • 42
2
int fID;
do {
    fID = Tools.generateViewId();
} while (findViewById(fID) != null);
view.setId(fID);

...

public class Tools {
    private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
    public static int generateViewId() {
        if (Build.VERSION.SDK_INT < 17) {
            for (;;) {
                final int result = sNextGeneratedId.get();
                int newValue = result + 1;
                if (newValue > 0x00FFFFFF)
                    newValue = 1; // Roll over to 1, not 0.
                if (sNextGeneratedId.compareAndSet(result, newValue)) {
                    return result;
                }
            }
        } else {
            return View.generateViewId();
        }
    }
}
Dmitry
  • 349
  • 5
  • 8
1

I use:

public synchronized int generateViewId() {
    Random rand = new Random();
    int id;
    while (findViewById(id = rand.nextInt(Integer.MAX_VALUE) + 1) != null);
    return id;
}

By using a random number I always have a huge chance of getting the unique id in first attempt.

Bjørn Stenfeldt
  • 1,170
  • 1
  • 15
  • 21
1

inspired by @dilettante answer, here's my solution as an extension function in kotlin:

/* sets a valid id that isn't in use */
fun View.findAndSetFirstValidId() {
    var i: Int
    do {
        i = Random.nextInt()
    } while (findViewById<View>(i) != null)
    id = i
}
Amin Keshavarzian
  • 2,335
  • 1
  • 26
  • 28
0
public String TAG() {
    return this.getClass().getSimpleName();
}

private AtomicInteger lastFldId = null;

public int generateViewId(){

    if(lastFldId == null) {
        int maxFld = 0;
        String fldName = "";
        Field[] flds = R.id.class.getDeclaredFields();
        R.id inst = new R.id();

        for (int i = 0; i < flds.length; i++) {
            Field fld = flds[i];

            try {
                int value = fld.getInt(inst);

                if (value > maxFld) {
                    maxFld = value;
                    fldName = fld.getName();
                }
            } catch (IllegalAccessException e) {
                Log.e(TAG(), "error getting value for \'"+ fld.getName() + "\' " + e.toString());
            }
        }
        Log.d(TAG(), "maxId="+maxFld +"  name="+fldName);
        lastFldId = new AtomicInteger(maxFld);
    }

    return lastFldId.addAndGet(1);
}
chinwo
  • 1
  • Please add a proper description to your answer in a way that's clearer to future visitors to assess the value of your answer. Code-only answers are frowned upon and can be deleted during reviews. Thanks! – Luís Cruz Dec 12 '16 at 22:41
-1

My Choice:

// Method that could us an unique id

    int getUniqueId(){
        return (int)    
                SystemClock.currentThreadTimeMillis();    
    }
nimi0112
  • 1,753
  • 1
  • 13
  • 29