Navigate from Fragment to Activity MVVMCross - xamarin

I am struggling to perform a navigation from a Fragment to an Activity at ViewModel level. I have an Activity with a DrawerLayout and this Activity has a FrameLayout to display different Fragments selected from the DrawerLayout. That navigation is perform by the ViewModel of this Activity and it is calling the ViewModel of each Fragment to display. In one of the Fragments I added a button binding a IMvxCommand method to perform the navigation from the Fragment to a new Activity and here is where I have the problem because when I click on the button nothing happens.
Find below my code.
ViewModel of Fragment
public class MainFrameViewModel : ContentViewModel
{
readonly IMvxNavigationService navigationService;
public MainFrameViewModel(IMvxNavigationService navigationService) : base(navigationService)
{
this.navigationService = navigationService;
}
public IMvxCommand GoMoreInfo
{
get
{
IMvxCommand navigateCommand = new MvxCommand(() => navigationService.Navigate<MoreInfoViewModel>());
return navigateCommand;
}
}
}
Fragment code
[MvxFragmentPresentation(typeof(ContentViewModel), Resource.Id.frameLayout)]
[Register("mvvmdemo.droid.views.fragments.MainFrameFragment")]
public class MainFrameFragment : MvxFragment<MainFrameViewModel>
{
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.Inflate(Resource.Layout.MainFrame, container, false);
}
}
Activity to navigate
[MvxActivityPresentation]
[Activity(Label = "MoreInfoActivity")]
public class MoreInfoActivity : MvxAppCompatActivity<MoreInfoViewModel>
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.MoreInfoLayout);
}
}
ContentViewModel is the ViewModel of the Activity containing the FrameLayout and the DrawerLayout.

Your bindings aren't working because you are using the default inflater, which knows nothing about MvvmCross bindings. You could solve this problem by using the MvvmCross inflater inside OnCreateView. Change return inflater.Inflate(Resource.Layout.MainFrame, container, false); call to return this.BindingInflate(Resource.Layout.MainFrame, null);
Also, you are ignoring the async part of the IMvxNavigationService. It would be an improvement to change from IMvxCommand to IMvxAsyncCommand and await or return the Task returned by IMvxNavigationService.Navigate()

Related

Binding text label on a home page to a timer

We have a really simple app, the idea is the timer will update a label on the home screen depending on different configuration within the mobile app. I have created the binding and can update the homepage from it's self but not from the timer. I think what is missing is a OnChange within the home page to detect if the string has changed.
Display layout code, bind the label to the name "LabelText"
<Label
Text = "{Binding LabelText, Mode=TwoWay}"
x:Name="MainPageStatusText"
HorizontalOptions="CenterAndExpand"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="6"
VerticalOptions="CenterAndExpand"
TextColor="White"
FontSize="Medium"/>
This is the class file to link the text string to the label, I can see it been called from the different places but when it's called from the app.cs it does not work
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Xamarin.Forms;
namespace Binding_Demo
{
public class MyClass : INotifyPropertyChanged
{
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{ PropertyChanged?.Invoke(this, e); }
protected void OnPropertyChanged(string propertyName)
{ OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); }
public event PropertyChangedEventHandler PropertyChanged;
private string labelText;
public string LabelText
{
get {
return labelText;
}
set
{
labelText = value;
OnPropertyChanged("LabelText");
}
}
}
}
This is the code inside the homepage, this works and I can see it sending data to the text label
public static MyClass _myClass = new MyClass();
public Homepage()
{
BindingContext = _myClass;
_myClass.LabelText = "Inside the home page";
}
This is the App.cs code, we start the timer and then want to set the text on the Homepage label. I can see the class been called, but it does not set the text.
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Binding_Demo
{
public partial class App : Application
{
public static MyClass _myClass = new MyClass();
public App()
{
//InitializeComponent();
Device.StartTimer(TimeSpan.FromSeconds(10), () =>
{
Task.Run(() =>
{
Debug.WriteLine("Timer has been triggered");
// !!!!! This is not setting the text in the label !!!!!
BindingContext = _myClass;
_myClass.LabelText = "Inside the timer app";
});
return true; //use this to run continuously
});
MainPage = new NavigationPage(new MainPage());
}
protected override void OnStart()
{
//
}
protected override void OnSleep()
{
}
protected override void OnResume()
{
// force app to mainpage and clear the token
}
}
}
I have created the binding and can update the homepage from it's self but not from the timer.
As Jason said, please make sure the binding model is unique. You could create a global static instance of MyClass in App class, then bind this instance to HomePage.
Check the code:
App.xaml.cs
public partial class App : Application
{
public static MyClass _myClass = new MyClass();
public App()
{
InitializeComponent();
Device.StartTimer(TimeSpan.FromSeconds(5), () =>
{
Task.Run(() =>
{
_myClass.LabelText = "Inside the timer app";
});
return true;
});
MainPage = new NavigationPage(new Homepage());
}
}
Homepage.xaml.cs:
public Homepage()
{
InitializeComponent();
BindingContext = App._myClass;
}

BindingContext not working for navigation in a button clicked event

I am attempting to navigate to a registration page from a login page using a button_clicked event. A new view model is created during the event and set as the BindingContext. However it does not appear to work
When I debug on the registration page I have an ICommand in the registration page view model that is attached to a button. The code is
public ICommand RegisterAppUser
{
get
{
return new RelayCommand(RegisterUser, () => Email == ConfirmEmail && Password == ConfirmPassword);
}
}
private async void RegisterUser()
{
var userDto = new RegistrationUserDto(this);
App.User = await App.PlayerManager.RegisterUserAsync(userDto);
}
I assumed the the "this" would represent the view model attached to the view. However when I reach the RegistrationUserDto(this) line I can see that the "this" is an unintialized instance of the registration view model
public partial class LoginPage : ContentPage
{
public LoginPage()
{
InitializeComponent();
}
private void Button_Clicked(object sender, EventArgs e)
{
Navigation.PushModalAsync(new RegistrationView() { BindingContext = new RegistrationViewModel() }); ;
}
}
No compilation errors but the action clearly can't work with no data

Can not override OnInterceptTouchEvent method in xamarin.android

I want to override OnInterceptTouchEvent method. I am using listview and trying to implement gesture detecor on it but as I appiled click listeners on child view so on fling method return null. I figured out that this method can solve my problem but I am unable to override in my activity.
#Override
public boolean onInterceptTouchEvent(MotionEvent motionEvent) {
return true;
}
If there exist any other approach to fix this please let me know.
Activity is not a view class, so there is no OnInterceptTouchEvent method,
OnInterceptTouchEvent is a function of the ViewGroup class that intercepts touch events.
So you could customize the ListView , then override the OnInterceptTouchEvent method,like this :
class MyListView : ListView
{
public MyListView(Context context) : base(context)
{
}
public MyListView(Context context, IAttributeSet attrs) : base(context, attrs)
{
}
public MyListView(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes)
{
}
public override bool OnInterceptTouchEvent(MotionEvent e)
{
return base.OnInterceptTouchEvent(e);
}
}
You are trying to override it the Java way when you should be using the C# equivalent.
Also, Intercept touch events are a part of the ViewGroup hierarchy and hence unavailable in your activity.
A ViewGroup is basically a class that holds a bunch of widgets together like a View holder.
Try pasting the below in your custom ListView and then use that ListView as your to implement a gesture detector
public override bool OnInterceptTouchEvent(MotionEvent ev)
{
return base.OnInterceptTouchEvent(ev);
}

access and open DisplayActionSheet from view model

i have a toolbar in my content page where there is one item called add , on clicking over add i want to open DisplayActionSheet
i have created ContentPage Toolbar in xaml and attached ICommand to it in view model. Now DisplayActionSheet is accessible only in View hence i am not sure how will i able to access it and render it from view model.
xaml file
<ContentPage.ToolbarItems>
<ToolbarItem Name="" Icon="ic_add.png" Order="Primary" Priority="0" Command="{Binding OnAddContactCommand}"/>
<ToolbarItem Name="" Icon="ic_search.png" Order="Primary" Priority="1" Command="{Binding OnContactSearchCommand}" />
</ContentPage.ToolbarItems>
View model
public ICommand OnContactSearchCommand => new Command(OnContactSearch);
public ICommand OnAddContactCommand => new Command(OnAddContactSearch);
events
private async void OnAddContactSearch()
{
//var action = await DisplayActionSheet(AppResources.select_contact_source, AppResources.cancel, null, AppResources.manual, AppResources.phonebook);
}
private void OnContactSearch()
{
Debug.WriteLine("OnContactSearch");
}
Like #Alessandro said Application.Current.MainPage works fine for action sheets and alerts as well. To hide view specific stuff from view model I created an IMessageBoxService which is injected into the view models' contructors that need it. Note that I am using the Autofac IoC container. For Xamarin's DependencyService you have change the constructors and look up the service in code.
IMessageBoxService.cs
public interface IMessageBoxService
{
void ShowAlert(string title, string message, Action onClosed = null);
// ...
Task<string> ShowActionSheet(string title, string cancel, string destruction, string[] buttons = null);
}
MessageBoxService.cs
public class MessageBoxService : IMessageBoxService
{
private static Page CurrentMainPage { get { return Application.Current.MainPage; } }
public async void ShowAlert(string title, string message, Action onClosed = null)
{
await CurrentMainPage.DisplayAlert(title, message, TextResources.ButtonOK);
onClosed?.Invoke();
}
public async Task<string> ShowActionSheet(string title, string cancel, string destruction = null, string[] buttons = null)
{
var displayButtons = buttons ?? new string[] { };
var action = await CurrentMainPage.DisplayActionSheet(title, cancel, destruction, displayButtons);
return action;
}
}
AppSetup.cs
protected void RegisterDependencies(ContainerBuilder cb)
{
// ...
cb.RegisterType<MessageBoxService>().As<IMessageBoxService>().SingleInstance();
}
Usage
public class EditProductViewModel : AddProductViewModel
{
private IMessageBoxService _messageBoxService;
public ICommand DeleteCommand { get; set; }
public EditProductViewModel(IPageNavigator navigator, IMessenger messenger,
IMessageBoxService messageBoxService, TagDataStore tagDataStore) : base(navigator, messenger, tagDataStore)
{
_messageBoxService = messageBoxService;
DeleteCommand = new Command(DeleteItem);
}
...
private async void DeleteItem()
{
var action = await _messageBoxService.ShowActionSheet(TextResources.MenuTitleDeleteProduct,
TextResources.ButtonCancel, TextResources.ButtonDelete);
if (action == TextResources.ButtonDelete)
{ } // delete
If you are doing viewmodel first navigation (s. Xamarin or Jonathan Yates' blog) you may chose to make this part of the Navigator service. It's a matter of taste
try with
Application.Current.MainPage.DisplayActionSheet();

Windows Phone Page navigation in MVVM

I am using MVVM in one of the app. I have created different project for Model, View and View Model.
I need to navigate to another XAML from ViewModel. I found some solution using MVVM light. Is there any way of implementing navigation from view model without using MVVM light.
Simple as that,
IF you want to navigate from page1 to page2,
private void MoveToPage2FromPage1()
{
NavigationService.Navigate(new Uri("/Page2.xaml", UriKind.Relative));
}
How to perform page navigation on Windows Phone 8
You can store current page url on a notify property of Shared ViewModel in App. After that, it is easy to catch the change of this url and navigate to the correct url by observing it.
public class AppViewModel : INotifyPropertyChanged
{
public string CurrentPageURL { get; set; }
private string _currentPageURL;
public string CurrentPageURL
{
get { return _currentPageURL;}
set
{
if (_currentPageURL==value)
return; // to prevent reload the same page.
_currentPageURL = value;
NotifyPropertyChangedCurrentPageURL
}
}
// INotifyPropertyChanged implementations
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
// Store in static singleton instance of AppViewModel
public class App : Application
{
private static Lazy<AppViewModel> _ViewModel=new Lazy<ViewModel>();
public static AppViewModel ViewModel { get { return _ViewModel.Value; } }
....
public App()
{
AppViewModel.PropertyChanged=(s,a) =>
{
if (a.PropertyName=="CurrentPageURL")
{
NavigationService.Navigate(new Uri(AppViewModel.CurrentPageURL, UriKind.Relative));
};
}
}
}
// Usage sample
public class Page1ViewModel
{
private btnMoveNextPage_Click(object s, EventHandler a) {
App.ViewModel.CurrentURL="~/Page2.xaml";
}
}

Resources