Reputation: 1709
I have LoginViewController class
// This file has been autogenerated from a class added in the UI designer.
using System;
using System.Collections.Generic;
using Foundation;
using UIKit;
using System.Linq;
using System.Reactive;
using ReactiveUI;
using GoBatumi.IOS.ViewModels;
namespace GoBatumi.IOS
{
public partial class LogInViewController : ReactiveViewController,IViewFor<LoginViewModel>
{
UITapGestureRecognizer SingUpGesture;
public LogInViewController (IntPtr handle) : base (handle)
{
this.WhenActivated(d =>
{
d(this.Bind(ViewModel, vm => vm.UserName, vm => vm.userNameTextField.Text));
d(this.Bind(ViewModel, vm => vm.Password, vm => vm.passwordTextField.Text));
d(this.Bind(ViewModel, vm => vm.LoginButton, vm => vm.logInButton));
d(this.Bind(ViewModel, vm => vm.PasswordTextField, vm => vm.passwordTextField));
});
}
private void MakeViewModelBinding(){
}
public override void ViewDidLayoutSubviews(){
base.ViewWillLayoutSubviews();
}
LoginViewModel _viewModel;
public LoginViewModel ViewModel
{
get => _viewModel ?? new LoginViewModel();
set => _viewModel = value;
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (LoginViewModel)value;
}
public override void ViewDidLoad(){
base.ViewDidLoad();
}
private void ShouldChangeViewSettings(bool enable){
passwordTextField.Enabled = enable;
logInButton.Enabled = enable;
if (enable)
logInButton.Alpha = 0.99f;
else
logInButton.Alpha = 0.4f;
}
}
public class TestUser
{
public string UserName{
get;
set;
}
public string Password{
get;
set;
}
}
}
Also, I have LoginViewModel class
using System;
using System.Diagnostics;
using ReactiveUI;
using UIKit;
namespace GoBatumi.IOS.ViewModels
{
public class LoginViewModel : ReactiveObject
{
public LoginViewModel()
{
}
private string _userName;
public string UserName
{
get => _userName;
set
{
this.RaiseAndSetIfChanged(ref _userName, value);
var result = string.IsNullOrEmpty(_userName);
ShouldChangeViewSettings(result);
}
}
private string _password;
public string Password
{
get => _password;
set
{
this.RaiseAndSetIfChanged(ref _password, value);
}
}
private UIButton _loginButton;
public UIButton LoginButton
{
get => _loginButton;
set => this.RaiseAndSetIfChanged(ref _loginButton, value);
}
private UITextField _passwordTextField;
public UITextField PasswordTextField
{
get => _passwordTextField;
set => this.RaiseAndSetIfChanged(ref _passwordTextField,
}
}
}
My problem is that string username and string password can bind with, usernameTextField.Texts and passwordTextField.Text
But it UIButton and UITextField are always null, they haven't bound.
My task is that whenever the user types a character into textField I must enable the button, and whenever a user deletes the wholeTextfield and if the string is empty, I must disable button again, so I need UiButton for changing the background color from ViewModel but UIButtonProperty always returns null.
Where is the problem?
I will be very glad if someone will give me a piece of advice. I'm a little bit new into MVVM and into ReactiveUi.
Thanks.
Upvotes: 0
Views: 435
Reputation: 620
Ok friend if I understand correctly, you can have do something like this:
In your ViewModel:
public ReactiveCommand<Unit,Unit> LoginCommand { get; set; }
public LoginViewModel()
{
//this operator does the magic, when UserName and Password be different than empty your button
//will be enabled
var canLogin = this.WhenAnyValue(x => x.UserName, x=> x.Password,
(user,password) => !string.IsNullOrWhiteSpace(user) && !string.IsNullOrWhiteSpace(password));
LoginCommand = ReactiveCommand.CreateFromTask<Unit, Unit>(async _ =>
{
//Your login logic goes here..
return Unit.Default;
}, canLogin);
}
And in your View
public LogInViewController (IntPtr handle) : base (handle)
{
this.WhenActivated(d =>
{
d(this.Bind(ViewModel, vm => vm.UserName, vm => vm.userNameTextField.Text));
d(this.Bind(ViewModel, vm => vm.Password, vm => vm.passwordTextField.Text));
d(this.BindCommand(this.ViewModel,vm => vm.LoginCommand,v => v.LoginButton));
});
}
your view and your viewmodel interacts via command binding.
I hope this helps you.
Upvotes: 1
Reputation: 662
A few suggestions:
Your view models should never reference anything platform/view related (get rid of the UITextField and UIButton members). View models are meant to be platform independent, so they can be reused and testable.
Use ReactiveCommands. They handle enabling/disabling the button automagically. If you check this documentation link, you'll find essentially the exact same example code/scenario.
Use the generic version of ReactiveViewController so you don't have to worry about implementing the ViewModel properties yourself (your version should have used RaiseAndSetIfChanged, as you'll see in the link).
...
public class LoginViewModel : ReactiveObject
{
public LoginViewModel()
{
var canLogin = this.WhenAnyValue(
x => x.UserName,
x => x.Password,
(userName, password) => !string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password));
LoginCommand = ReactiveCommand.CreateFromObservable(
LoginAsync, // A method that returns IObservable<Unit>
canLogin);
}
public ReactiveCommand<Unit, Unit> LoginCommand { get; }
private string _userName;
public string UserName
{
get { return _userName; }
set { this.RaiseAndSetIfChanged(ref _userName, value); }
}
private string _password;
public string Password
{
get { return _password; }
set { this.RaiseAndSetIfChanged(ref _password, value); }
}
}
...
public partial class LogInViewController : ReactiveViewController<LoginViewModel>
{
UITapGestureRecognizer SingUpGesture;
public LogInViewController (IntPtr handle) : base (handle)
{
this.WhenActivated(d =>
{
d(this.Bind(ViewModel, vm => vm.UserName, v => v.userNameTextField.Text));
d(this.Bind(ViewModel, vm => vm.Password, v => v.passwordTextField.Text));
d(this.BindCommand(ViewModel, vm => vm.LoginCommand, v => v.logInButton));
});
}
}
Here's a heavily documented ViewModel, along with the corresponding sample project to help you get headed in the right direction. And I highly recommend the book, "You, I, and ReactiveUI" if you really want to become proficient. Hope this helps.
Upvotes: 2