MvvmCross Checkbox bind to command android xml - android

Is it possible to bind android checkbox to execute a command on change? Could not find an example

Standard approach would be to simply bind to property of type bool in your viewmodel and perform your logic in setter of this property. Your binding will then look like this:
local:MvxBind="Checked IsChecked"
However if you really need bind to Command, you can also bind to Click event:
local:MvxBind="Checked IsChecked; Click YourCommand;"
ViewModel:
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
RaisePropertyChanged(() => IsChecked);
}
}
public ICommand YourCommand
{
get
{
return new MvxCommand(() =>
{
var isChecked = IsChecked;
//Now you can use isChecked variable
});
}
}
Note that you don't recieve value of the checkbox in your command parameter, so you need to bind to the bool property anyway. Another problem with this solution is that you must rely on a fact, that setter of your property would be called before your command.
If you really need to have command with bool parameter, then you can definitely do that. Awesome thing about MvvmCross framework is that you can always extend its functionality. In your case you would need to implement custom binding for CheckBox. Good starting point may be here: http://slodge.blogspot.cz/2013/06/n28-custom-bindings-n1-days-of-mvvmcross.html
Edit: To show how easy it is I gave it a try and implement simple command binding with bool parameter. (No CanExecute check). In case anyone is interested, here is the code.
Binding class:
public class CheckBoxChangedBinding
: MvxAndroidTargetBinding
{
private ICommand _command;
protected CheckBox View
{
get { return (CheckBox) Target; }
}
public CheckBoxChangedBinding(CheckBox view)
: base(view)
{
view.CheckedChange += CheckBoxOnCheckedChange;
}
private void CheckBoxOnCheckedChange(object sender, CompoundButton.CheckedChangeEventArgs e)
{
if (_command == null)
return;
var checkBoxValue = e.IsChecked;
_command.Execute(checkBoxValue);
}
protected override void SetValueImpl(object target, object value)
{
_command = value as ICommand;
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
public override Type TargetType
{
get { return typeof (ICommand); }
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
var view = View;
if (view != null)
{
view.CheckedChange -= CheckBoxOnCheckedChange;
}
}
base.Dispose(isDisposing);
}
}
In Setup.cs:
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories(registry);
registry.RegisterCustomBindingFactory<CheckBox>("CheckedChanged",
checkBox => new CheckBoxChangedBinding(checkBox));
}
In your layout:
<CheckBox
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
local:MvxBind="CheckedChanged CheckBoxCheckedCommand" />
And finally ViewModel:
public ICommand CheckBoxCheckedCommand
{
get
{
return new MvxCommand<bool>(isChecked =>
{
var parameter = isChecked;
});
}
}

Related

how to make kotlin interface property with default value to be used in java code

Using kotlin 1.6.21, and java 11.
Having a kotlin interface which has property with default value. How to make use it in java code?
interface ISomeKInterface {
val flag: Int
get() = return 1
fun onProc(data: String) {
if (flag == 1) {
// do something_1
} else if (flag == 2) {
// do something_2
} else {
// do the other
}
}
}
in kotlin it could do
object : ISomeKInterface {
override val flag: Int = 2
override fun onProc(data: String) {
if (flag == 1) {
// do something_1
} else if (flag == 2) {
// do different implementation for flag 2
} else {
// do the other
}
}
}
Edit:
in kotlin it works with either override the property or override the getter
fun test_() {
val theInterfaceImpl: ISomeKInterface = object : ISomeKInterface {
//override val flag: Int = 2
// the override the getter does the same as above override flag:Int = 2
override val flag: Int
get() {
val superFlag: Int = super.flag
println("+++ test override get(), flag:$superFlag, ret flag: 2")
return 2
}
// if no change to the default implementation this override is not required.
override fun onProc(data: String) {
println("+++ test override onProc(), flag: $flag default onProc(:$data)")
super.onProc(data)
}
}
val flag = theInterfaceImpl.flag
println("+++ test current flag:$flag")
theInterfaceImpl.onProc("eee888")
}
But it does not compile if trying to use it in java.
In java it requires the #Override public void onProc(String data), but it cannt call the super's default implementation - for the case that the default implementation of the ISomeKInterface is goog enough and does not need different implementation override.
#Test
public void test_() {
ISomeKInterface theInterfaceImpl = new ISomeKInterface() {
#Override
public int getFlag() {
// int superFlag = ISomeKInterface.super.getFlag(); //<== got error on ISomeKInterface.super.
// System.out.println("+++ flag:"+superFlag+", ret flag: "+2);
return 2;
}
#Override
public void onProc(#NonNull String data) {
// it is require the implement the onPro(data: String although it has the default implementation already)
// but what if the super's default onProc() works fine, and does not need provide different implementation??? it seems cannot call the super.onProc here
System.out.println("+++ test override onProc(:"+data);
ISomeKInterface.super.onProc(data); //<===
}
};
int flag = theInterfaceImpl.getFlag();
System.out.println("+++ current flag:"+flag);
theInterfaceImpl.onProc("eee888");
}
error: abstract method onProc(String) in ISomeKInterface cannot be accessed directly
ISomeKInterface.super.onProc(data); //<===
^
I use kotlin interface in java , so easy
public class Test {
public static void main(String[] args) {
ISomeKInterface iSomeKInterface = new ISomeKInterface() {
#Override
public void onProc() {
}
#Override
public int getFlag() {
return 2;
}
};
System.out.println(iSomeKInterface.getFlag());
}
}
Actually, the code override val flag: Int = 2 not change the flag value in ISomeKInterface ,and you can use super.flag get the the value which is 1.
further on,kotlin generate get,set function automatic, so if you want use in java, u need change the getFlag() function:
ISomeKInterface anInterface = new ISomeKInterface() {
#Override
public void onProc(#NonNull Object data) {
}
#Override
public int getFlag() {
return 2;
}
};
Default Method in Java 8:
public interface Alarm {
default String turnAlarmOn() {
return "Turning the alarm on.";
}
default String turnAlarmOff() {
return "Turning the alarm off.";
}
}
Reference: https://www.baeldung.com/java-static-default-methods

How to set up a listener for a variable in Kotlin

How to I set up an interface listener for detecting a variable change in Kotlin. I successful implemented the following in Java, but am running into issues doing it in Kotlin:
Interface:
public interface InterfaceRefreshList
{
public void refreshListRequest();
}
Class containing listener:
public class SignalChange
{
private static boolean refreshListSwitch;
private static List<InterfaceRefreshList> refreshListListeners = new ArrayList<>();
public static void setRefreshList(boolean value)
{
refreshListSwitch = value;
for(InterfaceRefreshList l : refreshListListeners)
l.refreshListRequest();
}
public static void addRefreshListListener(InterfaceRefreshList l)
{
refreshListListeners.add(l);
}
}
Class where listener is listening:
public class FragmentBrowse extends Fragment
{
public FragmentBrowse() /// Constructor
{
SignalChange.addRefreshListListener(() -> refreshList());
}
refreshList()
{
// do something
}
}
To signal a change:
SignalChange.setRefreshList(true);
I can set up the interface and the signal class:
class SignalChange
{
private var refreshListSwitch: Boolean = false
var setSwitch: Boolean
get() = refreshListSwitch
set(value)
{
refreshListSwitch = value
}
private var refreshListListeners = ArrayList<InterfaceRefreshPersonsList>()
fun sendRefreshSignal()
{
for(l in refreshListListeners) l.refreshPersonsList()
}
fun addRefreshListListener(l: InterfaceRefreshPersonsList)
{
refreshListListeners.add(l)
}
}
But I cannot setup the listener in the FragmentBrowse class. The fragment class doesn't allow constructors.
You can use built-in Kotlin delegates, for example:
object SignalChange {
var refreshListListeners = ArrayList<InterfaceRefreshList>()
// fires off every time value of the property changes
var property1: String by Delegates.observable("initial value") { property, oldValue, newValue ->
// do your stuff here
refreshListListeners.forEach {
it.refreshListRequest()
}
}
}
interface InterfaceRefreshList {
fun refreshListRequest()
}
Add listeners like this:
SignalChange.refreshListListeners.add(object : InterfaceRefreshList {
override fun refreshListRequest() {
refreshList()
}
})
OR
Intead of interface you can use lambda:
object SignalChange {
var refreshListListeners = ArrayList<() -> Unit>()
// fires off every time value of the property changes
var property1: String by Delegates.observable("initial value") { property, oldValue, newValue ->
// do your stuff here
refreshListListeners.forEach {
it()
}
}
}
And to add listener just call:
SignalChange.refreshListListeners.add(::refreshList)
//or
SignalChange.refreshListListeners.add { refreshList() }
fun refreshList() {
}
The simple way to Listen to variable in KOTLIN
private var myVariableName by Delegates.observable(0) { property, oldValue, newValue ->
Log.d(TAG,"New Value $newValue")
Log.d(TAG,"Old Value $oldValue")
}
for more info read https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.properties/-delegates/

Android Rxjava subscribe to a variable change

I am learning Observer pattern, I want my observable to keep track of a certain variable when it changes it's value and do some operations, I've done something like :
public class Test extends MyChildActivity {
private int VARIABLE_TO_OBSERVE = 0;
Observable<Integer> mObservable = Observable.just(VARIABLE_TO_OBSERVE);
protected void onCreate() {/*onCreate method*/
super();
setContentView();
method();
changeVariable();
}
public void changeVariable() {
VARIABLE_TO_OBSERVE = 1;
}
public void method() {
mObservable.map(value -> {
if (value == 1) doMethod2();
return String.valueOf(value);
}).subScribe(string -> System.out.println(string));
}
public void doMethod2() {/*Do additional operations*/}
}
But doMethod2() doesn't get called
Nothing is magic in the life : if you update a value, your Observable won't be notified. You have to do it by yourself. For example using a PublishSubject.
public class Test extends MyChildActivity {
private int VARIABLE_TO_OBSERVE = 0;
Subject<Integer> mObservable = PublishSubject.create();
protected void onCreate() {/*onCreate method*/
super();
setContentView();
method();
changeVariable();
}
public void changeVariable() {
VARIABLE_TO_OBSERVE = 1;
// notify the Observable that the value just change
mObservable.onNext(VARIABLE_TO_OBSERVE);
}
public void method() {
mObservable.map(value -> {
if (value == 1) doMethod2();
return String.valueOf(value);
}).subScribe(string -> System.out.println(string));
}
public void doMethod2() {/*Do additional operations*/}
}
If interested here a Kotlin version of Variable class, which lets subscribers to be updated after every variable change.
class Variable<T>(private val defaultValue: T) {
var value: T = defaultValue
set(value) {
field = value
observable.onNext(value)
}
val observable = BehaviorSubject.createDefault(value)
}
Usage:
val greeting = Variable("Hello!")
greeting.observable.subscribe { Log.i("RxKotlin", it) }
greeting.value = "Ciao!"
greeting.value = "Hola!"
This will print:
"Hello!"
"Ciao!"
"Hola!"
#dwursteisen Nothing is magic, no, but I think we can get it a little more magic than that... 😊
How about using an Rx BehaviourSubject in this way:
import rx.functions.Action1;
import rx.subjects.BehaviorSubject;
public class BehaviourSubjectExample {
public BehaviourSubjectExample() {
subject.skip(1).subscribe(new Action1<Integer>() {
#Override
public void call(Integer integer) {
System.out.println("The value changed to " + integer );
}
});
}
public final BehaviorSubject<Integer> subject = BehaviorSubject.create(0);
public int getValue() { return subject.getValue(); }
public void setValue(int value) { subject.onNext(value); }
}
Remove the .skip(1) if you want the observing code to see the initial value.
The variable backing remains with the BehaviourSubject and can be accessed through conventional Java Getter/Setter. This is a toy example of course: If your use case were really this simple there'd be no excuse for not just writing:
private int value = 0;
public int getValue() { return value; }
public void setValue(int value) {
this.value = value;
System.out.println("The value changed to " + value );
}
...but the use of BehaviourSubject lets you bridge changes to other Rx data-streams inside your class for composing more advanced behaviours.

MVVMCross: Binding Two Way doesn't update viewmodel

I'm trying to bind a property in a custom control in a Xamarin.Android project.
public class MyControl : RelativeLayout
{
public ObservableCollection<string> MyProperty { get; set; }
}
When MyProperty is updated in the ViewModel side, it updates fine the MyProperty in the View. However, nothing happens if I update the MyProperty in the View (I'd like to get the updated value in the ViewModel).
The binding:
public class MyControlMyPropertyTargetBinding : MvxAndroidTargetBinding
{
private bool _subscribed;
protected MyControl MyControl
{
get { return (MyControl)Target; }
}
public MyControlMyPropertyTargetBinding(MyControl target)
: base(target)
{
}
protected override void SetValueImpl(object target, object value)
{
var myControl = (MyControl)target;
myControl.MyProperty = (ObservableCollection<string>)value;
}
public override Type TargetType
{
get { return typeof(ObservableCollection<string>); }
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.TwoWay; }
}
public override void SubscribeToEvents()
{
base.SubscribeToEvents();
var myControl = MyControl;
if (myControl == null || myControl.MyProperty == null)
return;
myControl.MyProperty.CollectionChanged += MyPropertyOnCollectionChanged;
_subscribed = true;
}
private void MyPropertyOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
FireValueChanged(MyControl.MyProperty);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (isDisposing)
{
var myControl = MyControl;
if (myControl != null && myControl.MyProperty!= null && _subscribed)
{
myControl.MyProperty.CollectionChanged -= MyPropertyOnCollectionChanged;
_subscribed = false;
}
}
}
}
Setup.cs:
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
registry.RegisterCustomBindingFactory<MyControl>("MyProperty", myProperty => new MyControlMyPropertyTargetBinding(myProperty));
}
protected override IList<Assembly> AndroidViewAssemblies
{
get
{
var assemblies = base.AndroidViewAssemblies;
assemblies.Add(typeof(MyControl).Assembly);
return assemblies;
}
}
Update: The same control binded on Windows Phone works two way perfectly. The control itself is into an external reference.
Does someone know what I missed?
Edit 1: I updated target binding with collection changed subscription but nothing's fired. The ObservableCollection is updated programatically by code, not by user input.

MVVMCross Get SelectedItem from a MvxBindableListView

Little problem with my Android application and I don't know how to solve it with MVVM Cross.
Here is my ViewModel:
public class AddressesShowViewModel : MvxViewModel
{
public List<Address> Addresses { get; set; }
public AddressesShowViewModel(string addressesForListView)
{
Addresses = JsonConvert.DeserializeObject<List<Address>>(addressesForListView);
}
public IMvxCommand ShowItemCommand
{
get
{
//return new MvxRelayCommand<Type>((type) => this.RequestNavigate(type));
return new MvxRelayCommand(DoShowContact);
}
}
private Address selectedItem;
public Address SelectedItem
{
get { return selectedItem; }
set { selectedItem = value; FirePropertyChanged(() => SelectedItem); }
}
private void DoShowContact()
{
RequestNavigate<AddressShowViewModel>();
}
}
My AddressesShow.axml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res/INMobileCRM4Android.INMobileCRM4Android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Mvx.MvxBindableListView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
local:MvxBind="{'ItemsSource':{'Path':'Addresses'},'ItemClick':{'Path':'ShowItemCommand'}, 'SelectedItem':{'Path':'SelectedItem'}}"
local:MvxItemTemplate="#layout/addresslistitem"
/>
</FrameLayout>
I would like to know, how I can get the SelectedItem from the ListView in AddressesShow.axml.. I tried to create a Property 'SelectedItem'.. But its getting called at the beginning, when the ViewModel is created (and is obviously returning null), not when the Item is clicked.. Its btw a type of Address, not just a String or something.. Maybe any suggestions?
The lack of SelectedItem in Droid was identified as an issue last week during preparation for Daniel's talk at Build.
To workaround it, there were a couple of quick answers:
1 There is SelectedItemPosition you can use for binding - this is an int
2 You can use a Click ICommand/IMvxCommand binding instead of using SelectedItem - in your example, this would be the same axml but
public IMvxCommand ShowItemCommand
{
get
{
return new MvxRelayCommand<Address>(address => DoShowContact(address));
}
}
To be clear this Click option above is what I would use.
If SelectedItem really is needed...
Then for a complete answer, Daniel and I prototyped a new binding. This binding was registered using:
registry.RegisterFactory(new MvxCustomBindingFactory<MvxBindableListView>("SelectedItem", adapterView => new MvxAdapterViewSelectedItemTargetBinding(adapterView)));
and contained the logic:
using System;
using Android.Widget;
using Cirrious.MvvmCross.Binding.Droid.Views;
using Cirrious.MvvmCross.Binding.Interfaces;
using Cirrious.MvvmCross.Interfaces.Platform.Diagnostics;
namespace Cirrious.MvvmCross.Binding.Droid.Target
{
#warning This needs to be redone for all adapterviews not just list view!
#warning The use of ItemClick instead of ItemSelected needs to be reinvestigated here!
public class MvxAdapterViewSelectedItemTargetBinding : MvxBaseAndroidTargetBinding
{
private readonly MvxBindableListView _view;
private object _currentValue;
public MvxAdapterViewSelectedItemTargetBinding(MvxBindableListView view)
{
_view = view;
((ListView)_view).ItemClick += OnItemClick;
}
private void OnItemClick(object sender, AdapterView.ItemClickEventArgs itemClickEventArgs)
{
var container = (_view.GetItemAtPosition(itemClickEventArgs.Position) as MvxJavaContainer);
if (container == null)
{
MvxBindingTrace.Trace(MvxTraceLevel.Warning, "Missing MvxJavaContainer in MvxAdapterViewSelectedItemTargetBinding");
return;
}
var newValue = container.Object;
if (!newValue.Equals(_currentValue))
{
_currentValue = newValue;
FireValueChanged(newValue);
}
}
public override void SetValue(object value)
{
#warning Sort out Equals test here
if (value != null && value != _currentValue)
{
var index = _view.Adapter.GetPosition(value);
if (index < 0)
{
MvxBindingTrace.Trace(MvxTraceLevel.Warning, "Value not found for spinner {0}", value.ToString());
return;
}
_currentValue = value;
_view.SetSelection(index);
}
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.TwoWay; }
}
public override Type TargetType
{
get { return typeof(object); }
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
((ListView)_view).ItemClick -= OnItemClick;
}
base.Dispose(isDisposing);
}
}
}
To test this worked, I used the Tutorial PullToRefresh code adapted using:
<Mvx.MvxBindableListView android:id="#android:id/list" android:layout_width="fill_parent"
android:layout_height="fill_parent"
local:MvxBind="{'ItemsSource':{'Path':'Emails'},'ItemClick':{'Path':'ShowItemCommand'},'SelectedItem':{'Path':'TheSelectedEmail'}}"
local:MvxItemTemplate="#layout/listitem_email"
/>
and:
public class SimpleEmail
{
public string From { get; set; }
public string Header { get; set; }
public string Message { get; set; }
}
private ObservableCollection<SimpleEmail> _emails;
public ObservableCollection<SimpleEmail> Emails
{
get { return _emails; }
private set { _emails = value; RaisePropertyChanged(() => Emails); }
}
private SimpleEmail _email;
public SimpleEmail TheSelectedEmail
{
get { return _email; }
set
{
_email = value;
MvxTrace.Trace(MvxTraceLevel.Error, "HELLO {0} ", value == null ? "null" : value.From);
}
}
One thing to be careful about in all this work is that a listview selected item in Android is slightly different to a listbox selected item in Silverlight/wp - e.g. it can be quite hard to get a listview in android to highlight the current selection and it can be quite hard to get the listview to generate selection changed events.
Note: I've logged an issue on Droid SelectedItem to https://github.com/slodge/MvvmCross/issues/52 - I'll make sure the binding is added to the core library in the near future

Categories

Resources