Databinding in Android cannot find a setter with java.langString - android-layout

I am doing databinding with a custom component and I am running into difficulties trying to bind a string to value in my xml.
Here is the xml component in the xml. The issue was with the progressText string replace of dynamic variables. I am getting a
Cannot find a setter for <com.mycompany.CircleProgressBar
cpb:progressText> that accepts parameter type 'java.lang.String'
error when trying to compile the xml.
<com.mycompany.CircleProgressBar
xmlns:cpb="http://schemas.android.com/apk/res-auto"
android:id="#+id/progressWheel"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerInParent="true"
android:layout_margin="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
cpb:progress="#{viewmodel.progress}"
cpb:progressColor="#color/colorPrimary"
cpb:backgroundColor="#color/gray"
cpb:backgroundWidth="10"
cpb:textSize="10sp"
cpb:differenceInTextSizeInLines="10"
cpb:roundedCorners="true"
cpb:progressText="#{#string/work_package_percent_complete(viewmodel.progress)}"
cpb:spaceBetweenLines="5"
cpb:maxValue="100"
cpb:progressTextColor="#color/colorPrimary" />
Here is how I declare the variable used in my CircleProgressBar:
#BindingAdapter("progressText")
fun setTextView(view: CircleProgressBar, value: String) {
if (this::ta.isInitialized) {
this.text = ta.getString(R.styleable.CircleProgressBar_progressText) as String
} else {
this.text = value
}
view.invalidate()
}
When I try to do an import statement for String at the top of my xml file, I am getting a duplicate that String is is already declared. If I just put in straight text without the string replacement, everything works fine.

It seems BindingAdapter is often abused, because it's overkill for simple bindings. The idea is to have these generated, not to program them - and a String getter/setter with the expected name is enough for that. In order to expose the expected methods, these methods just need to be called:
setProgressText(value: String)
getProgressText()
When the CircleProgressBar is a field, it doesn't have to be passed into the method, preserving the expected method signature, about which's absence it explicitly complains. It doesn't even have to be a field in the view-model, because the model should not depend on views.

Related

How to bind a viewmodel method in layout and pass a dynamic value to the viewmodel method

How to bind a ViewModel method in layout and pass a dynamic value to the ViewModel method
ViewModel
suspend fun get_Organizations(
username: String
) = withContext(Dispatchers.IO) { repository.get_organizations(username) }
Layout
<data>
<variable name="viewmodel"
type="com.example.hopelastrestart.ui.home.organization.OrganizationViewModel"
/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_gravity="center_horizontal"
tools:text=""
android:text="#{(viewmodel.user.name)-> viewmodel.get_Organizations()"
android:textColor="#color/colorPrimaryDark"
android:textAppearance="#style/TextAppearance.AppCompat.Headline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</layout>
Because the username serves as the parameter, I think you're looking for:
android:text="#{()-> viewmodel.get_Organizations(viewmodel.user.name)}"
However, this isn't sufficient in itself because get_Organizations() is a suspension function. In general, suspension functions should not be exposed but, instead, managed internally by some coroutine (usually from the viewModelScope). In the case the username is immutable, this could be achieved by a coroutine block during the view model's init. I assume you are already using some sort of dependency injection or view model factory to provide the view model with the appropriate username.
class SomeViewModel(username: String) : ViewModel() {
private val _organizationsText: MutableLiveData<String> = MutableLiveData()
val organizationsText: LiveData<String> = _organizationsText
init {
viewModelScope.launch {
_organizationsText.value = repository.get_Organizations(username)
}
}
}
In this case, the data binding could be achieved without the use of a lambda:
android:text="#{viewModel.organizationsText}"
If the username is mutable, you'd be looking for something a bit more complex. Regardless, I would encourage you to refactor this via a LiveData transformation, a Kotlin Flow, or some other reactive framework. When the view model is already aware of the username, it shouldn't need to be passed into the getOrganizations() function because it's already available. If get_Organizations() must remain public, this could be achieved through a private, overloaded function. I am happy to elaborate given more context. There's quite little I could glean of your scenario based on the question itself.

Calling setter from JSF page which was declared with different parameter from variable itself

I'm using an External lib which has many classes with variables declared like that:
private String testBigDecimal;
public String getTestBigDecimal() {
return this.testBigDecimal;
}
public void setTestBigDecimal(BigDecimal testBigDecimal) {
this.testBigDecimal = BigDecimalValidador.validationXpto( testBigDecimal); //return String
}
Note that setter() method receives a BigDecimal value due to some validation process and return a String value. When those are called from server side everything is OK! ...but when called from view side (JSF page) with proper BigDecimalConverter I'm getting this Error:
javax.faces.component.UpdateModelException: javax.el.PropertyNotWritableException: ... Property [testBigDecimal] not writable on type ....
After Converter is correctly invoked, the code fails because it is still expecting for a setTestBigDecimal(Srting testBigDecimal) metohd.
My JSF Page: <h:inputText value="#{fooBean.testBigDecimal}" converter="bigDecimalConverter" />
Is there a way to ensure JSF can call the declared setter (which was in a "non standard way")?
In a local tests I changed 'String testBigDecimal' to 'BigDecimal testBigDecimal' type and everything works fine, but I can't change the Lib Source and Extend all this classes will be painful.
Anyone can help?

unit-test tooltip visibility and content

Consider the following situation:
I got a title which is either 'A' when the type of an object is 'someType', or is empty apart from that. That is:
title="#{type eq 'someType' ? 'A' : ''}"
If the string returned by type changes, I won't get any errors. To be precise, the tooltip will be visible no longer, whilst I get no warnings on this. The important thing is, that type is not in my responsibility, but instead in another project. I defined an enum to use this in my code, which represents the values that type can receive.
public enum Type {
SOME_TYPE("someType"), //
ANOTHER_TYPE("anotherType");
String descr;
private Types(String descr) {
this.descr= descr;
}
}
Do you have any best practices here?

Passing parameter between XAML pages

I have following requirement, I have 4 pages. First 3 pages prompt user to enter some information and finally on fourth page I do some processing and display the result.
I came up with this approach. I have created a class with all the field user enter in various pages
ref class CameraWiFiInfo sealed
{
public:
property String^ sCameraName;
property String^ sWiFIName;
property String^ sWifiPassword;
CameraWiFiInfo()
{
sCameraName = ref new String;
sWiFIName = ref new String;
sWifiPassword = ref new String;
}
};
I am trying to pass this object as follows
PAGE #1
CameraWiFiInfo^ cameraInfo = ref new CameraWiFiInfo();
cameraInfo->sCameraName = txtCameraName->Text;
this->Frame->Navigate(TypeName(WifiCheck::typeid),cameraInfo);
PAGE#2
void Page2::OnNavigatedTo(NavigationEventArgs^ e)
{
(void) e; // Unused parameter
CameraWiFiInfo^ cameraInfo= e->Parameter ;
}
I am getting error here error C2440: 'initializing': cannot convert from 'Platform::Object ^' to 'CameraWiFiInfo ^'.
I goggled in the net I didn't get any suitable C++/Win Rt XAML example to pass the data from one XAML page to another.If any one tried data passing in C++/Win Rt Please suggest on this.
You need an explicit cast:
CameraWiFiInfo^ cameraInfo= (CameraWiFiInfo^)e->Parameter;
But be careful when doing this, according to the documentation:
To enable serialization of the frame's state using GetNavigationState, you must pass only basic types to this method, such as string, char, numeric, and GUID types. […] In general, we discourage passing a non-basic type as a parameter to Navigate because it can’t be serialized when the application is suspended, and can consume more memory […]

Can I return a string using the #helper syntax in Razor?

I have a RazorHelpers.cshtml file in app_code which looks like:
#using Molecular.AdidasCoach.Library.GlobalConstants
#helper Translate(string key)
{
#GlobalConfigs.GetTranslatedValue(key)
}
However, I have a case where I want to use the result as the link text in an #Html.ActionLink(...). I cannot cast the result to a string.
Is there any way to return plain strings from Razor helpers so that I can use them both in HTML and within an #Html helper?
Razor helpers return HelperResult objects.
You can get the raw HTML by calling ToString().
For more information, see my blog post.
I don't think there is a way to make #helper return other types than HelperResult. But you could use a function with a return type of string, e.g.
#functions {
public static string tr(string key) {
return GlobalConfigs.GetTranslatedValue(key);
}
}
then
#Html.ActionLink(tr("KEY"), "action", "controller")
See also http://www.mikesdotnetting.com/article/173/the-difference-between-helpers-and-functions-in-webmatrix
edit: MVC Razor: Helper result in html.actionlink suggests your helper can return a string by using #Html.Raw(GlobalConfigs.GetTranslatedValue(key));
In your case, I think this would also work:
#(GlobalConfigs.GetTranslatedValue(key))
Additional sample:
#helper GetTooltipContent()
{
if(Model.SubCategoryType == SUBCATTYPE.NUMBER_RANGE)
{
#(string.Format("{0} to {1}", Model.SubCategoryMinimum, Model.SubCategoryMaximum))
}
else if(Model.SubCategoryType == SUBCATTYPE.NUMBER_MAXIMUM)
{
#("<= " + Model.SubCategoryMaximum)
}
else if(Model.SubCategoryType == SUBCATTYPE.NUMBER_MINIMUM)
{
#(">= " + Model.SubCategoryMinimum)
}
}
The following statements have been validated against MVC version 5.2.4.0. I am mostly targeting the part with: Is there any way to return plain strings from Razor helpers so that I can use them both in HTML and within an #Html helper?
I did some research on how the built in MVC helpers work and they are actually properties of System.Web.Mvc.WebViewPage class, so they have nothing to do with #helper feature.
Any #helper encodes strings as HTML and works as if the code is copy pasted to the View inside a Razor code block, aka #{ code }. On the other side, #functions are supposed to be used inside Razor blocks.
Well, if a #helper works as if the code is copy pasted, why not use #Html.Raw("<p>cool</p>")? Because the Html property is null inside helpers. Why? I have no idea.
Ok, but we can use a function to return a string and then apply #Html.Raw on the result. Does that work? Yes, it does. The following example creates a <p> element in the DOM:
#functions
{
static string GetString()
{
return "<p>awesome</p>";
}
}
#Html.Raw(GetString())
If you don't understand why #Html.Raw is necessary, please read this fine article from #SLaks about Razor automatic HTML encoding.
What about the approach with the built in properties? Yes, it is possible to create static classes with public methods that work just like that. The only problem is that you have to include the namespace in the View, with the #using keyword. Can that be improved? Yes, by adding the namespace in the Web.config within the Views folder. Example:
Helpers/Global.cs
namespace WebApp.Helpers
{
public static class Global
{
public static IHtmlString GetString()
{
return new HtmlString("Something <b>cool</b>");
}
}
}
Views/Web.config
<?xml version="1.0"?>
<configuration>
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.4.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<!-- Add to the end of namespaces tag. -->
<add namespace="WebApp.Helpers" />
Usage
#Global.GetString()
What is the outcome? The text Something and an additional <b> element will be found in the DOM. If you need access to the Request, simply add an HttpContextBase parameter to the helper method and pass the WebViewPage.Context property when calling it.
Can it get better? Yes, as always. The same output can be created with #helper and #functions:
#helper GetString1()
{
#(new HtmlString("Something <b>awesome</b>"))
}
#functions {
public static IHtmlString GetString2()
{
return new HtmlString("Something <b>awesome</b>");
}
}
#MyHelper.GetString1()
#MyHelper.GetString2()
Answer
Regarding OP's question, I recommend #Spikolynn's approach to create a function that returns string. However, if you need to write many lines of C# code in the helper, I suggest using a static class helper.

Resources