I'm making app in Xamarin with MVVMCross. This is my code:
Android View (iOS is almost the same):
var button = (Button)FindViewById(Resource.Id.button3);
var set = this.CreateBindingSet<MenuView, MenuViewModel>();
set.Bind(button).To(vm => vm.CommandNavigateToSecondPage);
set.Apply();
Core ViewModel:
public ICommand CommandNavigateToSecondPage
{
get
{
return new MvxCommand((() =>
{
ShowViewModel<SecondPageViewModel>();
}));
}
}
I want to have a default back button that will navigate me to previous page. I made same navigation with simple function in core and there was back button. Like this:
public void Navigate()
{
ShowViewModel<SecondPageViewModel>();
}
MVVM is all about binding this is why I want to make it in this way.
To display default home/back button in android
[Activity (Label = "MyView")]
public class MyView : MvxActivity
{
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
SetContentView(Resource.Layout.ThirdNativeView);
ActionBar.SetDisplayHomeAsUpEnabled(true);
}
/// handle back navigation here
public override bool OnOptionsItemSelected(IMenuItem item)
{
switch (item.ItemId)
{
case Android.Resource.Id.Home:
//Execute viewModel command here
this.Finish();
return true;
default:
return base.OnOptionsItemSelected(item);
}
}
}
more info on executing command here
Related
I am new to MvvmCross and still figuring out how things are done. I am using BEMCheckBox for one of my developing applications and I tried to bind BEMCheckBox's "On" property to ViewModel. Unfortunately, it is not working as expected.
BEMCheckBox
Github : https://github.com/saturdaymp/XPlugins.iOS.BEMCheckBox
NuGet : SaturdayMP.XPlugins.iOS.BEMCheckBox
Version: 1.4.3
MvvmCross
Version : 6.4.2
This is my View Class
[MvxFromStoryboard("Main")]
[MvxRootPresentation(WrapInNavigationController = false)]
public partial class MyView : BaseView<MyViewModel>
{
private BEMCheckBox CheckBox;
public MyView(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
//add check box
CheckBox = new BEMCheckBox(new CoreGraphics.CGRect(0, 0, 20, 20))
{
BoxType = BEMBoxType.Square,
TintColor = UIColor.FromRGBA(0, 0, 0, 0.14f),
OnFillColor = UIColor.FromRGB(42, 183, 202),
OnCheckColor = UIColor.White,
OnTintColor = UIColor.FromRGBA(0, 0, 0, 0.14f),
OnAnimationType = BEMAnimationType.Bounce,
OffAnimationType = BEMAnimationType.Bounce,
On = false,
CornerRadius = 0,
};
//CheckBoxContainerView is an UIView
CheckBoxContainerView.AddSubview(CheckBox);
CheckBoxContainerView.BackgroundColor = UIColor.Clear;
//Binding to View Model
var set = this.CreateBindingSet<MyView, MyViewModel>();
set.Bind(CheckBox).For(c => c.On).To(vm => vm.IsCheckBoxOn);
set.Apply();
}
}
This is my ViewModel Class
public class MyViewModel : BaseViewModel
{
private bool _isCheckBoxOn;
public bool IsCheckBoxOn
{
get => _isCheckBoxOn;
set
{
SetProperty(ref _isCheckBoxOn, value);
Console.WriteLine(_isCheckBoxOn);
}
}
public MyViewModel : base()
{
}
public override Task Initialize()
{
return base.Initialize();
}
}
When I do the same thing for UISwitch it works perfectly. Any help would be highly appreciated.
MvvmCross includes many target bindings out of the box, UISwitch being one of them. For a complete list see "built-in-bindings" in MvvmCross documentation.
One-way binding (ViewModel -> View)
By default, you can create a binding to assign a value to a property on your view from your view model, this is called one-way binding. The approach works even with custom 3rd party controls, as long as the types match.
Two-way binding (ViewModel -> View && View -> ViewModel)
In order to do a two-way bind, MvvmCross needs a mechanise for the view to notify the view model that a value of the view has changed. For this MvvmCross using target bindings. See MvvCross documentation for creating custom target bindings.
Example
See MvvmCross source code for MvxUISwitchOnTargetBinding.cs which shows how they do the binding for the UISwitch control and the On state.
public class MvxUISwitchOnTargetBinding : MvxTargetBinding<UISwitch, bool>
{
private IDisposable _subscription;
public MvxUISwitchOnTargetBinding(UISwitch target)
: base(target)
{
}
protected override void SetValue(bool value)
{
Target.SetState(value, true);
}
public override void SubscribeToEvents()
{
var uiSwitch = Target;
if (uiSwitch == null)
{
MvxBindingLog.Error( "Error - Switch is null in MvxUISwitchOnTargetBinding");
return;
}
_subscription = uiSwitch.WeakSubscribe(nameof(uiSwitch.ValueChanged), HandleValueChanged);
}
public override MvxBindingMode DefaultMode => MvxBindingMode.TwoWay;
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (!isDisposing) return;
_subscription?.Dispose();
_subscription = null;
}
private void HandleValueChanged(object sender, EventArgs e)
{
FireValueChanged(Target.On);
}
}
Note the HandleValueChanged method which calls the FireValueChanged() method, this is the method that passes the value that you want to send back up to your bound view model.
You will then need to register your custom target bindings in your Setup.cs class by overriding the FillTargetFactories.
I am using Xamarin Forms with Prism, based on this GitHub sample..
Desired Behavior
Deep link is clicked, showing the detail view:
User presses back button. Scroll and highlight the linked selection (not happening).
None of the OnNavigation events are firing. Is this a bug? How do I accomplish this?
App.Xaml
public partial class App : PrismApplication
{
public App(IPlatformInitializer initializer = null) : base(initializer) { }
protected override async void OnInitialized()
{
InitializeComponent();
await NavigationService.NavigateAsync("MainTabbedPage/NavigationPage/ShowsListPage/DetailPage?show=279121");
//await NavigationService.NavigateAsync("MainTabbedPage/NavigationPage/ShowsListPage");
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<UpcomingShowsPage>();
Container.RegisterTypeForNavigation<ShowsListPage>(); // <-- Problematic ListView
Container.RegisterTypeForNavigation<DetailPage>();
Container.RegisterTypeForNavigation<MainTabbedPage>();
Container.RegisterTypeForNavigation<NavigationPage>();
Container.RegisterType<ITsApiService, TsApiService>();
}
ShowsListPage.xaml
ContentPage is using the Prism directive: prism:ViewModelLocator.AutowireViewModel="True". (nothing special)
ShowsListPageViewModel.cs
using System.Collections.ObjectModel;
using InfoSeries.Core.Models;
using InfoSeries.Core.Services;
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using Xamarin.Forms;
namespace DeepNavigation.ViewModels
{
public class ShowsListPageViewModel : BindableBase, INavigationAware
{
private readonly ITsApiService _tsApiService;
private readonly INavigationService _navigationService;
private ObservableCollection<SerieFollowersVM> _highlightSeries;
public ObservableCollection<SerieFollowersVM> HighlightSeries
{
get { return _highlightSeries; }
set { SetProperty(ref _highlightSeries, value); }
}
public ShowsListPageViewModel(ITsApiService tsApiService, INavigationService navigationService)
{
_tsApiService = tsApiService;
_navigationService = navigationService;
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public async void OnNavigatedTo(NavigationParameters parameters)
{
var series = await _tsApiService.GetStatsTopSeries();
HighlightSeries = new ObservableCollection<SerieFollowersVM>(series);
}
public void OnNavigatingTo(NavigationParameters parameters)
{
}
private DelegateCommand<ItemTappedEventArgs> _goToDetailPage;
public DelegateCommand<ItemTappedEventArgs> GoToDetailPage
{
get
{
if (_goToDetailPage == null)
{
_goToDetailPage = new DelegateCommand<ItemTappedEventArgs>(async selected =>
{
NavigationParameters param = new NavigationParameters();
var serie = selected.Item as SerieFollowersVM;
param.Add("show", serie.Id);
await _navigationService.NavigateAsync("DetailPage", param);
});
}
return _goToDetailPage;
}
}
}
}
Question
How can I get the back button to select the list view?
Is there any platform guidance saying that the back button after a deep link must go to the source calling application.. rendering this question useless? (e.g. pop the navigation back to Chrome/Safari)
I writing app for Android using Xamarin.
I have this code in Activity for OnCreate method.
protected override int LayoutResource
{
get { return Resource.Layout.Main; }
}
private RecyclerView recyclerView;
private ProgressBar activityIndicator;
private RecyclerView.LayoutManager layoutManager;
protected override async void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
recyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerView);
activityIndicator = FindViewById<ProgressBar>(Resource.Id.activityIndicator);
activityIndicator.Visibility = Android.Views.ViewStates.Visible;
layoutManager = new LinearLayoutManager(this, LinearLayoutManager.Vertical, false);
recyclerView.SetLayoutManager(layoutManager);
var repository = new TestAppRepository();
var films = await repository.GetAllFilms();
var formsAdapter = new FormAdapter(films.results);
recyclerView.SetAdapter(formsAdapter);
activityIndicator.Visibility = Android.Views.ViewStates.Gone;
SupportActionBar.SetDisplayHomeAsUpEnabled(false);
SupportActionBar.SetHomeButtonEnabled(false);
I have Button in toolbar and need to refresh Recycler when I tap this button.
Here is calling of it for display
public override bool OnCreateOptionsMenu(IMenu menu)
{
MenuInflater.Inflate(Resource.Menu.home, menu);
return base.OnCreateOptionsMenu(menu);
}
How I need to write code to refresh Recycler?
Thank's for help
Use the button's setOnClickListener method and implement the onClick method of the new OnClickListener In the onClick method called the RecyclerView adapter's notifydatasetchanged()
I didn't know how better to word the title so I went with solution that came to my mind.
Here is the problem. I have a page that has list and each item on the lists opens a detail page (on click). But the VM is reused, which causes me several problems.
Previous data can be seen for split second when opening a the detail page
I need certain properties to be set to specific values when the page open, but since the VM is reused it keeps all the values from the previous detail and this messes up my logic.
This UWP app. I'm using Template10 framework's NavigationService to move between pages.
Main Page ViewModel
public class MainPageViewModel : ViewModelBase {
private List<MangaItem> _mangaList;
public List<MangaItem> mangaList {
get { return _mangaList; }
set { Set(ref _mangaList, value); }
}
private string _mainSearchText;
public string mainSearchText {
get { return _mainSearchText; }
set { Set(ref _mainSearchText, value); }
}
public MainPageViewModel() {
_mangaList = new List<MangaItem>();
mangaList = new List<MangaItem>();
Initialize();
}
private async void Initialize() {
mangaList = await MangaListGet.GetListAsync();
}
public async void MainSearchSubmitted() {
mangaList = await MangaListGet.GetListAsync(_mainSearchText);
}
public void MangaSelected(object sender, ItemClickEventArgs e) {
var mangaItem = (MangaItem)e.ClickedItem;
NavigationService.Navigate(typeof(Views.MangaDetail), mangaItem.id);
}
}
And Detail Page ViewModel
class MangaDetailViewModel : ViewModelBase {
private MangaItem _mangaDetail;
public MangaItem mangaDetail {
get { return _mangaDetail; }
set { Set(ref _mangaDetail, value); }
}
private string _mangaId;
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> suspensionState) {
_mangaId = parameter as string;
Initialize();
await Task.CompletedTask;
}
private async void Initialize() {
mangaDetail = await MangaDetailGet.GetAsync(_mangaId);
}
public void ChapterSelected(object sender, ItemClickEventArgs e) {
var _chapterId = (ChapterListItem)e.ClickedItem;
NavigationService.Navigate(typeof(Views.ChapterPage), _chapterId.id);
}
}
This code only shows the first problem is displaying previously loaded data for a split second. If needed I will add code that showcases the other problem, but I' not sure if it's really relevant right now. I'm thinking that maybe my entire logic is flawed or something.
EDIT:
<Page.DataContext>
<vm:ChapterPageViewModel x:Name="ViewModel" />
</Page.DataContext>
where vm is xmlns:vm="using:MangaReader.ViewModels".
Another solution is to use Bootstrapper.ResolveforPage() which is intended to handle dependency injection but would easily serve your needs. Like this:
[Bindable]
sealed partial class App : BootStrapper
{
static ViewModels.DetailPageViewModel _reusedDetailPageViewModel;
public override INavigable ResolveForPage(Page page, NavigationService navigationService)
{
if (page.GetType() == typeof(Views.DetailPage))
{
if (_reusedDetailPageViewModel == null)
{
_reusedDetailPageViewModel = new ViewModels.DetailPageViewModel();
}
return _reusedDetailPageViewModel;
}
else
{
return null;
}
}
}
The NavigationService will treat this the same as any other view-model. Meaning it will call OnNavTo() and the other navigation overrides you include.
Best of luck.
While Template10 documentation states the NavigationCacheMode is disabled by default, that isn't the case in it's example templates (as of writing this). This is set in View C# code (.xaml.cs file).
.xaml.cs file
namespace MangaReader.Views {
public sealed partial class MangaDetail : Page {
public MangaDetail() {
InitializeComponent();
//NavigationCacheMode = Windows.UI.Xaml.Navigation.NavigationCacheMode.Enabled; //this was set by default
NavigationCacheMode = Windows.UI.Xaml.Navigation.NavigationCacheMode.Disabled;
}
}
}
Now, new ViewModel will be created each time you access a this page.
From what I've been able to gather from google, calling RegisterForContextMenu on a view should be all I have to do if I want OnCreateContextMenu to be called when I long click on said view. This does not happen.
The funny thing is, if I pass in View instead of gameList in the code below, the context menu appears if I long click at an empty portion of the main view. Long clicking on the list (which is a subview) still has no result.
I also tried registering a ItemLongClick event listener on the ListView, this does not get called either :/
The GameList fragment is run inside a viewpager.
using Android.Views;
using Android.OS;
using Android.Runtime;
using Android.Widget;
using Android.Support.V4.App;
using Models = Boardwar.Common.Models;
using Boardwar.AndroidClient.Adapters;
namespace Boardwar.AndroidClient.Fragments {
public class GameList : Fragment {
GamesAdapter GamesAdapter;
public GameList () {
}
public GameList (IntPtr handle, JniHandleOwnership jni) : base(handle, jni) {
}
public override View OnCreateView (LayoutInflater inflater, ViewGroup parent, Bundle bundle) {
return inflater.Inflate(Resource.Layout.GameList, parent, false);
}
public override void OnActivityCreated (Bundle p0) {
base.OnActivityCreated(p0);
var mainActivity = Activity as MainActivity;
var gameList = View.FindViewById<ListView>(Resource.Id.game_list);
GamesAdapter = new GamesAdapter(mainActivity);
gameList.Adapter = GamesAdapter;
mainActivity.GamesAdapter = GamesAdapter;
RegisterForContextMenu(gameList);
View.FindViewById(Resource.Id.new_game_button).Click += (sender, e) => {
mainActivity.ReplaceChildFragments(typeof(NewGame));
};
}
public override void OnCreateContextMenu (IContextMenu menu, View view, IContextMenuContextMenuInfo menuInfo) {
base.OnCreateContextMenu(menu, view, menuInfo);
menu.Add(Resource.String.remove_finished_games);
}
public override bool OnContextItemSelected (IMenuItem item) {
MainActivity.Client.RemoveFinishedGames();
return true;
}
public override void OnResume () {
base.OnResume();
GamesAdapter.UpdateList();
}
public override void OnDestroyView () {
base.OnDestroyView();
(Activity as MainActivity).GamesAdapter = null;
}
}
}
My problem was that I was registering ClickHandlers on the actual list-items, instead of a global ClickHandler on the actual list-view. This would block the click event from propegating to the list-view, which is why the context menu wouldn't appear.