0

I have a list of strings and I'd like to create buttons in a linear layout whose texts come from that list. My linear layout in the XML file is:

...other stuff...
<LinearLayout
    android:id="@+id/my_linear_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"/>

Then in onCreate I have the following code:

buttons = viewModel.myLiveDataList.value!!.map {
    val button = Button(binding.myLinearLayout.context)
    button.layoutParams = LinearLayout.LayoutParams(
        LinearLayout.LayoutParams.WRAP_CONTENT,
        LinearLayout.LayoutParams.WRAP_CONTENT)
    button.text = it.displayName
    return button
}

I feel like I'm not doing this right because I never call addView on the linear layout, which all the examples I've seen do, but when I tried to call addView I got errors about the button already having a parent.

The result of this code is that the fragment displays only a single button whose text is the first entry of the list. It does not display the "other stuff" that exists before the linear layout and it does not display buttons for any other entries of the list.

I've verified with logging that the list itself does have multiple entries but the map function only executes on the first entry, and I don't know why everything else in the view is obliterated and I only get a single button.

Can anyone explain to me what I'm doing wrong?

Edit: As requested, I had originally tried to follow the example here and wrote the following:

buttons = viewModel.myLiveDataList.value!!.map {
    val button = Button(this)
    button.layoutParams = LinearLayout.LayoutParams(
        LinearLayout.LayoutParams.WRAP_CONTENT,
        LinearLayout.LayoutParams.WRAP_CONTENT)
    button.text = it.displayName
    binding.myLinearLayout.addView(button)
    return button
}

This wont compile because Button needs a context, so I changed that to Button(this.context) and then it crashes on addView with the message

java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
Jim
  • 204
  • 2
  • 8
  • Do the examples [here](https://stackoverflow.com/questions/3204852/how-to-add-a-textview-to-linearlayout-in-android) help? You do need `addView` so you should probably add how you are calling that to the question – Tyler V Dec 26 '21 at 01:09
  • have you considered using a `RecyclerView`? https://developer.android.com/guide/topics/ui/layout/recyclerview – ChocolateChapta Dec 26 '21 at 11:55
  • @TylerV: The problem with that example is that the interface to those classes seems different in Kotlin than in Java. For example, I can't pass in `this` to create a button, I need to pass in a context, and I can't pass the button into `addView` because I get an error about needing to remove it from it's parent. – Jim Dec 26 '21 at 14:48
  • @ChocolateChapta: I hadn't. If I can't get what I'm trying to do to work then I guess I'll investigate that for attempt #2, thanks. – Jim Dec 26 '21 at 14:53
  • Add the code you tried to the question. And the recycler view is a good idea. – Tyler V Dec 26 '21 at 15:12
  • @TylerV: Added in what I tried previously, and yea, I'm reading up on RecyclerView now, though it would still be nice to know what's going wrong here. – Jim Dec 26 '21 at 15:26

1 Answers1

1

Your code was close, but when you use this and return in an inner scope in Kotlin you sometimes need to specify the scope of those parameters. You got an error because this was referring to the map lambda, not the activity, which is why it said it expected a Context.

This works for me - successfully adds three buttons to the linear layout. Note the use of this@MainActivity to get the enclosing activity and return@map. You can also just type button on the last line without a return statement, and sometimes it can correctly infer the right scope for this without the @, but it's usually a good idea to specify it.

val linearLayout = findViewById<LinearLayout>(R.id.my_linear_layout)

val buttonText = listOf("Button A","Button B","Button C")
val buttons = buttonText.map { txt ->
    val button = Button(this@MainActivity)
    button.layoutParams = LinearLayout.LayoutParams(
        LinearLayout.LayoutParams.WRAP_CONTENT,
        LinearLayout.LayoutParams.WRAP_CONTENT)
    button.text = txt
    linearLayout.addView(button)
    return@map button // or just "button" by itself without a "return"
}

Note: I saw the same error about needing a context for Button when I first copied in your code, but in subsequent attempts to replicate it, this seems to work fine. I have no idea why it didn't work before and works now, but using the @ notation can't hurt. In Android Studio you can hover the mouse over the this text to see what scope it is referring to.

Resulting Layout

example output

Tyler V
  • 9,694
  • 3
  • 26
  • 52