Android databinding with multiple layouts - android

I have issues with the android data binding. I have layouts for different configurations like ie: activity_main.xml / land/activity_main.xml etc.
Currently when I use setContentView method, just pass the layout name, and it automatically detects which of the layouts should choose to set content view.
But If I use the data binding what would be the solution for that.
As I know the names for the binding would be different depending onto the configuration. So If I use ActivityMainBinding, that always will be the data binding for the same layout. I read about the solution to specify markers( bools for each config) and use the if/else statements and then to inflate the needed binding but that is so bad solution.
Can anyone suggest better solution for the case that an activity/fragment uses different layout for different configurations layout/port/ sw600-port/land etc.
Thanks!

You can just use it the same way:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this,
R.layout.activity_main);
// set all variables in binding
}
The ActivityMainBinding class that is generated will be a base class for bindings of all matching layouts and will have the aggregate of all variables/fields. If Views are only in some of the layouts, some of the field references will be null in some configurations, so you'll have to watch for that. If you are using mostly data binding expressions to set values or attach event handlers, you won't even need to use the View field references, so you won't have to worry about that.
If Views have different types in different layouts, the common base class will be used for the View field.
You can always look at the generated code by looking in the build folder. You might find it interesting to see how it is implemented.

Related

Kotlin - Generic Binding for Function

I had a function that used a view that could handle two different layouts because the two layouts have the same resources names. I am updating the code to use databindings, but I would like to keep using the generic function that is able to handle both layouts rather than splitting it into two functions for the two different bindings. I originally thought that I could do so using DataBindingUtil like so:
fun LoadChatMessage(context: Context, itemView: View, itemID: Int, item: Chat) {
val itemBinding = DataBindingUtil.inflate(LayoutInflater.from(context), itemID, itemView as ViewGroup, false)
}
In this scenario, itemID is the layoutId of the two layouts, either R.layout.chat_1 or R.layout.chat_2 (example names). However, I can't use this because it is considered to be not enough information to inflate DataBindingUtil. I tried hardcoding the layoutIds instead, and that was not the issue. The only way to fix the error message was by declaring itemBinding as an ItemChat1Binding or an ItemChat2Binding, but this is the very issue I was trying to avoid because I won't know which databinding to use until the function is called.
Is there any way to keep this generic format so I can plug in the corresponding layout to the function since the layouts use the same resource names?
Short answer: I recommend writing the code for both individually, not using databinding for this, or taking a look and seeing if it really makes sense to have your layouts this way.
Long answer:
Without more details and trying to recreate the situation on my own, I'm going to say that what you want to do is either not possible or incredibly unconventional. Data/View binding generates classes at build time based off of your xml that include fields/properties that correspond to the various xml properties. This means each one is uniquely tied to a specific xml and won't know anything about other xml files.
There might be some hope with what is known as a generic/template. Rather than writing the same code multiple times the only difference being the type, you can write the code once but with a sort of 'placeholder' type, often T.
For example
fun <T: ViewDataBinding> LoadChatMessage(context: Context, itemView: View, itemID: Int, item: Chat) {
val itemBinding: T = DataBindingUtil.inflate(LayoutInflater.from(context), itemID, itemView as ViewGroup, false)
}
However, I don't think this will allow you to do what it sounds like you want to do. This is because the closest that the code can get to knowing what type T is, is that it is a ViewDataBinding. It will not have any access to the properties that ItemChat1Binding or ItemChat2Binding will have.
The only thing I can think of that would sort of work is a massive work around requiring you to write a wrapper for a ViewDataBinding, but I recommend against that. I am questioning why you want to have 2 different layouts/views with the same resource names rather than one layout/view with a high level of flexibility.

Why does the R class not contain the field type?

Whenever we want to inflate a view or get a resource we have to cast it in run-time. views, for example, are used like so:
In the past, we would have needed to cast it locally
(RelativeLayout) findViewById(R.id.my_relative_layout_view)
Now, we use generics
findViewById<RelativeLayout>(R.id.my_relative_layout_view)
my question is why doesn't the compiler(or whoever generates the R class) doesn't also keep some kind of a reference to the type of the element(doesn't matter if it's a string or an int or any other type) that way casting problems should not occur
We cannot really speculate on that, that would be a design choice.
It might be that they wanted to avoid bloating the APK. Every ID would need a full package name to the class. So would each ID in android.R too. Since R is packaged in every APK.
Solutions
However, if you are using Kotlin, you can even do away with the generics check. Kotlin will determine it automatically.
val view = findViewById(R.id.my_relative_layout_view)
view.method()
Or event simpler, if you use synthetics:
my_relative_layout_view.method()
Also, if you are using data bindings, you can just access it like this:
binding.my_relative_layout_view.method()

Null Android data-binding

While studying android data-binding, my colleague told me that the android data-binding can be null in few cases, also when one layout includes another layout with data-binding, the generated data-binding file annotate the binding of another layout as #Nullable. My question is can data-binding be null and if yes when?
Data binding is a blueprint.
A class that is created at compile time when it sees the "layout" tag.
The blueprint class will be named LayoutNameBinding Pascal Case.
Just like any other class, it is non-existent until you reserve memory for it and new it up.
So when you use the data binding utility on the onCreate it creates the class and you can store that in a local variable for using later.
example:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.activity = this
binding.iBindingRecyclerView = this
binding.navHeader?.activity = this
setupNavigationDrawer()
}
"layout root xml files that have other layout root xml files nested in them or named include layouts" will be added as classes inside the parent NameOfLayoutBinding class. These will not be null as they are auto generated at compile time so when you new up the parent, the children will exist.
So a databinding will not be null if you are referring to the auto generated class and if you are newing it up in your onCreate method appropriately.
Now a failed binding event due to a null object can happen if you didn't pass in the variable that you are binding to, but that is not the question you asked.
Hope that helps, if you meant something different, please digress.
Finally, after so many years found an explaination for this. In case you are manually executing the DataBinding.inflate function then the returned DataBinding won't be null.
For auto generated bindings (i.e. when you include a layout and binding is generated and assigned to a variable in the parent layout binding), the data binding can be null based on some situation.
E.g.: Let's say we have layout as follow:
Portrait Mode layout: /src/res/layout/activity.xml
<LinearLayout ...>
<include
android:id="#+id/main_content"
layout="#layout/main_content_layout"
/>
</LinearLayout>
And for Lanscape mode: /src/res/layout-land/activity.xml
<LinearLayout ...>
<include
android:id="#+id/sidebar"
layout="#layout/sidebar_layout"
/>
<include
android:id="#+id/main_content"
layout="#layout/main_content_layout"
/>
</LinearLayout>
Now here as the multiple layout files are for same purpose but with different configuration (lanscape mode and portrait mode) the Android DataBinding will generate ActivityBinding.java file. Now here the developer would need to access binding for both sidebar and main content using object of ActivityBinding.java class. As the sidebar is not present in portrait mode layout file, the binding file won't have any reference. Hence the reference for sidebar binding would be kept as Nullable.
Hence, for the layout files with same name for different configuration and with different view hierarchy, the inner binding object generated can have null value, due to which the data binding may have Nullable binding fields.

Using same Android binding class from 2 different layouts

I am trying to display data from the same class in two different layouts using Android's data binding. The layouts are used to inflate the views in a ListView. I already have it functioning for one, and I was hoping to use the same adapter class since it's easy enough to specify which layout resource to use.
The problem arises in the automatically generated databinding classes; since there are two layout files, it generates two of them, say, LayoutOneBinding and LayoutTwoBinding, and when I use
DataBindingUtil.bind(inflatedView)
I get one of the two, and they have no common superclass that I can assign the result to and still be able to use the contained data. So, is there any way to reuse the data binding class across two different layouts?
Each layout file has a separate <variable>, but it is named the same and contains the same type of data.
There is a way to reuse binding in case you have same variable names in both bindings. Every data binding extends ViewDataBinding. So, you have a super class which you can accept.
Here, you cannot directly set the variable like dataBinding.variable1 = someValue. But, there is an alternate way i.e. use of #setVariable function.
So in your adapter, your code would be something like as follow:
dataBinding.setVariable(BR.variable1, someValue)
Ref: https://www.vogella.com/tutorials/AndroidDatabinding/article.html#implement-the-recyclerview-with-data-binding

Android: How to best pass data to a view?

I have a view that displays all the levels of my game. These levels are read by the activity and then passed into the view. I could read them from the view, but it's not really its responsibility, and I'm a fan of separation of concerns.
Right now, I'm calling a setter for this:
((GameView) findViewById(R.id.game)).setLevels(loadLevels());
However, I don't like the fact that the view will be dysfunctional if I forget to call the setter. Is there a better way to pass the levels in?
It is also a bit a matter of preference. Theoretically it's perfectly fine to pass the levels as you're doing. Alternatively, if you need more than just set the levels, but provide further functionalities (i.e. also saving of levels) I normally use a separate class responsible for handling such things (i.e. a Repository, some "Manager" class etc...). This class is then passed into the View on the constructor preferably s.t. one is forced to provide it. Of course, in order to separate things, I use interfaces rather than specific implementations s.t. it may then look as follows:
public class MyView {
public MyView(ILevelLoader levelLoader){
this.levelLoader = levelLoader;
}
...
}
Often, this may not work, because the view is something instantiated by the framework directly rather than by the application. In such a situation you're forced to do it through an appropriate setter. It is some sort of MVC/MVP pattern.
Just for your interest, you might also want to take a look at IoC containers and dependency injection. Guice provided by Google is a nice framework I've already used on Android.
I hope I didn't miss the point, but here goes:
Generally you have either a function setting something (like the text for a textview), or an attribute you set in the xml.
Take a look over at this answer I got on a question: How to layout a 'grid' of images in the center of the screen
There are some things the custom view needs, but lets take an example: 'numColumns'.
you can set it using setNumColumns (that would be the equivalent of your loadLevels() ? )
you can ignore it, it'll revert to default.
you can set it as an attribute lik so: app:numColumns="3"
You can try to use the attribute or the default in the class to accomplish this.
Make your view an abstract class with an abstract method getLevels()? This way, when you instantiate the class if you forget to pass the levels in your code won't compile.
Whether or not this is better is a matter of taste I guess :)

Categories

Resources