Using MvxCommand With CommandParameter binding - mvvm

I'm trying using fire MvxCommand with CommandParameter, but faced with following problem:
MyView.axml contains:
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button1"
local:MvxBind="Click MyCommand, CommandParameter=foo" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button2"
local:MvxBind="Click MyCommand, CommandParameter=bar" />
</LinearLayout>
MyViewModel.cs:
public class MyViewModel : MvxViewModel
{
public ICommand MyCommand { get; private set; }
public MyViewModel()
{ // param is null
MyCommand = new MvxCommand<string>(param =>
{
if (param == "foo")
{
// do something
}
else if (param == "bar")
{
// do something else
}
});
}
}
But when I check param variable is null.
What I'm doing wrong?

Your code is working for me on the Head of my source tree.
But this functionality is only two weeks old.
My guess is that this feature either didn't make it into the release that you are working with or there was a bug with it.
Can you check your debug trace for this binding? Is there any information there?
If the trace suggests that CommandParameter is an unknown symbol then my guess is that you will need to either build the latest source yourself - or to wait for a new release.
If the trace suggests something else then you may be able to patch the problem during setup.
One thing I know we did fix was a value converter issue where the Cirrious.MvvmCross.Binding.dll based ValueConverter's weren't being just by overriding Setup.ValueConverterAssemblies to register the ValueConverter required for this CommandParameter

I was doing CommandParameter coding today and you need to do couple of fixes. The axml code should contain CommandParameter='yourParameter' it looks like this:
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button1"
local:MvxBind="Click MyCommand, CommandParameter='foo'" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button2"
local:MvxBind="Click MyCommand, CommandParameter='bar'" />
Even if you want to catch an Integer you still need to pass this in single quotation marks as this: CommandParameter='1234'
In C# code the most important thing is remove MvxCommand from constructor. This should be treated as Property.
public class MyViewModel : MvxViewModel
{
public MyViewModel() { }
public MvxCommand<string> MyCommand
{
get
{
return new MvxCommand<string>(param =>
{
if (param == "foo")
{
// do something
}
else if (param == "bar")
{
// do something else
}
});
}
}
}
This was done in MvvmCross6. It should work fine with previous versions.

Related

MVVM - MutableLiveData of Custom Model not been updating into ViewModel with databinding and always is null

Summary:
When I try to send to the repository a custom Model, the MutableLiveData is null. I think it is because of the observer of the MutableLiveData.
Please, read to the end.
ViewModel
class LoginViewModel #Inject constructor(
private val repository: MyRepository) : ViewModel() {
var loginModel: MutableLiveData<LoginModel>
init {
loginModel = MutableLiveData<LoginModel>()
}
fun loadUser(): LiveData<Response<Custom<Token>>> {
return repository.login(loginModel.value!!)
}
}
As you can see here I have a MutableLiveData
My LoginModel is something like this
data class LoginModel(var user : String, var password : String)
A fragment was defined using databinding and binding with the ViewModel above.
login_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.app.example.view.BaseActivity">
<data>
<variable
name="viewModel"
type="com.app.example.view.login.LoginViewModel" />
</data>
<android.support.constraint.ConstraintLayout
android:id="#+id/activityMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/bg_login">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="80dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="80dp"
app:cardCornerRadius="7dp"
app:cardElevation="22dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|top"
android:layout_marginTop="60dp"
android:text="#string/bienvenido_text"
android:textAllCaps="true"
android:textSize="20sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="20dp"
android:orientation="vertical">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="#+id/etEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:cursorVisible="true"
android:text="#{viewModel.loginModel.user}"
android:gravity="center|start|bottom"
android:hint="#string/email_text"
android:inputType="textEmailAddress"
android:maxLength="50"
android:paddingBottom="10dp"
android:textSize="18sp" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:passwordToggleEnabled="true">
<android.support.design.widget.TextInputEditText
android:id="#+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="30dp"
android:cursorVisible="true"
android:gravity="center|start|bottom"
android:hint="#string/contrasenia_text"
android:text="#{viewModel.loginModel.password}"
android:inputType="textPassword"
android:maxLength="50"
android:paddingBottom="10dp"
android:textSize="18sp" />
</android.support.design.widget.TextInputLayout>
<Button
android:id="#+id/btnServerLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="15dp"
android:padding="10dp"
android:text="#string/ingresar_text"
android:textSize="18sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:layout_marginBottom="40dp"
android:orientation="horizontal">
</LinearLayout>
</android.support.v7.widget.CardView>
</android.support.constraint.ConstraintLayout>
</layout>
Using Databinding, I've implemented the relationship between etEmail and the property user of the Model UserModel and also I've implemented the relationship between etPassword and the property password
when the user click on btnServerLogin, the LoginFragment will execute the following code
binding.btnServerLogin.setOnClickListener {
loginViewModel!!.loadUser().observe(this, Observer<Response<Custom<Token>>> { this.handleResponse(it) })
}
And here is the problem.
kotlin.KotlinNullPointerException
There reason is because of loginModel.value!! is null
fun loadUser(): LiveData<Response<Custom<Token>>> {
return repository.login(loginModel.value!!)
}
The MutableLiveData's value is always null. I though It would change at the same time you were typing in your EditText of email or passoword.
Here is the code of the LoginFragment OnActivityCreated in which I've initialized the ViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
DeviceUtils.setTranslucentStatusBar(activity!!.window, R.color.colorPrimaryDark)
loginViewModel = ViewModelProviders.of(this, viewModelFactory)
.get(LoginViewModel::class.java)
binding.setLifecycleOwner(this)
binding.viewModel = loginViewModel
binding.btnServerLogin.setOnClickListener {
mPostsViewModel!!.loadUser().observe(this, Observer<Response<RespuestaModel<JwtTokenModel>>> { this.handleResponse(it) })
}
return
}
What am I doing wrong? Shall I need to add an specific Observer?
Thanks
Thanks to #communistWatermelon, I was able to resolve a piece of the problem but it wasn't enough. Here is the complete solution:
On one hand we have to initialize the value property of the MutableLiveData as #communistWatermelon said so:
var loginModel: MutableLiveData<LoginModel> = MutableLiveData()
init {
loginModel.value = LoginModel()
}
On the other hand, we need to set properly the binding in the layout
In the case of #{variable.field} the binder will generate a piece of code to get the value by calling variable.getField(). Note that the binder implicitly prefix the “get” word if it is not provided. So #{variable.getField()} is an explicit valid option.
In the case of #={variable.field} the binder will create the same code as before, but with additional code for getting the value from the View and setting it back to your object
After reading that, we need to change the way of the binding
android:text="#{viewModel.loginModel.password}"
android:text="#{viewModel.loginModel.user}"
to
android:text="#={viewModel.loginModel.password}"
android:text="#={viewModel.loginModel.user}"
EDIT
For those who didn't resolve using the above explanation, try registering the lifecycleOwner in your fragment with the following line:
binding.setLifecycleOwner(this)
Good coding!
This has happened to me as well, except what had happened is I used MutableLiveData on my ViewModel without registering my fragment/activity as LifeCycleOwner to the ViewDataBinding.
The Question would not experience this issue because
binding.setLifecycleOwner(this)
This may not be relevant to the question, but if anyone else lands here like I did, double check that you do the setLifecycleOwner on your binding.
MutableLiveData<LoginModel>() doesn't initialize the value property of the MutableLiveData object. You'll likely need to set a value in the init call, like this:
init {
loginModel = MutableLiveData<LoginModel>()
loginModel.value = LoginModel()
}
Because you're not setting a value, doing so would cause the `NullPointerException at
return repository.login(loginModel.value!!)

use parameters in conditional command in zk framework

I'm triying to use a conditional #command with an argument passed with Executions.createComponents, this is my java code:
Map data = new HashMap();
data.put("isFromHere", true);
modal = (Window) Executions.createComponents("root/to/window", null, data);
modal.doModal();
And in my zul page I'm trying to do this:
<button label="Save" onClick="#command(arg.isFromHere ? 'save' : 'not')" />
But every time arg.isFromHere is returning false, like the argument is not passed. But if I do this:
<button if="${arg.isFromHere}" label="Save" onClick="#command('save'" />
That does work fine! What means the arguments are getting to the zul page, but not working on conditional commands, Is there a way to make it work?
It has all to do with the lifecycle of the binder.
#Command and ${arg.xxx} are on different lifecycle.
Read this documentation and see your solution in it.
Solution for you :
VM :
private boolean fromHere;
#Init
public void init(#ExecutionArgParam("isFromHere") boolean fromHere){
this.fromHere = fromHere;
}
public boolean isFromHere() {
return fromHere;
}
zul :
<button label="Save" onClick="#command(vm.fromHere ? 'save' : 'not')" />

MvvmCross passing configuration to configurable plugin loader

I am creating a portable MockGeoLocationWatcher that one can substitute in place of the concrete implementations of IMvxGeoLocationWatcher until one has an actual device. This should facilitate development and testing of applications that require geo location.
The PluginLoader class for this plugin currently looks like this:
namespace Pidac.MvvmCross.Plugins.Location
{
public class PluginLoader : IMvxConfigurablePluginLoader
{
private bool _loaded;
public static readonly PluginLoader Instance = new PluginLoader();
public void EnsureLoaded()
{
if (_loaded)
return;
_loaded = true;
var locationWatcher = new MockGeoLocationWatcher();
var data = #"<?xml version='1.0' encoding='utf-8'?>
<WindowsPhoneEmulator xmlns='http://schemas.microsoft.com/WindowsPhoneEmulator/2009/08/SensorData'>
<SensorData>
<Header version='1' />
<GpsData latitude='48.619934106826' longitude='-84.5247359841114' />
<GpsData latitude='48.6852544862377' longitude='-83.9864059059864' />
<GpsData latitude='48.8445703681025' longitude='-83.7337203591114' />
<GpsData latitude='48.8662561090809' longitude='-83.2393355934864' />
<GpsData latitude='49.0825970371386' longitude='-83.0415816872364' />
<GpsData latitude='49.2621642999055' longitude='-82.7229781716114' />
<GpsData latitude='49.2621642999055' longitude='-82.6021285622364' />
<GpsData latitude='49.2047736379815' longitude='-82.3054977028614' />
</SensorData>
</WindowsPhoneEmulator>";
locationWatcher.SensorLocationData = data;
Mvx.RegisterSingleton(typeof(IMvxGeoLocationWatcher), locationWatcher);
}
public void Configure(IMvxPluginConfiguration configuration)
{
}
}
public class MockLocationWatcherConfiguration : IMvxPluginConfiguration
{
public static readonly MockLocationWatcherConfiguration Default = new MockLocationWatcherConfiguration();
// ideally, we should use this property to point to a file or string containing location data
// this should be configurable outside of code base.
public string SensorLocationData { get; set; }
}
}
I will like to pass the sensor data, currently hardcoded into the variable called "data" through an instance of MockLocationWatcherConfiguration but do not know where the MvvmCross framework is expecting to load the configuration for this plugin before IMvxConfigurablePluginLoader.Configure(configuration) is invoked. Ideally, I should specify this through configuration.
I looked at the Json plugin's implementation of PluginLoaded but still could not figure out where the configuration was retrieved before a cast was attempted in IMvxConfigurablePluginLoader.Configure.
Any ideas or pointers will be greatly appreciated.
TIA.
This is covered in the draft wiki page https://github.com/slodge/MvvmCross/wiki/MvvmCross-plugins - see "writing a configurable plugin"

<h:form> within <ui:repeat> not entirely working, only the last <h:form> is processed

I will like to edit a list of items in the same page. Each item should be edited using a separate form. I am creating a h:form within ui:repeat. Only when the last form is submitted, the user input is applied to the managed bean. For all other forms, user input is not applied to the model.
#ManagedBean
public class Controller {
Logger logger = Logger.getLogger("TestWeb");
private List<Customer> customerList;
public List<Customer> getCustomerList() {
if (customerList == null) {
customerList = new ArrayList<Customer>();
customerList.add(new Customer("Daffy Duck", "daffy#example.com"));
customerList.add(new Customer("Bugs Bunny", "bugs#example.com"));
customerList.add(new Customer("Samity Sam", "sam#example.com"));
}
return customerList;
}
public String updateCustomer(Customer c) {
logger.info("Updating: " + c.getName());
return null;
}
}
In the view, I have
<ui:repeat var="c" value="#{controller.customerList}">
<h:form>
<h3>Edit Customer</h3>
Name: <h:inputText value="#{c.name}"/><br/>
E-mail: <h:inputText value="#{c.email}"/><br/>
<h:commandButton value="Update"
action="#{controller.updateCustomer(c)}"/>
</h:form>
</ui:repeat>
I search for hours without any solution. What will be the correct way to do this? I can hack it by using a single form and using a ui:repeat within it. But there are many issues with that and I will rather not take that route. Thanks.
This is a bug in state saving of <ui:repeat> in Mojarra. There are several similar issue reports at http://java.net/jira/browse/JAVASERVERFACES, among others issue 2243.
You have basically 2 options: use another iterating component (e.g. <c:forEach>, <h:dataTable>, <t:dataList>, <p:dataList>, etc), or replace Mojarra by MyFaces (the <ui:repeat> in this construct works properly in there).

Creating a custom WHILE loop in NANT

I have tried my best to create a custom while loop but ended in vain.
Has anyone been successful in creating a custom while loop in NANT?
You can create a custom task :
<target name="sample">
<property name="foo.value" value="0"/>
<while property="foo.value" equals="0">
<do>
<echo message="${foo.value}"/>
<property name="foo.value" value="${int::parse(foo.value) + 1}"/>
</do>
</while>
</target>
<script language="C#" prefix="directory">
<code>
<![CDATA[
[TaskName("while")]
public class WhileTask : TaskContainer
{
private TaskContainer _doStuff;
private string _propertyName;
private string _equals;
private string _notEquals;
[BuildElement("do")]
public TaskContainer StuffToDo
{
get
{
return this._doStuff;
}
set
{
this._doStuff = value;
}
}
[TaskAttribute("property")]
public string PropertyName
{
get
{
return this._propertyName;
}
set
{
this._propertyName = value;
}
}
[TaskAttribute("equals")]
public string Equals
{
get
{
return this._equals;
}
set
{
this._equals = value;
}
}
[TaskAttribute("notequals")]
public string NotEquals
{
get
{
return this._notEquals;
}
set
{
this._notEquals = value;
}
}
protected override void ExecuteTask()
{
while (this.IsTrue())
{
this._doStuff.Execute();
}
}
private bool IsTrue()
{
if (!string.IsNullOrEmpty(this.Equals))
{
return this.Properties[this.PropertyName] == this.Equals;
}
return this.Properties[this.PropertyName] != this.NotEquals;
}
}
]]>
</code>
</script>
looking at the list of currently available tasks for NAnt, it looks like while is no longer supported (http://nant.sourceforge.net/release/latest/help/tasks/)
So I think the easiest and most efficient way how to do a custom while loop is recursion.
So for example, something like this:
<property name="count" value="120" />
<target name="wait">
<if test="${int::parse(count) > 0}" >
<property name="count" value="${int::parse(count) - 1}" />
<call target="wait"/>
</if>
</target>
Regards,
Marek
Here's another example of a simple yet effective version of the while loop implemented in NAnt.
<?xml version="1.0"?>
<project name="whiletask" xmlns="http://tempuri.org/nant-donotuse.xsd">
<script language="C#" prefix="loop">
<code>
<![CDATA[
/// <summary>
/// A while loop task. Will continuelly execute the task while the <c>test</c> is <c>empty</c>
/// or evalutes to <c>true</c>.
/// </summary>
[TaskName("while")]
public class WhileTask : TaskContainer
{
private string _test;
private TaskContainer _childTasks;
/// <summary>
/// The expression to test each iteration. If empty, then always evalutes to true (i.e. infinite loop.)
/// </summary>
[TaskAttribute("test", ExpandProperties = false)]
public string Test
{
get { return _test; }
set { _test = NAnt.Core.Util.StringUtils.ConvertEmptyToNull(value); }
}
/// <summary>
/// Superficial to ensure the XML schema is rendered correctly for this task. It will get executed
/// if tasks exist within it.
/// </summary>
[BuildElement("do")]
public TaskContainer ChildTasks
{
get { return _childTasks; }
set { _childTasks = value; }
}
/// <summary>
/// Executes the while loop while the <c>test</c> evalutes to true or <c>test</c> is empty.
/// </summary>
protected override void ExecuteTask()
{
while (this.Test == null
|| bool.Parse(Project.ExpandProperties(this.Test, this.Location)))
{
if (this._childTasks != null)
{
this._childTasks.Execute();
}
else
{
base.ExecuteTask();
}
}
}
}
]]>
</code>
</script>
<property name="i" value="0" />
<while test="${int::parse(i) <= 10}">
<echo message="${i}" />
<property name="i" value="${int::parse(i)+1}" />
</while>
</project>
There's quite a few ways you can do this. I wrote something similar to Cao which triggers off a property being true, so the condition can be as complex as you like and if it's made dynamic the value is evaluated each loop which is handy when you are calling functions e.g. to check a file exists. I also added simple break and continue controls. It can also be run as an infinite loop with no attributes which can be useful either when you want lots of conditions to exit (in which case use 'if' with break/continue or - in my case - I wanted to run a task until it exceptioned and then handle is with failonerror or a trycatch block.
Here's a bit of Nant script which shows two ways to countdown from 10:
<property name="greaterthanzero" value="${int::parse(count) > 0}" dynamic="true"/>
<property name="count" value="10" />
<while propertytrue="greaterthanzero" >
<echo>CountDown = ${count}</echo>
<property name="count" value="${int::parse(count) - 1}" />
</while>
<property name="count" value="10" />
<while>
<if test="${int::parse(count) > 0}" >
<echo>CountDown = ${count}</echo>
<property name="count" value="${int::parse(count) - 1}" />
<continue/>
</if>
<break/>
</while>
And here's a real world example I use to wait until a lockfile is deleted:
<property name="count" value="0" />
<property name="lockfileexists" value="${file::exists(lockfile)}" dynamic="true"/>
<while propertytrue="lockfileexists" >
<sleep seconds="1" />
<property name="count" value="${int::parse(count) + 1}" />
<if test="${count == '15'}" >
<echo>Timed out after 15 seconds</echo>
<break/>
</if>
</while>
Here's the task code:
<script language="C#" prefix="loops">
<code>
<![CDATA[
public class LoopBreakException : Exception {}
public class LoopContinueException : Exception {}
[TaskName("break")]
public class BreakTask : Task
{
protected override void ExecuteTask()
{
throw new LoopBreakException();
}
}
[TaskName("continue")]
public class ContinueTask : Task
{
protected override void ExecuteTask()
{
throw new LoopContinueException();
}
}
[TaskName("while")]
public class WhileTask : TaskContainer
{
[TaskAttribute("propertytrue")]
public string PropertyName { get; set; }
protected bool CheckCondition()
{
if (!string.IsNullOrEmpty(PropertyName))
{
try
{
return bool.Parse(Properties[PropertyName]);
}
catch (Exception ex)
{
throw new BuildException(string.Format("While Property '{0}' not found", PropertyName), Location);
}
}
//for infinite loops
return true;
}
protected override void ExecuteTask()
{
while (CheckCondition())
{
try
{
ExecuteChildTasks();
}
catch (LoopContinueException)
{
continue;
}
catch (LoopBreakException)
{
break;
}
}
}
}
]]>
</code>
Without additional information, there is a tutorial on creating a custom NAnt task here.
One nice thing about the article is the author suggests 2 means of debugging your custom task:
Copy the assembly (and pdb) file to the NAnt bin directory. Open your solution in Visual Studio that contains the source for your task. Place your breakpoints. Go to the project properties and open the Debugging page. Change the Debug Mode to Program and the Start Application to the path to the NAnt executable (e.g. C:\Program Files\NAnt\bin\NAnt.exe). Then set the working directory and/or command line arguments so that NAnt will pick up your build file. Click run and away you go.
Place System.Diagnostics.Debbugger.Break(); in your code before the line you want to break on. Re-compile the project and copy the assembly (and pdb) to the NAnt bin directory. When you run your NAnt script you should get a popup box asking you to choose a debugger.
There is another tutorial here.
Alternately, can you express your problem in terms of a foreach?
I have created the custom task by myself. But it seems there are some issues in using nested loops in NANT.
Basically I'm trying to use nested loop. A while loop inside a foreach or a foreach inside another foreach. But in both instances the loop executes the current target & the target from which the current target is called for every iteration instead of the body inside the second loop.
Regards
Sarathy
Here is one way of writing a WHILE loop in nant, with no custom tasks or script element, taking advantage of failonerror="false" on a foreach loop.
<property name="n" value="10000" /><!-- this would be inefficient if "n" is very large -->
<property name="i" value="0" />
<foreach item="String" in="${string::pad-right(' ', int::parse(n), ',')}" delim="," property="val" failonerror="false" >
<if test="${int::parse(i) > 3}"><!-- put our exit condition here -->
<fail message="condition met, exit loop early" />
</if>
<echo message="i: ${i}" />
<property name="i" value="${int::parse(i) + 1}" />
</foreach>
The output from executing the WHILE loop above is as follows. Note that, due to the failonerror="false" the fail call does not terminate the script:
[echo] i: 0
[echo] i: 1
[echo] i: 2
[echo] i: 3
[foreach] myscript.nant(24,18):
[foreach] condition met, exit loop early
BUILD SUCCEEDED - 1 non-fatal error(s), 0 warning(s)
I based the WHILE loop above on how I build a FOR loop, which is a slightly simplified version of the above code:
<property name="n" value="5" />
<property name="i" value="0" />
<foreach item="String" in="${string::pad-right(' ', int::parse(n), ',')}" delim="," property="val" >
<echo message="i: ${i}" />
<property name="i" value="${int::parse(i) + 1}" /> <!-- increment "i" -->
</foreach>
The output from the FOR loop looks like this:
[echo] i: 0
[echo] i: 1
[echo] i: 2
[echo] i: 3
[echo] i: 4
BUILD SUCCEEDED