Kotlin - Generic Binding for Function - android

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.

Related

What's the best way to assertColor() on a Jetpack Compose Component

I have several components that take in a state, and set colors accordingly.
In order to test this, I need a good way to call something like assertColor() on a node.
My initial thought was to add semantics properties, however Google itself warns to avoid doing this if the properties are only to be used for testing, which will be the case with these components:
Warning: You should only use custom Semantics properties when it's
hard to match a specific item, or you need to expose a certain state
that would be hard to check using the given finders and matchers. In
general, this pattern should be avoided if the custom properties are
only used for testing, because they stay in and pollute the production
app.
Further, the captureToImage() function can work, but is unreliable considering similar colors as they share the same ColorSpace.
I've seen posts referencing using a class-based approach, with holding that state logic inside the class, and returning the #Composable Unit from another function. However it is my preference to keep my code fully functional (function-based)
I have thought about making the helper state functions for the color public (currently private), however that involves exposing a function to the rest of the codebase that's really only to be used by this one composable.
It seems like there aren't any good solutions at the moment for testing colors in an easy way that doesn't pollute production code.
Does anyone have any advice, or has found a good balance with their unit tests for these properties?
What I ended up going with was a test util:
fun SemanticsNodeInteractionsProvider.onNodeWithTint(#ColorRes value: Int) = onAllNodes(SemanticsMatcher.expectValue(SemanticsProperties.tint, value)).onFirst()
With a custom SemanticsProperty helper:
object SemanticsProperties {
val tint = SemanticsPropertyKey<Int>("iconTint")
var SemanticsPropertyReceiver.tint by tint
}
fun Modifier.testTint(#ColorRes tint: Int) = semantics { this.tint = tint }
and then in the composables modifier, just setting like this:
.testTint(lineTintRes)
Then, in the test, it can be asserted like this:
composeTestRule.onNodeWithTint(Theme.colors.background).assertIsDisplayed()
It's probably not the best solution as it technically ships a bit of test code within each composable, but that seems to be the only way I've found that works.

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()

Android databinding with multiple layouts

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.

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: dynamic resource values from layout xml

I have an application, in which I need to use diffrent text strings in each view.
I have already a function which returns the correct string according to internal state:
getText(String id)
so getText("menuTitle") might return "Title1" at one time, and "Title2" at another
and getText("buttonX") might return "Press" at one time, and "Click" at another
I have no problem to do this progmatically in each activity
however, as I have a lot of activities, it would be great if I can somehow override the resource mechanism, so instead of writing code in each Activity for each text
View v=findViewbyId(...);
v.setText(setText(stringID));
I could set in the XML
....
<TextView text="myDir/menuTitle" />
and recieve a callback with the resource name so I could return
getText("menuTitle")
instead of reading the resource from the file
You can make one common Baseactivity which contains your common view and just extend this baseactivity in each of your activity class and just set the text over their.
I think, you need a binding mechanism. Take a look at this young project. Or have some fun googling 'android binding' term.

Categories

Resources