Tornike Gomareli
Tornike Gomareli

Reputation: 1709

Cant Bind UIButton and UITextField in ViewModel with Xamarin-iOS and ReactiveUI

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

Answers (2)

Adri&#225;n Romero
Adri&#225;n Romero

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

Colt Bauman
Colt Bauman

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

Related Questions