2

I have parent layout with databinding. I want to add child layout depends on logic. I expected to need similar code:

    <include
        android:id="@+id/layout_id"
        layout="@{model.isOk ? @layout/layout_container_1 : @layout/layout_container_2}"
        app:model="@{model}" />

But I get error:

android.databinding.tool.processing.ScopedException: [databinding] {"msg":"included value (@{model.isOk ? @layout/layout_container_1 : @layout/layout_container_2}) must start with @layout/.","file":"x.xml","pos":[]}

Is it possible to include child layout dynamically through "if-else" condition? If yes what is the best way? Any samples are welcome!

Tapa Save
  • 4,769
  • 5
  • 32
  • 54

2 Answers2

1

This is impossible with the current implementation of <include> tag. By "implementation" I mean how it is actually used by the SDK.

<include> tag is parsed in the process of layout inflation which means layout attribute of <include> tag must have a layout resource ID defined before any of your code logic can be executed. While data-binding is what you can use after a layout was successfully inflated.

Jenea Vranceanu
  • 4,530
  • 2
  • 18
  • 34
1

Is it possible to include child layout dynamically through "if-else" condition?

So far, this is not possible, because the layout attribute must point to a certain layout before the data-binding works:

(Data Binding) As its name implies, it binds data to a layout, so there must be a layout first, in order to bind data to it.

In other words, the layout="@{model.isOk ? @layout/layout_container_1 : @layout/layout_container_2}" doesn't work because there should be an existing layout before examining model.isOk, and also the layout attribute expects a layout resource after the @ symbol.

So, we need to do that programmatically instead of the XML layout. And this an be done by replacing the <include> with ViewStub which is a sort of deferring layout inflation to be done in the activity.

So before inflating the ViewStub layout, you can have a chance to check whether the model.isOk in the activity before deciding which layout can be inflated on the ViewStub.

And hence based on the model.isOk value, we can set the layout with binding.myLayoutStub.myViewStub?.layoutResource = R.layout.layout_container_1

Eventually we can inflate the new ViewStub layout using binding.myLayoutStub.myViewStub?.inflate() method.

Using the ViewStub, will make you have a couple of binding object, one for the activity (or fragment), and the other for the inflated layout of the ViewStub.

Here is an example like yours:

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="model"
            type="com.example.android.databindingexample.ActivityViewModel" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ViewStub
            android:id="@+id/layout_stub"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

And we've layout_container_1 & layout_container_2 that only differ in the the textview value:

layout_container_1.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout>

    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/purple_200">

        <TextView
            android:id="@+id/textview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Layout 1"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

ViewModel:

class ActivityViewModel : ViewModel() {
    var isOk = true
}

Then decide which layout in the activity based on the model.isOk whenever you inflate the ViewStub:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    // This is of Any type as it will be casted later to the proper DataBinding generated class when the ViewStub layout inflated
    private lateinit var stubBinding: Any

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        val model = ViewModelProvider(this).get(ActivityViewModel::class.java)

        binding.model = model

        // Listener to ViewStub inflation so that we can change its layout
        binding.layoutStub.setOnInflateListener { _, inflated ->
            stubBinding =
                (if (model.isOk) DataBindingUtil.bind<LayoutContainer1Binding>(
                    inflated
                )
                else DataBindingUtil.bind<LayoutContainer2Binding>(
                    inflated
                ))!!
            
            // Changing the text of the inflated layout in the ViewStub using the ViewStubBinding
            if (model.isOk)
                (stubBinding as (LayoutContainer1Binding)).textview.text = "This is Layout container 1"
            else
                (stubBinding as (LayoutContainer2Binding)).textview.text = "This is Layout container 2"
        }


        // Inflating the ViewStub
        if (!binding.layoutStub.isInflated) {
            binding.layoutStub.viewStub?.layoutResource = if (model.isOk)
                R.layout.layout_container_1 else R.layout.layout_container_2

            binding.layoutStub.viewStub?.inflate()
        }

    }

}

Preview:

enter image description here

Zain
  • 37,492
  • 7
  • 60
  • 84