Xamrin.Forms(Android) MessagingCenter Not Working - xamarin

The created MessagingCenter is not working,
The value generated by the Guid variable is not moved to the other page.
my Code in CustomWebviewRenderer.cs
Guid guid = Guid.NewGuid();
MessagingCenter.Send<CustomWebViewRenderer, Guid>(this, "Hi", guid);
MainActivity.cs
Guid guid;
MessagingCenter.Subscribe<CustomWebViewRenderer, Guid>(this, "Hi",(arg1, arg2) =>
{
guid = arg2;
});

For me this is also buggy as hell so i just made a messager class in shared code that works well, and call it from Android.
Android code example:
Messager.Notify.TabBarClicked((int)tab.Position);
Shared code example:
public class Messager
{
public readonly static Messager Notify = new Messager();
//-------------------------------------------------------------------
public void TabBarClicked(int tag)
//-------------------------------------------------------------------
{
MessagingCenter.Send(this, "TabBarClicked", tag.ToString());
}
// something more...
}
Adapt to your case..
Another buggy point is that i have much fails with messages if they have less or more than 1 arg parameter. And for me they also hate if parameters are not String type. No idea why, so just using workarounds..

Related

Dynamic ViewModel navigation

I am trying to find a way to be able to set from the View to what ViewModel I have to navigate. This is to be able to change the navigation flow without changing the core project.
I thought the easier way would be creating an interface, setting the target ViewModel there and injecting the interface into the ViewModel to then perform the navigation.
public interface IModelMapping
{
MvxViewModel ViewModelToNavigate();
}
public class MyViewModel : MvxViewModel
{
readonly IMvxNavigationService navigationService;
readonly IModelMapping modelMapping;
public MyViewModel(IMvxNavigationService navigationService, IModelMapping modelMapping)
{
this.navigationService = navigationService;
this.modelMapping = modelMapping;
}
public IMvxAsyncCommand GoContent
{
get
{
IMvxViewModel vm = modelMapping.ViewModelToNavigate();
IMvxAsyncCommand navigateCommand = new MvxAsyncCommand(() => navigationService.Navigate<vm>());
return navigteCommand;
}
}
}
The problem with this code is I am getting an error setting the navigationService.Navigate(). The error is 'vm is a variable but it is used like a type'
What about using the URI navigation together with the facade? See also https://www.mvvmcross.com/documentation/fundamentals/navigation#uri-navigation
Say you are building a task app and depending on the type of task you want to show a different view. This is where NavigationFacades come in handy (there is only so much regular expressions can do for you).
mvx://task/?id=00 <– this task is done, show read-only view (ViewModelA)
mvx://task/?id=01 <– this task isn’t, go straight to edit view (ViewModelB)
[assembly: MvxRouting(typeof(SimpleNavigationFacade), #"mvx://task/\?id=(?<id>[A-Z0-9]{32})$")]
namespace *.NavigationFacades
{
public class SimpleNavigationFacade
: IMvxNavigationFacade
{
public Task<MvxViewModelRequest> BuildViewModelRequest(string url,
IDictionary<string, string> currentParameters, MvxRequestedBy requestedBy)
{
// you can load data from a database etc.
// try not to do a lot of work here, as the user is waiting for the UI to do something ;)
var viewModelType = currentParameters["id"] == Guid.Empty.ToString("N") ? typeof(ViewModelA) : typeof(ViewModelB);
return Task.FromResult(new MvxViewModelRequest(viewModelType, new MvxBundle(), null, requestedBy));
}
}
}

Prism - moving data between viewmodels

I'm struggling to find the best implementation.
I'm using Prism and I have a View (ParentView), which has a small region within it. Depending on the item in a ddl, another smaller view (ChildView) gets injected into the region of the ParentView.
The ChildView will just have some properties which I would like to access from the ParentView.
So I realize I can use a Publish/Subscribe method to move data between viewmodels, but the issue is I have nothing to hang the Publish on. The view is made up of TextBoxes and no event triggers. The ChildView can be vastly different based on the selection of the ddl. I like the clean separation of each ChildView being it's own view injected inside the ParentView.
What is the best way to achieve this?
One solution can be to implement the interface INavigationAware in your viewmodels. After that you can use the methods onNavigatedFrom(), onNavigatedTo() and onNavigatingTo() to register your event.
EDIT:
If you want launch the event when a field in the child is changed you can do something like this:
private string _yourField;
public string YourField
{
get { return _yourField; }
set { SetProperty(ref _yourField, value);
//Here you can launch the event
}
}
In this case when YourField change the event is launched.
I tried a few implementations, but the one that worked was creating a singleton instance of the ChildView (childviewmodel) and then gaining access to the properties through the instance. It may not be pretty, but it works.
private static ChildViewModel _instance = new ChildViewModel ();
public static ChildViewModel Instance { get { return _instance; } }
#region Properties
private ChildModel _childModel= new ChildModel ();
public ChildModel _childModel
{
get { return _instance._childModel; }
set
{
SetProperty(ref _instance._childModel, value);
}
}
private string _childProperty1;
public string ChildProperty1
{
get { return _childProperty1; }
set
{
SetProperty(ref _childProperty1, value);
ChildModel.ChildProperty1= _childProperty1;
}
}
In reality - there were many childproperties. I only listed one for demo. And then I call it in ParentView
var _instance = ChildViewModel.Instance;
var _cm = _instance.ChildModel;
_parentModel = new ParentModel
{
Property1= ParentViewProperty1,
Property2= _cm.ChildProperty1,
};
Hope that helps someone else.

Binding CalendarView DateChange event with MvvmCross

I have a CalendarView that looks like this:
<CalendarView
android:layout_width="match_parent"
android:layout_height="300dp"
android:id="#+id/createReservationCalendarView" />
Here is how I handle the DateChange event without MvvmCross:
protected override void OnCreate(Bundle savedInstanceState)
{
... Code ...
calendar.DateChange += (s, args) =>
{
var year = args.Year;
var month = args.Month + 1;
var dayOfMont = args.DayOfMonth;
var date = new DateTime(year, month, dayOfMont);
var myReservations = new Intent(this, typeof(CreateReservationTimeslotScreen));
myReservations.PutExtra("selectedDate", date.Ticks);
StartActivity(myReservations);
};
}
Now that I have switched to MvvmCross, I would like to have my ViewModel start the new activity instead.
Im not sure how to do this, since the ViewModel should be OS and UI agnostic.
The "args" argument is of type CalendarView.DateChangeEventArgs, which is Android specific, so I cant use that in the ViewModel. It derives from System.EventArgs, so maybe I could use that instead. I am thinking that there must be a simpler way.
A thought that I had was if it is possible to update a property on the ViewModel from the activity, and then execute the switch to the new Activity from there? I'm not sure how this could be accomplished since activites dont have references to their ViewModels.
Any suggestions?
Thanks.
MvvmCross does give you access to your ViewModel from your View. The relationship between your View (e.g. Activity/fragment in Android) and your ViewModel, and their ability to share data (models) in both directions is a core characteristic a Mvvm framework.
In order to setup an Activity to be used with MvvmCross you need to make sure to inherit from MvxActivity or MvxAppCompatActivity (If using Android Support Library). Following which you need to link your Activity to its corresponding ViewModel using one of the possible conventions (See link, for basic sample of each registration offered by the MvxViewModelViewTypeFinder). A simple example would be to use the concrete type based registration using the type parameter overload.
public class FirstActivity : MvxAppCompatActivity<FirstViewModel>
Now that you have access to your ViewModel from your View you can create a command that can be used to execute the navigation:
CalendarViewModel (ViewModel linked to the current Activity in question)
Create a command that requires a DateTime parameter, which in turn will pass the value when navigation (see MvvmCross Navigation docs for alternative navigation and parameter passing conventions).
public class CalendarViewModel : MvxViewModel
{
IMvxCommand _goToMyReservationCommand;
public IMvxCommand GoToMyReservationCommand =>
_goToMyReservationCommand ??
(_goToMyReservationCommand = new MvxCommand<DateTime>(NavigateToMyReservation));
void NavigateToMyReservation(DateTime reservationDate)
{
ShowViewModel<MyReservationViewModel>(
new GoToMyReservationParameter
{
ReservationTicks = reservationDate.Ticks
});
}
}
Navigation Parameter Class
Holds the values and type information used for navigation.
public class GoToMyReservationParameter
{
public long ReservationTicks { get; set; }
}
MyReservationViewModel
The ViewModel that will receive the value passed.
public class MyReservationViewModel : MvxViewModel
{
public void Init(GoToMyReservationParameter parameters)
{
var reservationTicks = parameters.ReservationTicks;
// Do what you need with the parameters
}
}
View
Execute the command on the ViewModel and pass through the DateTime object.
public class CalendarActivity : MvxAppCompatActivity<CalendarViewModel>
{
protected override void OnCreate(Bundle bundle)
{
... Code...
calendar.DateChange += (s, args) =>
{
var year = args.Year;
var month = args.Month + 1;
var dayOfMont = args.DayOfMonth;
var date = new DateTime(year, month, dayOfMont);
ViewModel.GoToMyReservationCommand.Execute(date);
};
}
}

mvvmlight - what's the "proper way" of picking up url parameters for a view model

I'm just switching a project across to mvvmlight and trying to do things "the right way"
I've got a simple app with a listbox
When an item is selected in the listbox, then I've hooked up a RelayCommand
This RelayCommand causes a call on an INavigationService (http://geekswithblogs.net/lbugnion/archive/2011/01/06/navigation-in-a-wp7-application-with-mvvm-light.aspx) which navigates to a url like "/DetailPage.xaml?DetailId=12"
The DetailPage.xaml is then loaded and ... this is where I'm a bit unsure...
how should the DetailPage get hooked up to a DetailView with DetailId of 12?
should I do this in Xaml somehow using a property on the ViewLocator?
should I do this in the NavigatedTo method?
Please feel free to point me to a full sample - sure this has been done a (hundred) thousand times before, but all the blogs and tutorials seem to be skipping this last trivial detail (focussing instead on the messaging and on the ioc on on the navigationservice)
Thanks!
The only place you can retrieve the URL parameter is in the view. So since your view is likely depending on it, you should fetch it in the OnNavigatedTo method.
Then, you should pass it along to your viewmodel, either using messaging (to expensive if you ask me), or by referring to your datacontext (which is the viewmodel I presume), and execeuting a method on that.
private AddTilePageViewModel ViewModel
{
get
{
return DataContext as AddTilePageViewModel;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var postalCode = NavigationContext.TryGetKey("PostalCode");
var country = NavigationContext.TryGetStringKey("Country");
if (postalCode.HasValue && string.IsNullOrEmpty(country) == false)
{
ViewModel.LoadCity(postalCode.Value, country);
}
base.OnNavigatedTo(e);
}
I'm using some special extensions for the NavigationContext to make it easier.
namespace System.Windows.Navigation
{
public static class NavigationExtensions
{
public static int? TryGetKey(this NavigationContext source, string key)
{
if (source.QueryString.ContainsKey(key))
{
string value = source.QueryString[key];
int result = 0;
if (int.TryParse(value, out result))
{
return result;
}
}
return null;
}
public static string TryGetStringKey(this NavigationContext source, string key)
{
if (source.QueryString.ContainsKey(key))
{
return source.QueryString[key];
}
return null;
}
}
}
Create a new WindowsPhoneDataBound application, it has an example of how to handle navigation between views. Basically you handle the navigation part in your view, then set the view's DataContext accord to the query string. I think it plays nicely with the MVVM pattern since your ViewModels don't have to know anything about navigation (which IMO should be handled at the UI level).

Waiting for a text change in WatiN

I am trying to test a web page that has ajax calls to update a price.
An ajax call is fired on page load to update an initially empty div.
This is the extension method I'm using to wait for a change in the inner text of the div.
public static void WaitForTextChange(this IE ie, string id)
{
string old = ie.Element(id).Text;
ie.Element(id).WaitUntil(!Find.ByText(old));
}
However it's not pausing even though when I write out the old value and ie.Element(id).Text after the wait, they are both null. I can't debug as this acts as a pause.
Can the Find.ByText not handle nulls or have I got something wrong.
Has anyone got some code working similar to this?
I found my own solution in the end after delving into WatiN's constraints.
Here's the solution:
public class TextConstraint : Constraint
{
private readonly string _text;
private readonly bool _negate;
public TextConstraint(string text)
{
_text = text;
_negate = false;
}
public TextConstraint(string text, bool negate)
{
_text = text;
_negate = negate;
}
public override void WriteDescriptionTo(TextWriter writer)
{
writer.Write("Find text to{0} match {1}.", _negate ? " not" : "", _text);
}
protected override bool MatchesImpl(IAttributeBag attributeBag, ConstraintContext context)
{
return (attributeBag.GetAdapter<Element>().Text == _text) ^ _negate;
}
}
And the updated extension method:
public static void WaitForTextChange(this IE ie, Element element)
{
string old = element.Text;
element.WaitUntil(new TextConstraint(old, true));
}
It assumes that the old value will be read before the change so there's a slight chance of a race condition if you use it too long after setting off the update but it works for me.

Resources