Search code examples
javakotlinandroid-layoutinheritanceandroid-viewbinding

ViewBinding with dynamically added content


I have base activity implementing some features for every screen of my app. Every child activity then overrides method with its XML layout.

BaseActivity

@Override
protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.base_screen);
    View mFormView = findViewById(R.id.pnContent);
    if (getContentAreaLayoutId() != 0) {
        LayoutInflater inflater = LayoutInflater.from(this);
        View contentLayout = inflater.inflate(getContentAreaLayoutId(), (ViewGroup) mFormView, false);
        ((ViewGroup) mFormView).addView(contentLayout, 0);
    }
}

ChildActivity

override fun getContentAreaLayoutId(): Int = R.layout.standard_content_settings

Now I would like to use view binding in my child activities. I was thinking about something like this:

override fun onCreate(savedInstanceState: Bundle?) {
    val binding = StandardContentSettingsBinding.inflate(layoutInflater)
}

But when I used binding (binding.edURL.setText("test")), view does not change and stays empty. I guess I'm using view binding wrong and have to inflate it with another way?


Solution

  • You can inflate the binding object in the BaseActivity and expose methods to child activities to get the binding object.

    public abstract class BaseActivity extends AppCompatActivity {
    
        //base binding
        private BaseScreenBinding baseBinding;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState); // always call base methods, your code didnt have those calls.
    
            baseBinding = BaseScreenBinding.inflate(getLayoutInflater());
            setContentView(baseBinding.getRoot());
    
            //your existing code to inflate child content
            View mFormView = baseBinding.pnContent;
            if (getContentAreaLayoutId() != 0) {
                LayoutInflater inflater = LayoutInflater.from(this);
                View contentLayout = inflater.inflate(getContentAreaLayoutId(), (ViewGroup) mFormView, false);
                ((ViewGroup) mFormView).addView(contentLayout, 0);
            }
        }
        
        protected BaseScreenBinding getBaseBinding() {
            return baseBinding;
        }
    
        protected abstract int getContentAreaLayoutId();
    }
    

    you can now use ViewBinding in your child activity, your layout was inflated by BaseActivity, in child you will bind to it.

    class ChildActivity : BaseActivity() {
    
        private lateinit var binding: StandardContentSettingsBinding
    
        override fun getContentAreaLayoutId(): Int = R.layout.standard_content_settings
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            // Layout was inflated in BaseActivity, so now only bind to it.
            binding = StandardContentSettingsBinding.bind(getBaseBinding().pnContent)
    
            //This should work now
            binding.edURL.setText("test")
        }
    }
    

    In the child activity, calling StandardContentSettingsBinding.bind(View), is different from inflate(), as it doesn't create a new view hierarchy but instead uses the existing one, which is what you want in this case.

    some references: https://developer.android.com/topic/libraries/data-binding/generated-binding