Aiden Strydom
Aiden Strydom

Reputation: 1208

MvvmCross: Hybrid Xamarin.Forms and Android Activity Application

Being new to MvvmCross I've decided to create a small Xamarin.Forms app. I have a MainPage.xaml tied to its ViewModel MainViewModel.cs which is displayed first. I have a FirstView.axml located in the droid project along with its activity. The associated ViewModel is located in the Core project along side the MainViewModel and is named FirstViewModel.cs

Upon clicking a navigate button I want MvvmCross to display the FirstView.axml layout and bind to the VM. However,

Whenever the command is invoked I get

03-10 10:11:38.704 D/ViewRootImpl(18964): ViewPostImeInputStage ACTION_DOWN
mvx:Diagnostic: 17,87 Showing ViewModel FirstViewModel
03-10 10:11:38.854 I/mono-stdout(18964): mvx:Diagnostic: 17,87 Showing ViewModel FirstViewModel
[0:] mvx:Diagnostic: 17,87 Showing ViewModel FirstViewModel
mvx:Error: 17,91 Failed to create ContentPage FirstPage
03-10 10:11:38.894 I/mono-stdout(18964): mvx:Error: 17,91 Failed to create ContentPage FirstPage
[0:] mvx:Error: 17,91 Failed to create ContentPage FirstPage
mvx:Error: 17,92 Skipping request for FirstViewModel
03-10 10:11:38.904 I/mono-stdout(18964): mvx:Error: 17,92 Skipping request for FirstViewModel
[0:] mvx:Error: 17,92 Skipping request for FirstViewModel`

Currently the project looks like this:

Startup Activity

[Activity(Label = "Hello MvvmCrossForms", MainLauncher = true)]
public class CrossFormsApp : FormsApplicationActivity
{
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        //Init forms
        Forms.Init(this, bundle);

        InitialiseMvx();

        //Create mvxformsApp
        var mvxFormsApp = new MvxFormsApp();
        var presenter = Mvx.Resolve<IMvxViewPresenter>() as MvxFormsDroidPagePresenter;

        //Assign the viewPresenter
        presenter.MvxFormsApp = mvxFormsApp;
        LoadApplication(mvxFormsApp);

        //Start mvxApp
        Mvx.Resolve<IMvxAppStart>().Start();
    }

    private void InitialiseMvx()
    {
        if (MvxSingleton<IMvxIoCProvider>.Instance == null)
            Mvx.RegisterSingleton(MvxSimpleIoCContainer.Initialize());

        MvxAndroidSetupSingleton.EnsureSingletonAvailable(this.ApplicationContext)
                                .EnsureInitialized();
    }
}

MainViewModel

public class MainViewModel : MvxViewModel
{
    private string _inputString;

    public ICommand NavigateCommand
    {
        get { return new MvxCommand(() => ShowViewModel<FirstViewModel>()); }
    }

    public string InputString
    {
        get { return _inputString; }
        set { SetProperty(ref _inputString, value); }
    }
}

Essentially what I'm looking for is the reverse of this: MvvmCross: How to navigate from regular view to Mvvm viewmodel on Android?

Upvotes: 0

Views: 1442

Answers (2)

Aiden Strydom
Aiden Strydom

Reputation: 1208

What I ended up doing was defining a empty interface IXFViewModel which I implement on the Forms ViewModels. Then in my custom presenter I get a list of interfaces which the MvxViewModelRequest.ViewModelType implements.

If the type IXFViewModel is found in the list of interfaces, the custom presenter - which extends MvxFormsDroidPagePresenter - handles the presentation by calling base.Show(request). If the IXFViewModel type is not found the custom presenter switches to an MvxAndroidViewPresenter to handle the view presentation.

The solution below:

Bootstrap

public class Setup : MvxAndroidSetup
{
    /// prefix ///

    protected override IMvxAndroidViewPresenter CreateViewPresenter()
    {
        var presenter = new XFDroidPresenter();
        Mvx.RegisterSingleton<IMvxViewPresenter>(presenter);
        return presenter;
    }
}

The Custom Presenter

public class XFDroidPresenter : MvxFormsDroidPagePresenter
{
    protected MvxAndroidViewPresenter DroidPresenter { get; set; }
    protected Activity Activity { get { return Mvx.Resolve<IMvxAndroidCurrentTopActivity>().Activity; } }

    public XFDroidPresenter()
    {
        DroidPresenter = new MvxAndroidViewPresenter();
    }

    public override void ChangePresentation(MvxPresentationHint hint)
    {
        if(Activity.GetType() != typeof(CrossFormsApp))
        {
            DroidPresenter.ChangePresentation(hint);
            return;
        }

        base.ChangePresentation(hint);
    }

    public override void Show(MvxViewModelRequest request)
    {
        var implementedInterfaces = request.ViewModelType
                                           .GetInterfaces()
                                           .ToList();

        if (!implementedInterfaces.Contains(typeof(IXFViewModel)))
        {
            DroidPresenter.Show(request);
            return;
        }

        base.Show(request);
    }
}

The Launcher and Forms Activity

[Activity(Label = "Hello MvvmCrossForms", Icon = "@drawable/icon", MainLauncher = true)]
public class CrossFormsApp : FormsApplicationActivity
{
    private MvxAndroidLifetimeMonitor _lifeTimeMonitor;

    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        Forms.Init(this, bundle);

        InitialiseMvx();

        var mvxFormsApp = new MvxFormsApp();
        var presenter = Mvx.Resolve<IMvxViewPresenter>() as MvxFormsDroidPagePresenter;

        presenter.MvxFormsApp = mvxFormsApp;
        LoadApplication(mvxFormsApp);
        Mvx.Resolve<IMvxAppStart>().Start();

        _lifeTimeMonitor = Mvx.Resolve<IMvxAndroidCurrentTopActivity>() as MvxAndroidLifetimeMonitor;
        _lifeTimeMonitor.OnCreate(this);
    }

    private void InitialiseMvx()
    {
        if (MvxSingleton<IMvxIoCProvider>.Instance == null)
            Mvx.RegisterSingleton(MvxSimpleIoCContainer.Initialize());

        MvxAndroidSetupSingleton.EnsureSingletonAvailable(this.ApplicationContext)
                                .EnsureInitialized();
    }

    protected override void OnStart()
    {
        base.OnStart ();
        _lifeTimeMonitor.OnStart(this);
    }

    protected override void OnRestart()
    {
        base.OnRestart ();
        _lifeTimeMonitor.OnRestart(this);
    }

    protected override void OnResume()
    {
        base.OnResume ();
        _lifeTimeMonitor.OnResume(this);
    }

    protected override void OnDestroy()
    {
        base.OnDestroy ();
        _lifeTimeMonitor.OnDestroy(this);
    }
}

The Interface and ViewModel

public interface IXFViewModel
{
}

public class MainViewModel : MvxViewModel, IXFViewModel
{
    private string _inputString;

    public ICommand NavigateCommand
    {
        get { return new MvxCommand(() => Close(this)); }
    }

    public string InputString
    {
        get { return _inputString; }
        set { SetProperty(ref _inputString, value); }
    }
}

Upvotes: 0

Sven-Michael St&#252;be
Sven-Michael St&#252;be

Reputation: 14750

In MvvmCross, the ViewPresenter is the component, that handles the navigation between the views.

So we have to create our own

  • that inherits from MvxFormsDroidPagePresenter, because we have a Forms App
  • redirects non-Forms calls of

    • ChangePresentation (which is usally used for Close())
    • Show

    to a ViewPresenter that handles Activities (MvxAndroidViewPresenter)

  • a list of non-Forms ViewModels (_androidViews)
class HybridDroidViewPresenter : MvxFormsDroidPagePresenter
{
    private readonly MvxAndroidViewPresenter _androidPesenter;
    private readonly List<Type> _androidViews;
    protected Activity Activity => Mvx.Resolve<IMvxAndroidCurrentTopActivity>().Activity;


    public HybridDroidViewPresenter()
    {
        _androidPesenter = new MvxAndroidViewPresenter();
        _androidViews = new List<Type>
        {
            typeof (FirstViewModel)
        };
    }

    public override void ChangePresentation(MvxPresentationHint hint)
    {
        if (Activity.GetType() != typeof (MainActivity))
        {
            // if we are not on the Forms Activity, we assume, we are on an Android Activity
            _androidPesenter.ChangePresentation(hint);
            return;
        }

        base.ChangePresentation(hint);
    }

    public override void Show(MvxViewModelRequest request)
    {
        if (_androidViews.Contains(request.ViewModelType))
        {
            _androidPesenter.Show(request);
            return;
        }

        base.Show(request);
    }
}

To register our new ViewPresenter, we have to override CreateViewPresenter in our Setup

public class Setup : MvxAndroidSetup
{
    // ...

    protected override IMvxAndroidViewPresenter CreateViewPresenter()
    {
        var presenter = new HybridDroidViewPresenter();
        Mvx.RegisterSingleton<IMvxViewPresenter>(presenter);
        return presenter;
    }
}

Our ViewPresenter and the MvxAndroidViewPresenter needs IMvxAndroidCurrentTopActivity. So we need to let it know about the life cycle of our MainActivity. This can be done via IMvxAndroidActivityLifetimeListener and modifying the Activity as follows:

[Activity(Label = "MainActivity", ScreenOrientation = ScreenOrientation.Portrait)]
public class MainActivity
    : FormsApplicationActivity
{
    private IMvxAndroidActivityLifetimeListener _lifetimeListener;

    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        Forms.Init(this, bundle);
        var mvxFormsApp = new MvxFormsApp();
        LoadApplication(mvxFormsApp);

        var presenter = (MvxFormsDroidPagePresenter) Mvx.Resolve<IMvxViewPresenter>();
        presenter.MvxFormsApp = mvxFormsApp;

        Mvx.Resolve<IMvxAppStart>().Start();
        _lifetimeListener = Mvx.Resolve<IMvxAndroidActivityLifetimeListener>();
        _lifetimeListener.OnCreate(this);
    }

    protected override void OnDestroy()
    {
        _lifetimeListener.OnDestroy(this);
        base.OnDestroy();
    }

    protected override void OnNewIntent(Intent intent)
    {
        base.OnNewIntent(intent);
        _lifetimeListener.OnViewNewIntent(this);
    }

    protected override void OnResume()
    {
        base.OnResume();
        _lifetimeListener.OnResume(this);
    }

    protected override void OnPause()
    {
        _lifetimeListener.OnPause(this);
        base.OnPause();
    }

    protected override void OnStart()
    {
        base.OnStart();
        _lifetimeListener.OnStart(this);
    }

    protected override void OnRestart()
    {
        base.OnRestart();
        _lifetimeListener.OnRestart(this);
    }

    protected override void OnStop()
    {
        _lifetimeListener.OnStop(this);
        base.OnStop();
    }
}

Now we are able to navigate to our FirstViewModel via

ShowViewModel<FirstViewModel>()

and the FirstActivity looks like a normal MvvMCross Android Activity and sets the FirstView.axml as ContentView.

[Activity(Label = "FirstActivity")]
public class FirstActivity : MvxActivity<FirstViewModel>
{
    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        SetContentView(Resource.Layout.FirstView);
    }
}

Be aware of:

  • With this you can't navigate to a Forms-View form a non-Forms View (but to non-Forms Views), except going back via Close(), or the back button
  • There might be some issues left with the lifecycle (e.g. tombstoning)
  • The devs of MvvMCross are unhappy with the Forms-Support of MvvMCross, so there might be some breaking changes on the Forms Presenter

Upvotes: 2

Related Questions